一、为什么需要这个策略(痛点开场)
回测系统要么是 backtrader / zipline 那种"几十页文档、上手两天"的重型框架,要么是你一上来就被各种坑(比如未来函数、滑点设错、动量算法不对)劝退。
但最小可用的向量化回测,自己写也就 100 行 pandas:
- 一个 30 只大盘股的股票池
- 过去 120 个交易日累计涨幅(动量因子)
- 每月末取 top10 等权持仓 20 天
- 算收益 / 回撤 / Sharpe
这一篇把它"白纸化"写出来。文章跑通的 50 行核心代码 + 一个完整 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 会随行情轮动。
五、常见坑与注意
六、小结 + 下篇预告
小结:本篇手写了"动量因子 + 月度再平衡 + 等权回测"的最朴素骨架,从拉数据到算五件套指标全在 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 量化策略入门示例,演示证书下回测指标不能反映真实市场表现,不构成任何投资建议;投资有风险,入市需谨慎。
网硕互联帮助中心




评论前必须登录!
注册