在技术分析中,如果 均线、MACD、KDJ、RSI 这四个指标都同时发出买入信号,那么该股票可能处于一个较好的买点。我们可以从多个角度来分析何时是最佳买点。
四大指标共振的最佳买点
要让这四个指标同时发出买入信号,通常市场会出现以下几种情况:
1. 突破均线支撑,形成多头排列
- 5日均线(MA5)上穿 10日均线(MA10),金叉向上
- 股价站上 20 日均线,均线系统由空头排列转为多头排列
- 股价在 60 日均线附近获得支撑,不破位
📌 信号解读:说明市场买盘强劲,趋势可能反转向上。
2. MACD 形成金叉,红柱放量
- DIF 线上穿 DEA 线,形成 MACD 金叉
- MACD 绿柱逐渐缩短,红柱开始放量
- MACD 处于 0 轴下方刚刚翻红,后续上涨空间大
📌 信号解读:MACD 反映的是趋势强度,金叉说明上涨趋势正在形成。
3. KDJ 低位金叉,K 值向上穿 D 值
- K 线(短期趋势)上穿 D 线(长期趋势),形成金叉
- K 值小于 30,说明股价严重超卖
- J 线急速上升,表明短期资金流入较强
📌 信号解读:说明短期市场可能出现强势反弹,尤其是在底部区域。
4. RSI 超卖反弹,突破 30
- RSI < 30 时,开始向上回升
- RSI 由下往上突破 50,表明市场买盘增强
- 股价创新低,但 RSI 没有创新低(底背离),预示着反弹可能到来
📌 信号解读:说明市场已经超卖,买方力量开始增强。
综合结论:最值得买入的时机
✅ 均线:股价突破 5 日、10 日均线,且均线形成多头排列。
✅ MACD:0 轴下方形成金叉,红柱放量。
✅ KDJ:K 线和 D 线在低位金叉,J 线向上突破。
✅ RSI:从超卖区(30 以下)反弹,突破 50 进一步确认。
⏳ 最佳买点:
股价经过长期下跌,在底部震荡整理后,四个指标同时形成买入信号,这时买入胜率较高。
注意事项
- 结合成交量:如果四个指标共振,同时放量上涨,信号更加强烈。
- 避免单边趋势行情:在强烈下跌趋势中,RSI 和 KDJ 的买入信号可能会失效。
- 结合市场环境:如果大盘指数整体走弱,即使个股技术指标看起来很好,也要谨慎。
附:通过python选股
import akshare as ak
import pandas as pd
import numpy as np
from tqdm import tqdm
# 计算均线
def calculate_ma(df, windows=[5, 10, 20]):
for w in windows:
df[f'MA{w}'] = df['close'].rolling(window=w).mean()
return df
# 计算 MACD 指标
def calculate_macd(df, short=12, long=26, signal=9):
df['EMA12'] = df['close'].ewm(span=short, adjust=False).mean()
df['EMA26'] = df['close'].ewm(span=long, adjust=False).mean()
df['DIF'] = df['EMA12'] - df['EMA26']
df['DEA'] = df['DIF'].ewm(span=signal, adjust=False).mean()
df['MACD'] = 2 * (df['DIF'] - df['DEA']) # 计算 MACD
return df
# 计算 KDJ 指标
def calculate_kdj(df, n=9, k_smooth=3, d_smooth=3):
low_min = df['low'].rolling(window=n).min()
high_max = df['high'].rolling(window=n).max()
df['RSV'] = (df['close'] - low_min) / (high_max - low_min) * 100
df['K'] = df['RSV'].ewm(alpha=1 / k_smooth, adjust=False).mean()
df['D'] = df['K'].ewm(alpha=1 / d_smooth, adjust=False).mean()
df['J'] = 3 * df['K'] - 2 * df['D']
return df
# 计算 RSI 指标
def calculate_rsi(df, period=14):
delta = df['close'].diff()
gain = np.where(delta > 0, delta, 0)
loss = np.where(delta < 0, -delta, 0)
avg_gain = pd.Series(gain).rolling(window=period).mean()
avg_loss = pd.Series(loss).rolling(window=period).mean()
rs = avg_gain / avg_loss
df['RSI'] = 100 - (100 / (1 + rs))
return df
# 获取 A 股市场所有股票
def get_shanghai_stock_list():
stock_list = ak.stock_info_sh_name_code()
# print(stock_list)
return stock_list[['证券代码', '证券简称']]
# 获取 sz市场所有股票
def get_sz_stock_list():
stock_list = ak.stock_info_sz_name_code()
# print(stock_list)
return stock_list[['A股代码', 'A股简称']]
# 获取单只股票的行情数据
def get_stock_data(stock_code, days=100):
try:
df = ak.stock_zh_a_hist(symbol=stock_code, start_date="20250101", period="daily", adjust="qfq")
#print(df)
df = df[['日期', '开盘', '最高', '最低', '收盘', '成交量']]
df.columns = ['date', 'open', 'high', 'low', 'close', 'volume']
df['date'] = pd.to_datetime(df['date'])
df = df.sort_values(by='date', ascending=True).reset_index(drop=True)
return df.tail(days)
except:
return None
# 筛选符合四大指标的股票
def stock_screening(stock_list, code_column, name_column, lookback_days=1):
selected_stocks = []
seeked_stocks = []
print(stock_list)
for index, row in tqdm(stock_list.iterrows(), total=len(stock_list)):
stock_code = row[code_column]
stock_name = row[name_column]
df = get_stock_data(stock_code)
if df is None or len(df) < 30:
continue
# 计算技术指标
df = calculate_ma(df)
df = calculate_macd(df)
df = calculate_kdj(df)
df = calculate_rsi(df)
# 遍历过去 lookback_days 天的数据
for i in range(1, lookback_days + 1):
last_row = df.iloc[-i]
prev_row = df.iloc[-(i + 1)]
# 打印最后几天的指标数据(仅当 lookback_days > 1 时)
# if lookback_days > 1:
# print(df.tail(lookback_days)[
# ['date', 'close', 'MA5', 'MA10', 'MA20', 'DIF', 'DEA', 'MACD', 'K', 'D', 'J', 'RSI']])
seeked_stocks.append((stock_code, stock_name))
# 筛选条件
if (
# 1. 均线:MA5 上穿 MA10,且股价站上 MA20
#prev_row['MA5'] < prev_row['MA10'] and last_row['MA5'] > last_row['MA10'] and last_row['close'] > last_row['MA20']
#2. MACD:DIF 上穿 DEA,且 MACD 处于 0 轴下方
#prev_row['DIF'] < prev_row['DEA'] and last_row['DIF'] > last_row['DEA'] and last_row['MACD'] < 0
# 3. KDJ:K 值和 D 值在 20 以下,形成金叉
prev_row['K'] < prev_row['D'] and last_row['K'] > last_row['D'] and last_row['K'] < 20
# # 4. RSI:RSI 从 30 以下反弹,并突破 50
#and prev_row['RSI'] < 30 and last_row['RSI'] > 30 and last_row['RSI'] > 50
):
# if (
# # 1. 均线:MA5 上穿 MA10,且股价站上 MA20
# # prev_row['MA5'] < prev_row['MA10'] and last_row['MA5'] > last_row['MA10'] and last_row['close'] > last_row['MA20']
# # 2. MACD:DIF 上穿 DEA(不要求 MACD 低于 0)
# prev_row['DIF'] < prev_row['DEA'] and last_row['DIF'] > last_row['DEA']
# # 3. KDJ:K 值和 D 值形成金叉(不要求 K < 30)
# and prev_row['K'] < prev_row['D'] and last_row['K'] > last_row['D']
# # 4. RSI:RSI 低位反弹(不要求 RSI 必须突破 50)
# and prev_row['RSI'] < 30 and last_row['RSI'] > prev_row['RSI']
# ):
if lookback_days > 1:
print(f"{stock_code} 在 {last_row['date']} 符合条件")
else:
selected_stocks.append((stock_code, stock_name))
return selected_stocks, seeked_stocks
# 使用示例
def stock_sh_screening():
stock_list = get_shanghai_stock_list()
return stock_screening(stock_list, '证券代码', '证券简称')
def stock_sz_screening():
stock_list = get_sz_stock_list()
return stock_screening(stock_list, 'A股代码', 'A股简称')
def stock_sh_last30_screening():
stock_list = get_shanghai_stock_list()
return stock_screening(stock_list, '证券代码', '证券简称', lookback_days=30)
def stock_sz_last30_screening():
stock_list = get_sz_stock_list()
return stock_screening(stock_list, 'A股代码', 'A股简称', lookback_days=30)
# 运行筛选
if __name__ == "__main__":
#stocks_sh, seeks_sh = stock_sz_last30_screening()
stocks_sh, seeks_sh = stock_sh_screening()
stocks_sz, seeks_sz = stock_sz_screening()
print("检索过的股票:")
print(seeks_sz)
print(seeks_sh)
print("符合条件的股票:")
print(stocks_sz)
print(stocks_sh)
结果输出示例:
检索过的股票:
[('000048', '京基智农'), ('000078', '海王生物'), ('000158', '常山北明'), ('000410', '沈阳机床'), ('000505', '京粮控股'), ('000506', '*ST中润'), ('000518', '四环生物'), ('000559', '万向钱潮'), ('000581', '威孚高科'), ('000603', '盛达资源'), ('000615', 'ST美谷'), ('000622', '*ST恒立'), ('000665', '湖北广电'), ('000668', '荣丰控股'), ('000678', '襄阳轴承'), ('000710', '贝瑞基因'), ('000711', '*ST京蓝'), ('000723', '美锦能源'), ('000735', '罗 牛 山'), ('000766', '通化金马'), ('000837', '秦川机床'), ('000890', '法尔胜'), ('000905', '厦门港务'), ('000910', '大亚圣象'), ('000929', '兰州黄河'), ('001212', '中旗新材'), ('001267', '汇绿生态'), ('001270', '铖昌科技'), ('001288', '运机集团'), ('001306', '夏厦精密'), ('002020', '京新药业'), ('002026', '山东威达'), ('002029', '七 匹 狼'), ('002033', '丽江股份'), ('002046', '国机精工'), ('002052', '*ST同洲'), ('002093', '国脉科技'), ('002099', '海翔药业'), ('002101', '广东鸿图'), ('002104', '恒宝股份'), ('002105', '信隆健康'), ('002109', '兴化股份'), ('002114', '罗平锌电'), ('002158', '汉钟精机'), ('002162', '悦心健康'), ('002167', '东方锆业'), ('002194', '武汉凡谷'), ('002196', '方正电机'), ('002197', 'ST证通'), ('002205', '国统股份'), ('002207', '准油股份'), ('002215', '诺 普 信'), ('002246', '北化股份'), ('002271', '东方雨虹'), ('002277', '友阿股份'), ('002306', '中科云网'), ('002338', '奥普光电'), ('002347', '泰尔股份'), ('002388', 'ST新亚'), ('002420', '毅昌科技'), ('002428', '云南锗业'), ('002472', '双环传动'), ('002484', '江海股份'), ('002523', '天桥起重'), ('002524', '光正眼科'), ('002529', '海源复材'), ('002553', '南方精工'), ('002580', '圣阳股份'), ('002589', '瑞康医药'), ('002593', '日上集团'), ('002614', '奥佳华'), ('002622', '皓宸医疗'), ('002628', '成都路桥'), ('002631', '德尔未来'), ('002633', '申科股份'), ('002634', '棒杰股份'), ('002672', '东江环保'), ('002690', '美亚光电'), ('002713', '东易日盛'), ('002741', '光华科技'), ('002760', '凤形股份'), ('002773', '康弘药业'), ('002802', '洪汇新材'), ('002816', '和科达'), ('002822', 'ST中装'), ('002840', '华统股份'), ('002846', '英联股份'), ('002851', '麦格米特'), ('002921', '联诚精密'), ('002947', '恒铭达'), ('002992', '宝明科技'), ('003018', '金富科技'), ('003019', '宸展光电'), ('300008', '天海防务'), ('300022', '吉峰科技'), ('300024', '机器人'), ('300048', '合康新能'), ('300078', '思创医惠'), ('300125', 'ST聆达'), ('300147', '香雪制药'), ('300167', '*ST迪威'), ('300168', '万达信息'), ('300179', '四方达'), ('300180', '华峰超纤'), ('300181', '佐力药业'), ('300199', '翰宇药业'), ('300208', '*ST中程'), ('300210', '森远股份'), ('300233', '金城医药'), ('300253', '卫宁健康'), ('300268', 'ST佳沃'), ('300320', '海达股份'), ('300404', '博济医药'), ('300412', '迦南科技'), ('300432', '富临精工'), ('300436', '广生堂'), ('300451', '创业慧康'), ('300457', '赢合科技'), ('300459', '汤姆猫'), ('300476', '胜宏科技'), ('300491', '通合科技'), ('300499', '高澜股份'), ('300506', '*ST名家'), ('300545', '联得装备'), ('300547', '川环科技'), ('300548', '博创科技'), ('300565', '科信技术'), ('300570', '太辰光'), ('300572', '安车检测'), ('300585', '奥联电子'), ('300593', '新雷能'), ('300605', '恒锋信息'), ('300630', '*ST普利'), ('300667', '必创科技'), ('300675', '建科院'), ('300693', '盛弘股份'), ('300726', '宏达电子'), ('300731', '科创新源'), ('300737', '科顺股份'), ('300752', '隆利科技'), ('300777', '中简科技'), ('300778', '新城市'), ('300810', '中科海讯'), ('300813', '泰林生物'), ('300819', '聚杰微纤'), ('300841', '康华生物'), ('300842', '帝科股份'), ('300853', '申昊科技'), ('300855', '图南股份'), ('300863', '卡倍亿'), ('300870', '欧陆通'), ('300873', '海晨股份'), ('300878', '维康药业'), ('300885', '海昌新材'), ('300901', '中胤时尚'), ('300922', '天秦装备'), ('300938', '信测标准'), ('300946', '恒而达'), ('300960', '通业科技'), ('300961', '深水海纳'), ('300986', '志特新材'), ('301005', '超捷股份'), ('301012', '扬电科技'), ('301017', '漱玉平民'), ('301021', '英诺激光'), ('301031', '中熔电气'), ('301032', '新柴股份'), ('301071', '力量钻石'), ('301079', '邵阳液压'), ('301093', '华兰股份'), ('301099', '雅创电子'), ('301120', '新特电气'), ('301161', '唯万密封'), ('301170', '锡南科技'), ('301177', '迪阿股份'), ('301190', '善水科技'), ('301216', '万凯新材'), ('301221', '光庭信息'), ('301225', '恒勃股份'), ('301251', '威尔高'), ('301255', '通力科技'), ('301256', '华融化学'), ('301257', '普蕊斯'), ('301261', '恒工精密'), ('301263', '泰恩康'), ('301268', '铭利达'), ('301279', '金道科技'), ('301320', '豪江智能'), ('301379', '天山电子'), ('301382', '蜂助手'), ('301389', '隆扬电子'), ('301397', '溯联股份'), ('301408', '华人健康'), ('301419', '阿莱德'), ('301421', '波长光电'), ('301459', '丰茂股份'), ('301486', '致尚科技'), ('301525', '儒竞科技'), ('301603', '乔锋智能'), ('301611', '珂玛科技')]
[('600107', '美尔雅'), ('600121', '郑州煤电'), ('600126', '杭钢股份'), ('600165', '*ST宁科'), ('600172', '黄河旋风'), ('600179', '安通控股'), ('600184', '光电股份'), ('600187', '国中水务'), ('600190', 'ST锦港'), ('600228', '返利科技'), ('600234', '*ST科新'), ('600237', '铜峰电子'), ('600272', '开开实业'), ('600288', '大恒科技'), ('600289', '*ST信通'), ('600315', '上海家化'), ('600345', '长江通信'), ('600359', '新农开发'), ('600360', 'ST华微'), ('600361', '创新新材'), ('600375', '*ST汉马'), ('600398', '海澜之家'), ('600403', '大有能源'), ('600416', '湘电股份'), ('600421', '华嵘控股'), ('600423', '柳化股份'), ('600478', '科力远'), ('600482', '中国动力'), ('600538', '国发股份'), ('600539', '狮头股份'), ('600560', '金自天正'), ('600563', '法拉电子'), ('600589', '大位科技'), ('600608', 'ST沪科'), ('600610', '中毅达'), ('600619', '海立股份'), ('600641', '万业企业'), ('600693', '东百集团'), ('600711', 'ST盛屯'), ('600712', '南宁百货'), ('600802', '福建水泥'), ('600815', '厦工股份'), ('600825', '新华传媒'), ('600829', '人民同泰'), ('600833', '第一医药'), ('600835', '上海机电'), ('600857', '宁波中百'), ('600860', '京城股份'), ('600895', '张江高科'), ('600897', '厦门空港'), ('600960', '渤海汽车'), ('600980', '北矿科技'), ('600984', '建设机械'), ('601010', '文峰股份'), ('601020', '华钰矿业'), ('601900', '南方传媒'), ('603002', '宏昌电子'), ('603003', '*ST龙宇'), ('603007', 'ST花王'), ('603023', '*ST威帝'), ('603036', '如通股份'), ('603057', '紫燕食品'), ('603062', '麦加芯彩'), ('603063', '禾望电气'), ('603065', '宿迁联盛'), ('603067', '振华股份'), ('603070', '万控智造'), ('603108', '润达医疗'), ('603112', '华翔股份'), ('603151', '邦基科技'), ('603161', '科华控股'), ('603166', '福达股份'), ('603200', '上海洗霸'), ('603228', '景旺电子'), ('603233', '大参林'), ('603267', '鸿远电子'), ('603291', '联合水务'), ('603303', '得邦照明'), ('603326', '我乐家居'), ('603330', '天洋新材'), ('603368', '柳药集团'), ('603377', 'ST东时'), ('603378', '亚士创能'), ('603388', 'ST元成'), ('603393', '新天然气'), ('603399', '永杉锂业'), ('603489', '八方股份'), ('603506', '南都物业'), ('603565', '中谷物流'), ('603588', '高能环境'), ('603607', '京华激光'), ('603618', '杭电股份'), ('603678', '火炬电子'), ('603688', '石英股份'), ('603716', '塞力医疗'), ('603717', '天域生物'), ('603721', '中广天择'), ('603737', '三棵树'), ('603788', '宁波高发'), ('603799', '华友钴业'), ('603813', '原尚股份'), ('603833', '欧派家居'), ('603878', '武进不锈'), ('603883', '老百姓'), ('603887', '城地香江'), ('603898', '好莱客'), ('603901', '永创智能'), ('603931', '格林达'), ('603939', '益丰药房'), ('603956', '威派格'), ('603960', '克来机电'), ('603983', '丸美生物'), ('603993', '洛阳钼业'), ('605008', '长鸿高科'), ('605018', '长华集团'), ('605068', '明新旭腾'), ('605081', '太和水'), ('605100', '华丰股份'), ('605128', '上海沿浦'), ('605133', '嵘泰股份'), ('605155', '西大门'), ('605178', '时空科技'), ('605198', '安德利'), ('605199', '葫芦娃'), ('605208', '永茂泰'), ('605228', '神通科技'), ('605266', '健之佳'), ('605303', '园林股份'), ('605318', '法狮龙'), ('605389', '长龄液压'), ('605566', '福莱蒽特'), ('605589', '圣泉集团')]
符合条件的股票:
[('000518', '四环生物'), ('300147', '香雪制药'), ('300548', '博创科技')]
[('600421', '华嵘控股'), ('603721', '中广天择'), ('605199', '葫芦娃')]
升级版python代码:
测试发现akshare库在linux系统下获取的数据才是全的,window下不全,应该是多线程并发造成后端有部分数据没回
import akshare as ak
import pandas as pd
import numpy as np
from tqdm import tqdm
import sqlite3
from datetime import datetime
# Database setup (SQLite example)
def init_db():
conn = None
try:
conn = sqlite3.connect('stock_data.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS stock_history (
stock_code TEXT,
date TEXT,
code TEXT,
open REAL,
close REAL,
high REAL,
low REAL,
volume REAL,
amount REAL,
amplitude REAL,
change_pct REAL,
change_amt REAL,
turnover REAL,
PRIMARY KEY (stock_code, date)
)
''')
# 为常用查询字段创建索引
cursor.execute('CREATE INDEX IF NOT EXISTS idx_stock_code ON stock_history(stock_code)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_date ON stock_history(date)')
conn.commit()
print("数据库初始化/检查完成")
except sqlite3.Error as e:
print(f"数据库错误: {e}")
finally:
if conn:
conn.close()
# Initialize database on import
init_db()
def save_to_db(stock_code, df):
conn = None
try:
conn = sqlite3.connect('stock_data.db')
# 添加stock_code列(用户输入的代码)
df = df.copy()
df['stock_code'] = stock_code
# 确保列顺序与数据库一致
db_columns = [
'stock_code', 'date', 'code', 'open', 'close', 'high', 'low',
'volume', 'amount', 'amplitude', 'change_pct', 'change_amt', 'turnover'
]
# 只保留需要的列
df = df[db_columns]
# 使用事务批量插入
with conn:
df.to_sql('stock_history', conn, if_exists='append', index=False)
#print(f"成功保存 {stock_code} 的 {len(df)} 条数据")
except Exception as e:
print(f"保存到数据库出错: {str(e)}")
finally:
if conn:
conn.close()
def get_stock_data(stock_code, days=100, online=False):
if online:
try:
# 获取原始数据
df = ak.stock_zh_a_hist(symbol=stock_code, period="daily", adjust="qfq")
if df is None or len(df) < 30:
return None
#print(df)
# 确保包含所有列(防止某些列缺失的情况)
expected_columns = ['日期', '股票代码', '开盘', '收盘', '最高', '最低',
'成交量', '成交额', '振幅', '涨跌幅', '涨跌额', '换手率']
# 检查是否有缺失的列
missing_cols = [col for col in expected_columns if col not in df.columns]
if missing_cols:
print(f"警告:缺失以下列: {missing_cols}")
df = df.reindex(columns=expected_columns) # 确保列顺序一致
# 重命名所有列(中英文对照)
column_mapping = {
'日期': 'date',
'股票代码': 'code',
'开盘': 'open',
'收盘': 'close',
'最高': 'high',
'最低': 'low',
'成交量': 'volume',
'成交额': 'amount',
'振幅': 'amplitude',
'涨跌幅': 'change_pct',
'涨跌额': 'change_amt',
'换手率': 'turnover'
}
df = df.rename(columns=column_mapping)
# 转换日期格式
# df['date'] = pd.to_datetime(df['date'])
# 按日期排序
df = df.sort_values(by='date', ascending=True)
#print(df)
# 保存到数据库(如果需要)
if online: # 只有在线获取时才保存
save_to_db(stock_code, df.tail(days))
return df.tail(days)
except Exception as e:
#print(f"获取 {stock_code} 数据出错: {str(e)}")
# 尝试从数据库加载
return None
else: # 离线模式
return load_from_db(stock_code, days)
# 数据库读取函数也需要对应修改
def load_from_db(stock_code, days=100):
try:
conn = sqlite3.connect('stock_data.db')
query = f"""
SELECT * FROM stock_history
WHERE stock_code = '{stock_code}'
ORDER BY date DESC
LIMIT {days}
"""
df = pd.read_sql(query, conn)
if not df.empty:
# 转换日期格式
df['date'] = pd.to_datetime(df['date'])
# 按日期升序排列
df = df.sort_values(by='date', ascending=True)
return df
return None
except Exception as e:
print(f"从数据库加载出错: {str(e)}")
return None
finally:
if conn:
conn.close()
# 计算均线
def calculate_ma(df, windows=[5, 10, 20]):
for w in windows:
df[f'MA{w}'] = df['close'].rolling(window=w).mean()
return df
# 计算 MACD 指标
def calculate_macd(df, short=12, long=26, signal=9):
df['EMA12'] = df['close'].ewm(span=short, adjust=False).mean()
df['EMA26'] = df['close'].ewm(span=long, adjust=False).mean()
df['DIF'] = df['EMA12'] - df['EMA26']
df['DEA'] = df['DIF'].ewm(span=signal, adjust=False).mean()
df['MACD'] = 2 * (df['DIF'] - df['DEA']) # 计算 MACD
return df
# 计算 KDJ 指标
def calculate_kdj(df, n=9, k_smooth=3, d_smooth=3):
low_min = df['low'].rolling(window=n).min()
high_max = df['high'].rolling(window=n).max()
df['RSV'] = (df['close'] - low_min) / (high_max - low_min) * 100
df['K'] = df['RSV'].ewm(alpha=1 / k_smooth, adjust=False).mean()
df['D'] = df['K'].ewm(alpha=1 / d_smooth, adjust=False).mean()
df['J'] = 3 * df['K'] - 2 * df['D']
return df
# 计算 RSI 指标
def calculate_rsi(df, period=14):
delta = df['close'].diff()
gain = np.where(delta > 0, delta, 0)
loss = np.where(delta < 0, -delta, 0)
avg_gain = pd.Series(gain).rolling(window=period).mean()
avg_loss = pd.Series(loss).rolling(window=period).mean()
rs = avg_gain / avg_loss
df['RSI'] = 100 - (100 / (1 + rs))
return df
# 获取 A 股市场所有股票
def get_shanghai_stock_list():
stock_list = ak.stock_info_sh_name_code()
# stock_list.to_csv("stock_list.csv", index=False, encoding="utf-8-sig")
# print(stock_list)
return stock_list[['证券代码', '证券简称']]
# 获取 sz市场所有股票
def get_sz_stock_list():
stock_list = ak.stock_info_sz_name_code()
# print(stock_list)
return stock_list[['A股代码', 'A股简称']]
# 筛选符合四大指标的股票
def stock_screening(stock_list, code_column, name_column, lookback_days=1):
selected_stocks = []
seeked_stocks = []
print(stock_list)
for index, row in tqdm(stock_list.iterrows(), total=len(stock_list)):
stock_code = row[code_column]
stock_name = row[name_column]
df = get_stock_data(stock_code)
# print("#####################")
# print(df)
# print("#####################")
if df is None or len(df) < 30:
continue
# 计算技术指标
df = calculate_ma(df)
df = calculate_macd(df)
df = calculate_kdj(df)
df = calculate_rsi(df)
# 遍历过去 lookback_days 天的数据
for i in range(1, lookback_days + 1):
last_row = df.iloc[-i]
prev_row = df.iloc[-(i + 1)]
# 打印最后几天的指标数据(仅当 lookback_days > 1 时)
# if lookback_days > 1:
# print(df.tail(lookback_days)[
# ['date', 'close', 'MA5', 'MA10', 'MA20', 'DIF', 'DEA', 'MACD', 'K', 'D', 'J', 'RSI']])
seeked_stocks.append((stock_code, stock_name))
# 筛选条件
if (
# 1. 均线:MA5 上穿 MA10,且股价站上 MA20
# prev_row['MA5'] < prev_row['MA10'] and last_row['MA5'] > last_row['MA10'] and last_row['close'] > last_row['MA20']
#2. MACD:DIF 上穿 DEA,且 MACD 处于 0 轴下方
#prev_row['DIF'] < prev_row['DEA'] and last_row['DIF'] > last_row['DEA'] and last_row['MACD'] < 0
# 3. KDJ:K 值和 D 值在 20 以下,形成金叉
#prev_row['K'] > 80 and last_row['D'] > 80
prev_row['K'] < prev_row['D'] and last_row['K'] > last_row['D'] and last_row['K'] < 20
# # 4. RSI:RSI 从 30 以下反弹,并突破 50
#and prev_row['RSI'] < 30 and last_row['RSI'] > 30 and last_row['RSI'] > 50
):
# if (
# # 1. 均线:MA5 上穿 MA10,且股价站上 MA20
# # prev_row['MA5'] < prev_row['MA10'] and last_row['MA5'] > last_row['MA10'] and last_row['close'] > last_row['MA20']
# # 2. MACD:DIF 上穿 DEA(不要求 MACD 低于 0)
# prev_row['DIF'] < prev_row['DEA'] and last_row['DIF'] > last_row['DEA']
# # 3. KDJ:K 值和 D 值形成金叉(不要求 K < 30)
# and prev_row['K'] < prev_row['D'] and last_row['K'] > last_row['D']
# # 4. RSI:RSI 低位反弹(不要求 RSI 必须突破 50)
# and prev_row['RSI'] < 30 and last_row['RSI'] > prev_row['RSI']
# ):
if lookback_days > 1:
print(f"{stock_code} 在 {last_row['date']} 符合条件")
else:
selected_stocks.append((stock_code, stock_name))
return selected_stocks, seeked_stocks
# 使用示例
def stock_sh_screening():
stock_list = get_shanghai_stock_list()
return stock_screening(stock_list, '证券代码', '证券简称')
def stock_sz_screening():
stock_list = get_sz_stock_list()
return stock_screening(stock_list, 'A股代码', 'A股简称')
def stock_sh_last30_screening():
stock_list = get_shanghai_stock_list()
return stock_screening(stock_list, '证券代码', '证券简称', lookback_days=30)
def stock_sz_last30_screening():
stock_list = get_sz_stock_list()
return stock_screening(stock_list, 'A股代码', 'A股简称', lookback_days=30)
# 运行筛选
if __name__ == "__main__":
#stocks_sh, seeks_sh = stock_sz_last30_screening()
stocks_sh, seeks_sh = stock_sh_screening()
stocks_sz, seeks_sz = stock_sz_screening()
print("检索过的股票:")
print(seeks_sz)
print(seeks_sh)
print(len(seeks_sh)+len(seeks_sz))
print("符合条件的股票:")
print(stocks_sz)
print(stocks_sh)
print(len(stocks_sz)+len(stocks_sh))