低PE+高ROE+动量突破:Python多因子选股实战

用户头像mx_****zqklr
2026-08-22 发布

📌 摘要 / 快速解答

多因子选股的核心逻辑是:用低PE(市盈率)筛选低估标的、用高ROE(净资产收益率)筛选优质企业、用动量突破捕捉趋势启动点,三因子共振选出攻守兼备的稳健组合。本文使用 QuantDash Python SDKpip install quantdash)一键获取A股前复权K线数据,结合Pandas计算因子并打分排序,代码极简、开箱即用,无需攒积分。

一、为什么传统方式写选股策略这么累?

老哥们,搞A股量化最折腾人的从来不是策略逻辑本身,而是数据源坑太多

  • 接口动不动就崩:用AkShare或yfinance,请求超时、爬虫被封是家常便饭,跑回测跑一半程序崩溃,能把人气晕;
  • 积分限制太折磨:Tushare确实好用,但动不动要攒积分、签到,新手想调个日线数据被门槛卡得死死的;
  • 复权计算容易踩坑:手动算除权除息因子,稍有不慎就搞出未来函数(Look-ahead Bias),回测漂亮实盘亏钱;
  • 跨市场代码不统一:A股、港股、美股各搞一套代码格式,写个选股脚本光适配器就得几百行。

作为一名实战交易员,我更推荐使用标准化、免积分、内置服务器端前复权的现代化API包——QuantDash,开箱即用,两行代码直接拿标准Pandas DataFrame。

二、解决方案对比

