A股用模型参数来选股-第一步

这是一个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)

这个代码可以直接可以粘贴使用,只需要修改保存路径即可,以及修改想要的参数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuchuanj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值