这是一个python代码,里面包含金融库来获取东方财富api调取历史A股数据
并且可以通过调整参数,来测自己想要的均线趋势以及板块概念和成交量,反正只有 你想不到;没有做不到的
import efinance as ef
import pandas as pd
import datetime
import os
from tqdm import tqdm
import concurrent.futures
# 恢复使用固定日期
# 修改get_stock_data和get_index_data函数,使用传入的target_date参数
# 修改get_stock_data函数,添加获取年初至今数据的参数
# 修改get_stock_data函数,确保获取上一年的数据
def get_stock_data(code, target_date, days=10, for_sixty_days=False, for_ytd=False):
"""获取单只股票的历史数据"""
# 使用传入的目标日期
end_date = datetime.datetime.strptime(target_date, '%Y%m%d')
# 如果需要计算年初至今涨跌幅,获取从上一年12月开始的数据
if for_ytd:
# 获取上一年12月1日的日期,确保能获取到上一年最后一个交易日
start_date = datetime.datetime(end_date.year - 1, 12, 1).strftime('%Y%m%d')
# 如果需要计算60个交易日的涨跌幅,获取更长时间的数据
elif for_sixty_days:
# 获取约6个月的数据,确保能覆盖60个交易日(考虑节假日和周末)
start_date = (end_date - datetime.timedelta(days=180)).strftime('%Y%m%d')
else:
start_date = (end_date - datetime.timedelta(days=days)).strftime('%Y%m%d')
end_date = end_date.strftime('%Y%m%d')
try:
df = ef.stock.get_quote_history(code, beg=start_date, end=end_date)
if df.empty:
return None
# 重命名列
df = df.rename(columns={
'日期': 'Date', '开盘': 'Open', '收盘': 'Close',
'最高': 'High', '最低': 'Low', '成交量': 'Volume'
})
df['Date'] = pd.to_datetime(df['Date'])
df = df.set_index('Date')
return df
except Exception as e:
print(f"获取股票 {code} 数据出错: {e}")
return None
# 修改get_index_data函数,使用正确的指数代码格式
def get_index_data(target_date, days=10):
"""获取创业板指数的历史数据"""
# 使用传入的目标日期
end_date = datetime.datetime.strptime(target_date, '%Y%m%d')
start_date = (end_date - datetime.timedelta(days=days)).strftime('%Y%m%d')
end_date = end_date.strftime('%Y%m%d')
try:
# 直接使用创业板指数代码 399006,不添加前缀
df = ef.stock.get_quote_history('399006', beg=start_date, end=end_date)
if df.empty:
print("无法获取创业板指数数据")
return None
# 重命名列
df = df.rename(columns={'日期': 'Date', '收盘': 'Close'})
df['Date'] = pd.to_datetime(df['Date'])
df = df.set_index('Date')
# 简化输出
print(f"获取到创业板指数数据,最新收盘价: {df['Close'].iloc[-1]:.2f}")
return df
except Exception as e:
print(f"获取创业板指数数据出错: {e}")
return None
# 修改get_stock_concept_info函数,移除调试输出
def get_stock_concept_info(code):
"""获取股票所属板块和概念"""
try:
# 使用东方财富网的概念分类数据
# 首先尝试从基本信息获取
stock_info = ef.stock.get_base_info(code)
# 初始化概念和行业信息
concepts = ""
industry = ""
# 从基本信息中获取行业
if '所处行业' in stock_info:
industry = stock_info['所处行业']
# 尝试获取股票所属概念板块
try:
# 获取股票所属概念板块
concept_list = ef.stock.get_belong_board(code)
if concept_list is not None and not concept_list.empty:
# 提取概念名称并合并为字符串
concepts = ",".join(concept_list['板块名称'].tolist())
except Exception as e:
pass
# 如果仍然没有获取到概念信息,尝试获取行业板块
if not concepts:
try:
# 获取股票所属行业板块
industry_list = ef.stock.get_belong_industry(code)
if industry_list is not None and not industry_list.empty:
# 提取行业名称并合并为字符串
industry_names = ",".join(industry_list['行业名称'].tolist())
if industry:
industry = f"{industry},{industry_names}"
else:
industry = industry_names
except Exception as e:
pass
# 简化默认值处理
if not concepts:
concepts = industry if industry else f"市场:{code[0]}开头"
if not industry:
industry = f"市场:{code[0]}开头"
return concepts, industry
except Exception as e:
# 简化异常处理
market_type = ""
if code.startswith('8'):
market_type = "新三板"
elif code.startswith('3'):
market_type = "创业板"
elif code.startswith('6'):
market_type = "上证主板"
elif code.startswith('0'):
market_type = "深证主板"
else:
market_type = f"市场:{code[0]}开头"
return market_type, market_type
# 修改check_stock函数,移除调试输出
def check_stock(code, index_data, target_date, ma_period=5, sh_ma_period=3, vol_period=2, vol_mult=1.01, target_concepts=None):
"""检查单只股票是否满足条件"""
# 获取股票数据,传入目标日期,并指定需要获取足够的历史数据用于计算60个交易日涨跌幅和年初至今涨跌幅
stock_data = get_stock_data(code, target_date, days=max(ma_period, vol_period, sh_ma_period) + 5, for_sixty_days=True)
# 获取年初至今的数据
ytd_data = get_stock_data(code, target_date, for_ytd=True)
if stock_data is None or len(stock_data) < max(ma_period, vol_period):
return None
# 计算个股均线
stock_data['MA'] = stock_data['Close'].rolling(window=ma_period).mean()
# 计算成交量均线
stock_data['Vol_MA'] = stock_data['Volume'].rolling(window=vol_period).mean()
# 获取最新一天的数据
latest_data = stock_data.iloc[-1]
# 检查条件
# 1. 个股趋势向上:价格高于均线
stock_above_ma = latest_data['Close'] > latest_data['MA']
# 2. 成交量放大:当前成交量超过成交量均线的倍数
volume_mult = latest_data['Volume'] / latest_data['Vol_MA']
volume_condition = volume_mult > vol_mult
# 3. 价格突破:当前价格相对于均线有一定的幅度的上涨(至少0.2%)
price_change = (latest_data['Close'] - latest_data['MA']) / latest_data['MA']
price_breakthrough = price_change > 0.002 # 0.2%
# 获取股票名称和概念信息
try:
stock_info = ef.stock.get_base_info(code)
stock_name = stock_info.get('股票名称', '未知')
if not stock_name or stock_name == '未知':
# 尝试从实时行情中获取名称
realtime_info = ef.stock.get_realtime_quotes(code)
if not realtime_info.empty:
stock_name = realtime_info.iloc[0].get('股票名称', '未知')
# 获取概念和行业信息
concepts, industry = get_stock_concept_info(code)
# 确保概念和行业信息不为空
if not concepts:
concepts = f"市场:{code[0]}开头"
if not industry:
industry = f"市场:{code[0]}开头"
# 移除调试输出
except Exception as e:
stock_name = '未知'
# 根据股票代码前缀设置默认概念
if code.startswith('8'):
concepts, industry = "新三板", "新三板"
elif code.startswith('3'):
concepts, industry = "创业板", "创业板"
elif code.startswith('6'):
concepts, industry = "上证主板", "上证主板"
elif code.startswith('0'):
concepts, industry = "深证主板", "深证主板"
else:
concepts, industry = f"市场:{code[0]}开头", f"市场:{code[0]}开头"
# 检查是否属于目标概念或板块
concept_match = True
matched_concepts = [] # 记录匹配到的概念,用于展示
if target_concepts and len(target_concepts) > 0:
# 如果设置了目标概念,默认为不匹配
concept_match = False
# 确保概念和行业信息是字符串类型
concepts_lower = str(concepts).lower() if concepts is not None else ""
industry_lower = str(industry).lower() if industry is not None else ""
all_info = (concepts_lower + " " + industry_lower)
for concept in target_concepts:
concept_lower = concept.lower()
# 更宽松的匹配:只要包含关键词的一部分即可
if concept_lower in all_info:
concept_match = True
matched_concepts.append(concept)
# 如果满足所有条件,返回股票信息
if stock_above_ma and volume_condition and price_breakthrough and concept_match:
# 从历史数据中计算指标,而不是获取实时数据
try:
# 获取最新交易日和前一交易日数据
if len(stock_data) >= 2:
latest_day = stock_data.iloc[-1]
prev_day = stock_data.iloc[-2]
# 计算涨跌幅和涨跌额
change_percent = (latest_day['Close'] - prev_day['Close']) / prev_day['Close'] * 100
change_amount = latest_day['Close'] - prev_day['Close']
# 计算振幅
amplitude = (latest_day['High'] - latest_day['Low']) / prev_day['Close'] * 100
# 其他可以从历史数据中获取的值
high_price = latest_day['High']
low_price = latest_day['Low']
open_price = latest_day['Open']
pre_close = prev_day['Close']
# 计算成交额 (假设Volume是手数,需要乘以100和均价)
avg_price = (latest_day['High'] + latest_day['Low'] + latest_day['Close']) / 3
turnover_value = latest_day['Volume'] * 100 * avg_price
# 计算60日涨跌幅
sixty_day_change = 0
if len(stock_data) >= 61: # 至少需要61个交易日的数据
# 获取最新日期的收盘价
latest_close = latest_day['Close']
# 检查数据排序方式
# 确保数据是按日期降序排列的(最新日期在前)
is_sorted_desc = stock_data.index[0] > stock_data.index[-1]
# 根据排序方式获取60个交易日前的收盘价
if is_sorted_desc:
# 如果是降序排列,第60个交易日在索引60的位置
sixty_trading_days_ago_close = stock_data.iloc[60]['Close']
sixty_days_ago_date = stock_data.index[60].strftime('%Y-%m-%d')
else:
# 如果是升序排列,第60个交易日在倒数第61个位置
sixty_trading_days_ago_close = stock_data.iloc[-61]['Close']
sixty_days_ago_date = stock_data.index[-61].strftime('%Y-%m-%d')
# 计算涨跌幅
sixty_day_change = (latest_close - sixty_trading_days_ago_close) / sixty_trading_days_ago_close * 100
# 添加调试信息(可以取消注释查看)
latest_date = stock_data.index[-1 if is_sorted_desc else 0].strftime('%Y-%m-%d')
# print(f"股票 {code}: 排序方式={'降序' if is_sorted_desc else '升序'}")
# print(f"最新日期 {latest_date}(收盘价:{latest_close:.2f}), 60个交易日前 {sixty_days_ago_date}(收盘价:{sixty_trading_days_ago_close:.2f})")
# print(f"60日涨跌幅: {sixty_day_change:.2f}%, 数据长度: {len(stock_data)}")
else:
# 如果没有足够的历史数据,使用默认值
change_percent = 0
change_amount = 0
amplitude = 0
high_price = latest_data.get('High', 0)
low_price = latest_data.get('Low', 0)
open_price = latest_data.get('Open', 0)
pre_close = 0
turnover_value = 0
sixty_day_change = 0
# 计算年初至今涨跌幅
ytd_change = 0
if ytd_data is not None and not ytd_data.empty:
# 获取最新日期的收盘价
latest_close = latest_day['Close']
try:
# 获取当前年份
current_year = datetime.datetime.strptime(target_date, '%Y%m%d').year
# 找到上一年的最后一个交易日
prev_year_data = ytd_data[ytd_data.index.year == current_year - 1]
if not prev_year_data.empty:
# 获取上一年最后一个交易日的收盘价
# 按日期排序,取最后一个
prev_year_data = prev_year_data.sort_index()
prev_year_last_day_close = prev_year_data.iloc[-1]['Close']
# 计算涨跌幅
ytd_change = (latest_close - prev_year_last_day_close) / prev_year_last_day_close * 100
# 调试信息
# last_day_date = prev_year_data.index[-1].strftime('%Y-%m-%d')
# print(f"股票 {code}: 上一年最后交易日 {last_day_date}, 收盘价 {prev_year_last_day_close:.2f}")
# print(f"最新收盘价 {latest_close:.2f}, 年初至今涨跌幅 {ytd_change:.2f}%")
except Exception as e:
# 如果计算失败,尝试另一种方法
try:
# 尝试获取当年第一个交易日
current_year_data = ytd_data[ytd_data.index.year == current_year]
if not current_year_data.empty:
# 按日期排序
current_year_data = current_year_data.sort_index()
# 获取当年第一个交易日
first_trading_day = current_year_data.iloc[0]
# 使用第一个交易日的开盘价作为基准
first_day_open = first_trading_day['Open']
# 计算涨跌幅
ytd_change = (latest_close - first_day_open) / first_day_open * 100
except:
ytd_change = 0
else:
# 如果没有足够的历史数据,使用默认值
change_percent = 0
change_amount = 0
amplitude = 0
high_price = latest_data.get('High', 0)
low_price = latest_data.get('Low', 0)
open_price = latest_data.get('Open', 0)
pre_close = 0
turnover_value = 0
sixty_day_change = 0
ytd_change = 0 # 添加默认值
except Exception as e:
# 如果计算失败,使用默认值
change_percent = 0
change_amount = 0
amplitude = 0
high_price = latest_data.get('High', 0)
low_price = latest_data.get('Low', 0)
open_price = latest_data.get('Open', 0)
pre_close = 0
turnover_value = 0
sixty_day_change = 0
ytd_change = 0 # 添加默认值
return {
'股票代码': code,
'股票名称': stock_name,
'收盘价': latest_data['Close'],
'均线值': latest_data['MA'],
'价格突破比例': price_change * 100, # 转为百分比
'成交量': latest_data['Volume'],
'成交量均线': latest_data['Vol_MA'],
'成交量倍数': volume_mult,
'所属概念': concepts,
'所属行业': industry,
'匹配概念': ','.join(matched_concepts) if matched_concepts else '', # 这一列显示股票匹配到的目标概念
# 从历史数据计算的字段
'涨跌幅': change_percent,
'涨跌额': change_amount,
'成交额': turnover_value,
'振幅': amplitude,
'最高': high_price,
'最低': low_price,
'今开': open_price,
'昨收': pre_close,
'60日涨跌幅': sixty_day_change, # 这里缺少了逗号
'年初至今涨跌幅': ytd_change # 新增年初至今涨跌幅
}
return None
# 修改screen_stocks函数,减少输出内容
def screen_stocks(target_date='20250314', ma_period=5, sh_ma_period=3, vol_period=2, vol_mult=1.01, target_concepts=None):
"""筛选满足条件的股票"""
print(f"开始筛选 {target_date} 满足条件的股票...")
# 打印筛选条件
print(f"筛选条件:")
print(f"- 大盘趋势:创业板指数(399006)价格高于{sh_ma_period}日均线")
print(f"- 个股趋势:价格高于{ma_period}日均线")
print(f"- 成交量条件:当前成交量超过{vol_period}日均线的{vol_mult}倍")
print(f"- 价格突破:当前价格相对于均线上涨至少0.2%")
if target_concepts and len(target_concepts) > 0:
print(f"- 目标板块/概念: {', '.join(target_concepts)}")
print(f" (匹配概念列将显示每只股票实际匹配到的目标概念)")
# 获取中证1000指数数据,传入目标日期
index_data = get_index_data(target_date, days=sh_ma_period + 5)
if index_data is None:
print("获取指数数据失败,无法继续筛选")
return []
# 计算中证1000指数均线
index_data['MA'] = index_data['Close'].rolling(window=sh_ma_period).mean()
# 检查大盘趋势
latest_index = index_data.iloc[-1]
index_above_ma = latest_index['Close'] > latest_index['MA']
if not index_above_ma:
print(f"指数未满足条件(当前值: {latest_index['Close']:.2f}, 均线值: {latest_index['MA']:.2f})")
print("大盘趋势不向上,不建议买入")
return []
print(f"指数满足条件(当前值: {latest_index['Close']:.2f}, 均线值: {latest_index['MA']:.2f})")
# 获取所有A股股票列表
all_stocks = ef.stock.get_realtime_quotes()
# 过滤掉科创板、北交所和ST股票
filtered_stocks = all_stocks[~all_stocks['股票代码'].str.startswith('68')] # 过滤科创板
# 过滤北交所股票(4开头、83开头和92开头)
filtered_stocks = filtered_stocks[
(~filtered_stocks['股票代码'].str.startswith('4')) &
(~filtered_stocks['股票代码'].str.startswith('83')) &
(~filtered_stocks['股票代码'].str.startswith('92'))
]
# 过滤ST股票
filtered_stocks = filtered_stocks[~filtered_stocks['股票名称'].str.contains('ST')]
stock_codes = filtered_stocks['股票代码'].tolist()
print(f"共获取到 {len(all_stocks)} 只股票,过滤后剩余 {len(stock_codes)} 只股票,开始筛选...")
# 使用多线程加速筛选过程,禁用调试输出
qualified_stocks = []
# 大幅增加工作线程数量,提高并行效率
max_workers = min(100, os.cpu_count() * 8 if os.cpu_count() else 50) # 显著提高线程数上限
# 删除分批处理,一次性处理所有股票
print(f"使用 {max_workers} 个线程处理 {len(stock_codes)} 只股票...")
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {executor.submit(check_stock, code, index_data, target_date, ma_period, sh_ma_period, vol_period, vol_mult, target_concepts): code for code in stock_codes}
# 修改tqdm配置,使其只显示一个进度条
for future in tqdm(concurrent.futures.as_completed(futures), total=len(futures), desc="筛选进度", ncols=100, leave=True, position=0):
result = future.result()
if result:
qualified_stocks.append(result)
# 转换为DataFrame并排序
if qualified_stocks:
df = pd.DataFrame(qualified_stocks)
# 按价格突破比例排序
df = df.sort_values(by='价格突破比例', ascending=False)
# 保存结果到Excel
output_dir = '/Users/wuchuan/Desktop/py'
os.makedirs(output_dir, exist_ok=True)
# 使用固定文件名,每次覆盖原有文件
excel_path = f"{output_dir}/选股策略.xlsx"
# 选择要保存的列,删除无法获取的字段
columns_to_save = [
'股票代码', '股票名称', '收盘价', '均线值', '价格突破比例',
'成交量倍数', '所属概念', '所属行业', '匹配概念',
# 保留能够计算的字段
'涨跌幅', '涨跌额', '成交量', '成交额', '振幅',
'最高', '最低', '今开', '昨收',
'60日涨跌幅', '年初至今涨跌幅' # 添加年初至今涨跌幅
]
# 添加匹配概念列的说明
print("\n'匹配概念'列说明: 显示该股票匹配到的目标概念,即您指定的那些概念中,该股票实际具有的概念。")
df[columns_to_save].to_excel(excel_path, index=False)
print(f"筛选结果已保存到: {excel_path}")
# 简化输出,只显示找到的股票数量
print(f"\n共找到 {len(df)} 只满足条件的股票")
else:
print("没有找到满足条件的股票")
return qualified_stocks
# 修改主函数部分,删除测试模式相关代码
if __name__ == '__main__':
# 设置参数
# 使用当前日期或最近的交易日,避免使用未来日期
target_date = '20250319' # 使用目标日期
ma_period = 3 # 个股均线周期
sh_ma_period = 3 # 创业板指数均线周期
vol_period = 2 # 成交量均线周期
vol_mult = 1.01 # 成交量倍数阈值
# 预定义概念映射,用于更准确地匹配概念
concept_mapping = {
'机器人': ['机器人', '工业机器人', '服务机器人', '特种机器人', '智能制造'],
'算力': ['算力', 'AI算力', '云计算', '大数据', '人工智能', 'GPU', 'AIGC', '芯片', '半导体'],
'消费': ['消费', '消费电子', '白酒食品饮料', '零售', '餐饮', '食品'],
'房地产': ['房地产', '地产'],
'证券': ['证券', '券商', '金融', '保险', '银行', '信托', '多元金融'],
'旅游': ['旅游', '酒店', '免税', '景区']
}
# 设置目标板块或概念,可以自行修改
# 如果不想筛选特定板块或概念,可以设置为None
target_concepts = ['机器人', '算力', '证券', '消费', '房地产'] # 添加消费和房地产
# 扩展目标概念,使用映射表,并去除重复项
expanded_concepts = []
if target_concepts:
for concept in target_concepts:
expanded_concepts.append(concept)
if concept in concept_mapping:
expanded_concepts.extend(concept_mapping[concept])
# 去除重复项
expanded_concepts = list(set(expanded_concepts))
# 直接运行完整筛选
qualified_stocks = screen_stocks(target_date, ma_period, sh_ma_period, vol_period, vol_mult, expanded_concepts)
这个代码可以直接可以粘贴使用,只需要修改保存路径即可,以及修改想要的参数