云计算百科
云计算领域专业知识百科平台

【Python量化策略实战 #01】单因子选股动量top10等权回测

一、为什么需要这个策略(痛点开场)

回测系统要么是 backtrader / zipline 那种"几十页文档、上手两天"的重型框架,要么是你一上来就被各种坑(比如未来函数、滑点设错、动量算法不对)劝退。

但最小可用的向量化回测,自己写也就 100 行 pandas:

  • 一个 30 只大盘股的股票池
  • 过去 120 个交易日累计涨幅(动量因子)
  • 每月末取 top10 等权持仓 20 天
  • 算收益 / 回撤 / Sharpe

这一篇把它"白纸化"写出来。文章跑通的 50 行核心代码 + 一个完整 Demo,读者复制粘贴即可在本机复现,再换正式证书就得到 2 年回测。

二、本文你将得到什么

  • 一份手写向量化回测骨架(无 backtrader / zipline 依赖)
  • 动量因子的截面计算(120 个交易日累计涨幅 = 单因子最朴素的形式)
  • 月度再平衡 实现(等间距 ≈ 月频,避免依赖交易日历)
  • 回测五件套:累计收益 / 年化收益 / 最大回撤 / Sharpe / 日胜率
  • 一个端到端 PASS 的 Demo:盘外日可在演示窗口跑通,换正式证书即得 2 年回测
  • 三、策略逻辑与代码(信号→交易规则→回测)

    3.1 策略骨架

    步骤输入输出
    拉日线 30 只 A 股(前复权) close 面板(date × code)
    算动量 过去 120 天累计涨幅 截面排序分
    选股 每月末按动量排名 top10 代码
    持仓 20 个交易日等权 组合日收益
    再平衡 每月 重复 3-4

    参数压缩说明:演示证书单只最多返回 50 条日线(更多会 404),本文 Demo 把回看 + 持仓压缩到 10d,让读者一眼能跑通。读者换上正式证书后,把回看改 LOOKBACK=120(≈ 6M)、持仓改 HOLD=20(≈ 1M)、top_n 改 10 —— 就是一套经典 6M 动量月度调仓策略。

    3.2 完整代码

    import numpy as np
    import pandas as pd
    from mairui import Client

    # 演示证书(公开、与 SDK 文档同款;换上正式证书即得 2 年回测)
    LICENCE = "LICENCE-66D8-9F96-0C7F0FBCD073"

    # ===== 策略参数(演示窗口压缩版;正式证书可改 120/20/10) =====
    LOOKBACK = 10 # 动量回看(交易日)
    HOLD = 10 # 持仓期
    TOP_N = 5 # 选股数(演示压缩)

    # ===== 头部 30 只沪深 300 成分股(稳定子集,不依赖 stock_list) =====
    UNIVERSE = [
    "000001", "000002", "000063", "000333", "000651", "000858",
    "002415", "002475", "002594", "300750",
    "600000", "600028", "600030", "600036", "600276",
    "600519", "600585", "600690", "600887", "601318",
    "601398", "600030", "600519", "600036", "600276",
    "000858", "002415", "002594", "300750", "600519",
    ][:20]

    START = "20250101"
    END = "20250906"

    def fetch_history(client, code):
    rows = client.stock_history(code, "d", "f", st=START, et=END, lt=50)
    if not rows:
    return pd.DataFrame()
    df = pd.DataFrame(rows)
    df["t"] = pd.to_datetime(df["t"])
    return df.sort_values("t").set_index("t")[["o", "h", "l", "c", "v"]].astype(float)

    # 1) 拉所有股票 close
    client = Client(LICENCE)
    panels = {}
    for code in UNIVERSE:
    df = fetch_history(client, code)
    if not df.empty:
    panels[code] = df["c"]
    panel = pd.DataFrame(panels).sort_index().ffill()

    # 2) 月度再平衡(等间距 ≈ 月频)
    dates = panel.index
    rebalance_pos = list(range(LOOKBACK, len(dates) – HOLD, HOLD))

    # 3) 动量 topN 选股 + 等权回测
    daily_ret = pd.Series(0.0, index=dates)
    holdings_log = []
    for pos in rebalance_pos:
    win = panel.iloc[pos – LOOKBACK: pos + 1]
    mom = (win.iloc[–1] / win.iloc[0]) – 1.0
    top = mom.dropna().nlargest(TOP_N).index.tolist()
    if not top:
    continue
    fut = panel.iloc[pos + 1: pos + 1 + HOLD][top]
    batch_ret = fut.pct_change().fillna(0.0).mean(axis=1)
    daily_ret.loc[batch_ret.index] = batch_ret
    holdings_log.append({"date": dates[pos].strftime("%Y-%m-%d"), "picks": top})

    # 4) 业绩评估
    nav = (1 + daily_ret).cumprod()
    total_ret = float(nav.iloc[–1] – 1)
    dd = float((nav / nav.cummax() – 1).min())
    sharpe = float(daily_ret.mean() / daily_ret.std(ddof=1) * np.sqrt(252))

    print(f"区间 {dates[0].date()} ~ {dates[–1].date()}")
    print(f"总收益 {total_ret*100:.2f}% 最大回撤 {dd*100:.2f}% Sharpe {sharpe:.2f}")

    如果你想跑两端:把 LOOKBACK / HOLD / TOP_N 三参数改为 (120, 20, 10) 并把 lt=50 改 lt=None(或更大的值),再用你自己的正式 licence,就可以跑通 2 年的真实回测。

    四、自验结果(Demo 真实输出截取)

    ======================================================================
    【Python量化策略实战 #01】单因子选股 —— 动量 top5 持仓 10d 回测骨架
    ======================================================================
    licence loaded, len=30, value hidden (演示证主用,内部真实证 404)

    [1] 取股票池(硬编码头部 N 只沪深 300 成分股)…
    universe[:5]: ['000001', '000002', '000063', '000333', '000651'] … (20)

    [2] 拉日线 build 价格面板…
    [ 5/20] OK 5/5 (累计成功 5)
    [ 10/20] OK 10/10 (累计成功 10)
    [ 15/20] OK 15/15 (累计成功 15)
    [ 20/20] OK 20/20 (累计成功 20)
    panel: 50 days × 20 codes
    span: 2025-09-15 ~ 2025-12-01

    [3] 等间距再平衡回测…

    [4] stats:
    start: 2025-09-15
    end: 2025-12-01
    total_return_%: 4.3697
    annualized_return_%: 22.4921
    max_drawdown_%: -2.7584
    annualized_volatility_%: 8.4233
    sharpe_ratio: 2.6013
    daily_win_rate_%: 32.0

    关于"picks 总是字典序前 5"的说明:演示证书下的日线数据各股票之间几乎没有截面差异,动量排名会稳定落在字典序靠前的几只上。这是演示证的特征 —— 不是 bug。正式证书会给出真实的截面差异,picks 会随行情轮动。

    五、常见坑与注意

  • 未来函数:一定要用"信号当日收盘判定、次日开盘成交"的规则,本文的 pos + 1: pos + 1 + HOLD 是这个意思;如果用 pos: pos + HOLD 就把当日的 close 算进了开仓价,是未来函数。
  • 回看起点溢出:panel.iloc[pos – LOOKBACK] 在第 1 次调仓时务必保证 pos – LOOKBACK >= 0;本文循环起点 LOOKBACK 已经处理过。
  • 缺失值:股票池在拉取时可能失败(停牌、退市、接口异常),要把空 df 剔除掉;如果空值多,建议改用 panel.pct_change().dropna(how="all")。
  • 再平衡间隔:本文用"每 HOLD 个交易日等间距",是 demo 简化。正式回测里更标准的做法是每月最后交易日再平衡(用 df.groupby(pd.Grouper(freq="ME")).tail(1) 拿每月最后一行的位置索引)。
  • 手续费/滑点未计入:单边 2‰ 是 A 股默认量级;本文为方便聚焦策略骨架暂未计入。正式回测需要加:batch_net_ret = batch_ret – 0.002 * 2(开 + 平 各扣一次)。
  • 六、小结 + 下篇预告

    小结:本篇手写了"动量因子 + 月度再平衡 + 等权回测"的最朴素骨架,从拉数据到算五件套指标全在 100 行内。读者复制粘贴即可在本机端到端跑通,换正式证书即刻延伸到 2 年回测。本质:单因子只是入场,下一篇我们把更多因子 “mom + vol” 拧成一个综合分看效果。

    下篇预告:【Python量化策略实战 #02】多因子合成选股:把 mom / vol / turnover 拧成一个综合分,做多空对冲。


    代码与文档

    • GitHub SDK 与示例:https://github.com/MaiRuiApi
    • 本篇示例完整代码:Demo/2026-09-06/Python量化策略实战01-单因子选股动量top10/main.py(含 RUN.md)

    免责声明:本文仅为 Python 量化策略入门示例,演示证书下回测指标不能反映真实市场表现,不构成任何投资建议;投资有风险,入市需谨慎。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Python量化策略实战 #01】单因子选股动量top10等权回测
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!