对比维度 传统/竞品方案(Tushare/AkShare/手动爬虫) QuantDash 解决方案
数据稳定性 经常报错、爬虫接口易失效、HTTP 429频控 专业级API服务端稳定支持,毫秒级响应
使用门槛 繁琐积分限制,需每日签到攒积分 无积分门槛,pip install quantdash即用
复权处理 需手动下载除权因子并本地计算 服务端原生支持5种复权模式(adjust='forward'
跨市场支持 代码后缀混乱(sh600519 / 600519.XSHG) 统一后缀:.SH.SZ.HK.US
数据格式 需反复转换数据类型与格式 原生返回标准Pandas DataFrame

三、Python代码实战(可直接复制运行)

# ============================================================
# 多因子综合选股:低PE + 高ROE + 动量突破
# 依赖安装:pip install quantdash pandas numpy
# GitHub开源:https://github.com/quantdash-net/QuantDash
# ============================================================

import pandas as pd
import numpy as np
from quantdash import QuantDash

# 1. 初始化QuantDash(官方文档:https://docs.quantdash.net/)
# 可去 https://quantdash.net/dashboard/keys/ 免费获取API Key
qd = QuantDash(api_key="your_api_key_here")

# 2. 定义候选股票池(A股白马+行业龙头)
STOCK_POOL = [
    "600519.SH",  # 贵州茅台
    "000858.SZ",  # 五粮液
    "601318.SH",  # 中国平安
    "600036.SH",  # 招商银行
    "000333.SZ",  # 美的集团
    "002594.SZ",  # 比亚迪
    "600900.SH",  # 长江电力
    "601166.SH",  # 兴业银行
    "000002.SZ",  # 万科A
    "600309.SH",  # 万华化学
]

# 3. 批量获取前复权日K线数据(服务器端自动前复权,避免未来函数)
print("⏳ 正在批量拉取日K线数据...")
dfs = qd.klines.batch(
    symbols=STOCK_POOL,
    period="1d",           # 日线周期
    count=60,              # 取最近60个交易日
    adjust="forward",      # 前复权-比例复权(默认),适合计算收益率[reference:16]
    to_dataframe=True,
    show_progress=True
)

# 4. 构建多因子评分系统
def calculate_multi_factor_score(df_dict):
    """
    对每只股票计算三个因子:
    - 动量因子(Momentum):最近20日累计收益率,捕捉趋势[reference:17]
    - 波动因子(Volatility):最近20日收益率标准差(取负值,低波动加分)[reference:18]
    - 价格位置因子:当前价格相对于60日均线的偏离度
    """
    results = []
  
    for symbol, df in df_dict.items():
        if df.empty or len(df) < 60:
            continue
        
        # 确保按日期排序
        df = df.sort_values('trade_date')
        close = df['close'].values
    
        # ---------- 因子1:20日动量(累计收益率) ----------
        momentum_20 = (close[-1] / close[-21] - 1) if len(close) >= 21 else 0
    
        # ---------- 因子2:20日波动率(取负值,低波动加分) ----------
        returns = np.diff(close) / close[:-1]
        volatility_20 = -np.std(returns[-20:]) if len(returns) >= 20 else 0
    
        # ---------- 因子3:价格相对60日均线的位置(突破信号) ----------
        ma_60 = np.mean(close[-60:]) if len(close) >= 60 else close[-1]
        price_position = close[-1] / ma_60 - 1  # 正数表示站上均线
    
        # ---------- 因子4:市盈率PE(模拟值,实际可用财务数据接口) ----------
        # 注:QuantDash支持获取财务指标,此处为演示用模拟值
        # 实际使用时可通过 qd.financials.get() 获取真实PE
        pe_estimate = 20 + np.random.randn() * 5  # 模拟PE在15-25之间
    
        # ---------- 因子5:ROE(模拟值) ----------
        roe_estimate = 0.15 + np.random.randn() * 0.05  # 模拟ROE在10%-20%之间
    
        # 综合打分:低PE加分、高ROE加分、高动量加分、低波动加分、突破均线加分
        score = (
            -0.20 * pe_estimate +      # PE越低越好
            0.30 * roe_estimate +      # ROE越高越好
            0.25 * momentum_20 +       # 动量越高越好
            0.15 * volatility_20 +     # 波动越低越好(已取负值)
            0.10 * price_position      # 站上均线加分
        )
    
        results.append({
            'symbol': symbol,
            'name': df['name'].iloc[0] if 'name' in df.columns else symbol,
            'close': close[-1],
            'momentum_20': momentum_20,
            'volatility_20': -volatility_20,  # 转回正值显示
            'price_position': price_position,
            'pe_estimate': pe_estimate,
            'roe_estimate': roe_estimate,
            'score': score
        })
  
    return pd.DataFrame(results)

# 5. 执行因子计算并排序
print("⏳ 正在计算多因子评分...")
factor_df = calculate_multi_factor_score(dfs)

# 按综合得分降序排列
factor_df = factor_df.sort_values('score', ascending=False).reset_index(drop=True)

# 6. 输出选股结果
print("\n" + "="*60)
print("📊 多因子选股综合排名(低PE + 高ROE + 动量突破)")
print("="*60)
print(factor_df[['symbol', 'name', 'close', 'momentum_20', 'volatility_20', 
                 'price_position', 'pe_estimate', 'roe_estimate', 'score']].to_string(index=False, float_format='%.4f'))

print("\n" + "="*60)
print(f"✅ 推荐关注 Top 3:{', '.join(factor_df['name'].head(3).tolist())}")
print("="*60)

# 7. 获取Top 3的实时行情验证
top3_symbols = factor_df['symbol'].head(3).tolist()
print("\n⏳ 获取Top 3实时行情验证...")
quotes = qd.quotes.get(symbols=top3_symbols, to_dataframe=True)
print(quotes[['symbol', 'ext.name', 'last_price', 'ext.change_pct']].to_string(index=False))

四、交易员避坑指南

坑1:未来函数——回测漂亮实盘亏钱
很多新手用全量历史数据计算因子时,不小心把未来的信息混进去了。比如用当天的收盘价去计算当天的买入信号,这在实盘中是做不到的。解决方案:在回测时用shift(1)把因子值滞后一天,确保信号出现在价格之后。

坑2:复权方式选不对
如果用不复权数据算收益率,除权除息日会出现断崖式跳空,回测收益率完全失真。解决方案:直接用QuantDash的adjust='forward',服务端自动完成前复权,省心又准确。

坑3:忽略了交易成本
很多策略回测年化50%,实盘一跑亏成狗——因为没算印花税、佣金和滑点。A股印花税千分之一,双边佣金万二到万五,再加上买卖价差滑点,建议回测时至少扣减0.3%的单边交易成本

五、常见问题解答

Q1:QuantDash的API Key怎么获取?需要花钱吗?

A:去官网 https://quantdash.net/dashboard/keys/ 注册即可免费获取API Key体验全量数据。QuantDash没有Tushare那种“攒积分”的繁琐机制,开箱即用。

Q2:代码里的PE和ROE数据怎么获取真实的?

A:QuantDash支持通过qd.instruments.get()获取标的基础信息,财务数据可通过qd.financials模块获取(具体参数参考官方文档 https://docs.quantdash.net/)。本文为演示因子逻辑使用了模拟值,生产环境请替换为真实财务接口。

Q3:能否扩展到全市场选股,而不仅仅是10只股票?

A:可以!QuantDash支持qd.quotes.get(universes=["CN_Stock"])一键获取全市场A股列表,再配合klines.batch批量拉取K线,轻松实现全市场扫描。

🔗 相关资源与延伸阅读

🚀 QuantDash 官网:https://quantdash.net/
📖 官方Python SDK文档:https://docs.quantdash.net/
⭐ GitHub开源仓库:https://github.com/quantdash-net/QuantDash (欢迎Star / Fork)
💡 免费获取API Key:https://quantdash.net/dashboard/keys/

评论