小果动量策略回测系统专家
一、系统概述
教程 https://gitcode.com/qq_50882340/xg_mom_backtrader
1.1 系统定位
小果动量策略回测系统(xg_mom_backtrader)是一套专为动量排名轮动策略设计的专业级量化回测框架。它把「动量强弱度量 → 每日排名 → 持有排名前 N 的标的 → 排名跌出即卖出 → 大涨触发止盈」这套经典动量轮动逻辑标准化为可配置的流水线,让研究者能够高效地验证动量因子有效性、回测策略并输出完整绩效报告。
1.2 动量策略核心逻辑
每个交易日按以下流程决策:
① 计算每只标的的动量分数 score(加权线性回归斜率 × R²)
② 按 score 从高到低排序,筛出满足 [min_mom, max_mom] 区间的标的
③ 取排名前 buy_rank 只作为目标持仓
④ 先卖出:当前持有但排名跌出前 buy_rank 的标的 → 全部卖出(动量卖出)
⑤ 再买入:目标持仓中尚未持有的标的 → 按比例/金额分配资金买入(动量买入)
⑥ 止盈:当日涨幅 ≥ sell_zdf 的持仓 → 卖出 sell_amount 金额(止盈卖出)
1.3 动量分数的计算原理
calculate_momentum_score 采用加权线性回归 + R² 加权的稳健动量度量:
- 取最近
mom_daily天的收盘价; - 对收盘价取对数
y = ln(close); - 以
x = 0,1,2,...为自变量,对y做加权线性回归(权重从 1 线性递增到 2,越近的样本权重越大); - 由回归斜率
slope年化为年化收益:年化收益 = e^(slope)²⁵⁰ − 1; - 计算决定系数
R²,用以衡量该趋势的稳定性/可信度; - 最终动量分数
score = 年化收益 × R²。
> 该设计同时惩罚了两类情况:斜率(趋势方向与速度)不佳者分数低;斜率好但趋势不稳定(R² 低)者分数也会被压低。
1.4 设计哲学
- 解耦与复用:数据加载、动量打分、排名轮动、交易执行、绩效评估各环节解耦,可独立优化。
- 透明可解释:每个交易日的动量得分、排名、买卖原因均可从
trade_log与报告追溯。 - 灵活可扩展:支持自定义数据注入、金额/百分比两种交易模式、可关闭的止盈,覆盖从简单到复杂的动量策略。
二、安装与运行环境
2.1 安装包
教程 https://gitcode.com/qq_50882340/xg_mom_backtrader
2.2 依赖
pandasnumpypyarrow(读取 parquet 历史数据用)openpyxl(导出 Excel 用)
2.3 包结构
推荐以「包」方式导入(发布到 GitHub/PyPI 的仓库结构):
xg_mom_backtrader/
├── __init__.py # 空文件即可
└── xg_mom_backtrader.py # 主模块(动量回测类)
2.4 导入方式
from xg_mom_backtrader.xg_mom_backtrader import xg_mom_backtrader
2.5 数据目录要求(使用内置数据源时)
系统默认从脚本同级的 data/ 目录读取本地 parquet 数据,目录结构为:
data/
├── 历史数据/
│ ├── 159915.SZ.parquet # 标的日线数据
│ └── ...
└── 指数数据/
└── 000300.SH.parquet # 基准指数数据
> 若不想准备本地数据,也可用自定义数据注入方式(见「十、自定义数据详解」),系统会自动切换到内存数据源,无需本地文件。
2.6 数据字段要求
必须字段:
| 字段名 | 说明 | 是否必须 |
|---|---|---|
date |
日期 | ✅ 必须 |
close |
收盘价 | ✅ 必须 |
open |
开盘价 | ✅ 必须 |
high |
最高价 | ✅ 必须 |
low |
最低价 | ✅ 必须 |
volume |
成交量 | ✅ 必须 |
推荐字段:
| 字段名 | 说明 | 是否必须 |
|---|---|---|
preClose |
前收盘价 | ⚠️ 推荐(用于复权与涨跌停过滤,缺失时会自动用 close.pct_change 计算涨跌幅) |
zdf |
涨跌幅 | ⚠️ 推荐(不存在时自动计算) |
amount |
成交金额 | ⚠️ 可选 |
证券代码 |
股票代码 | ⚠️ 可选(会自动添加) |
证券名称 |
股票名称 | ⚠️ 可选 |
三、初始化参数完整参考
> 以下为 __init__ 方法全部参数详细说明。
3.1 时间与标的 (Time & Universe)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
start_date |
str |
'20240101' |
回测起始日期,格式 YYYYMMDD |
end_date |
str |
'20500101' |
回测结束日期,格式 YYYYMMDD |
stock_list |
list |
['159915.SZ','513100.SZ','518880.SH'] |
候选标的池,元素需带交易所后缀(.SZ/.SH) |
index_stock |
str |
'000300.SH' |
对比指数代码(仅用于输出指数数据,非策略基准) |
3.2 资金与费用 (Capital & Cost)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cash |
float |
100000 |
初始资金(元) |
comm |
float |
0.0001 |
手续费率(万1 = 0.0001) |
3.3 动量与排名 (Momentum & Rank)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
mom_type |
str |
'百分比' |
资金分配方式:'百分比'(占总资产比例)/ '金额'(固定金额) |
mom_value |
float |
0.1 |
动量交易值。百分比模式为每期动用比例(0.1=10%总资产);金额模式为每期固定动用金额 |
mom_daily |
int |
25 |
动量计算天数(线性回归窗口) |
min_mom |
float |
0 |
最小动量分数阈值(分数低于此不买入) |
max_mom |
float |
5 |
最大动量分数阈值(分数高于此不买入) |
buy_rank |
int |
1 |
买入排名,持有排名前 N 的标的(也是最大同时持仓数) |
3.4 止盈 (Take Profit)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
sell_zdf |
float |
0.03 |
止盈阈值,当日涨幅 ≥ 此值触发止盈(0.03=3%)。设很大值(如 8888)可关闭止盈 |
sell_amount |
float |
1000 |
每次止盈卖出的金额(元) |
3.5 运行与数据源 (Runtime & Data Source)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
max_workers |
int |
4 |
多线程加载数据时的线程数 |
use_custom_data |
bool |
False |
是否使用自定义数据源(注入数据后系统会自动置为 True) |
3.6 内部固定参数(构造时自动设置,一般不修改)
| 参数 | 值 | 说明 |
|---|---|---|
adj_type |
'none' |
复权方式(源码当前固定为 none,即不做前复权/后复权) |
min_shares |
100 |
最小交易单位(ETF 一手 = 100 份) |
share_multiple |
100 |
交易数量的整数倍(每次按 100 份整数倍下单) |
3.7 mom_type 与 mom_value 匹配表
mom_type |
mom_value 含义 |
每期买入金额逻辑 |
|---|---|---|
'百分比' |
占总资产比例 | 每只买入 ≈ (总资产 × mom_value) ÷ 目标持仓数 |
'金额' |
固定金额 | 每只买入 ≈ mom_value ÷ 目标持仓数 |
> 目标持仓数 = len(target_codes)(通常等于 buy_rank,若有效候选不足则小于它)。买入金额同时被可用现金约束(min(买入额度, 账户现金)),并按 100 份整数倍、先扣手续费再成交。
四、数据加载与处理详解
4.1 数据来源优先级
get_stock_data(stock_code) 加载单只标的数据时按两步取数:
- 自定义数据源:当
use_custom_data=True且该代码已注入 → 从内存_custom_data_cache读取; - 本地文件:否则读取
data/历史数据/{code}.parquet。
两种路径都会执行统一的清洗流程:
- 截取
[start_date, end_date]区间; - 按日期升序排序;
- 剔除
close <= 0或open <= 0的无效行; - 调用
adjust_price(当前 adj_type='none' 时原样返回); - 重新计算
zdf = close.pct_change()。
4.2 多线程加载
get_all_stock_data() 用 ThreadPoolExecutor(线程数 = max_workers)并发加载全部标的,打印每只的成功/失败、数据范围与价格范围,并返回 {code: DataFrame} 字典。
4.3 指数数据
get_index_data() 读取 data/指数数据/{index_stock}.parquet,仅用于输出展示(不参与策略基准对比)。
4.4 基准曲线(等权重持有不动)
calculate_benchmark_curve(all_data) 以全部标的的共同交易日为轴,每只归一化为「首日=1」,逐日取平均得到"等权买入持有不动"净值曲线,作为策略的对照基准,用于计算超额收益。
五、动量分数计算详解
5.1 方法签名
def calculate_momentum_score(self, df) -> float
5.2 入参/出参
df:某只标的截至当前交易日的 DataFrame(至少含date、close)。- 返回:动量分数
float。数据不足(行数 <mom_daily或 < 10)时返回0。
5.3 计算步骤
| 步骤 | 说明 |
|---|---|
| 取窗口 | 取 df.tail(mom_daily) 最近 mom_daily 行 |
| 取对数 | y = ln(close) |
| 构造权重 | w = linspace(1, 2, n)(线性增权,越新权重越大) |
| 加权回归 | x=0,1,...,对 y 做加权最小二乘,得斜率 slope |
| 年化 | 年化收益 = e^slope − 1(按 250 个交易日年化) |
| 拟合优度 | R² = 1 − Σ(w·残差²) / Σ(w·(y−ȳ)²) |
| 打分 | score = 年化收益 × R² |
5.4 参数调优建议
| 参数 | 说明 | 建议 |
|---|---|---|
mom_daily |
动量窗口天数 | 短周期 10–20,中周期 25–60,长周期 60–120;与调仓频率匹配 |
min_mom/max_mom |
动量分数过滤区间 | 可排除过热(分数过高)与弱势(分数过低)标的 |
buy_rank |
持有/买入前 N | 越大越分散、换手越低;越小越集中、进攻性越强 |
六、交易执行详解
6.1 买入规则(calculate_buy_shares)
calculate_buy_shares(amount, price) -> (shares, actual_cost)
- 先扣除手续费:
买入金额 = amount / (1 + comm); - 按 100 份整数倍取整:
shares = floor(买入金额 / price / 100) × 100; - 不足一手(< 100 份)则买入 0;
- 若含手续费成本超过可用金额,循环减一手直到不超;
- 返回
(买入股数, 实际成本=市值+手续费)。
6.2 卖出规则
全卖(动量卖出) —— calculate_sell_shares_all(shares, price):
- 排名跌出前 N 时,将持仓全部卖出(不足一手时按实际持仓卖出);
- 实收金额 = 卖出市值 − 手续费。
按金额卖(止盈卖出) —— calculate_sell_shares_by_amount(shares, price, sell_amount):
- 当日涨幅 ≥
sell_zdf时触发,卖出sell_amount对应市值; - 按 100 份整数倍;卖出股数超持仓则全卖;
- 实收金额 = 卖出市值 − 手续费。
6.3 日交易记录字段
每笔成交写入 trade_log,字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
date |
datetime | 交易日期 |
stock |
str | 标的代码 |
type |
str | 交易类型:动量卖出 / 动量买入 / 止盈卖出 |
price |
float | 成交价格 |
shares |
int | 成交股数 |
amount |
float | 成交金额(买入为含手续费成本;卖出为实收金额) |
commission |
float | 该笔手续费 |
reason |
str | 交易原因,如 排名跌出前1,卖出XXX、排名前1,买入XXX、涨幅3.50%触发止盈 |
七、完整函数清单
7.1 构造与配置
| 方法 | 说明 |
|---|---|
__init__(...) |
构造动量回测实例并打印参数配置 |
get_config() -> dict |
返回当前全部配置参数(含隐藏内部参数与已注入数据 key) |
7.2 数据加载与处理
| 方法 | 说明 |
|---|---|
get_stock_data(stock_code) |
加载单只标的(自定义源优先,其次本地 parquet),返回清洗后的 DataFrame |
_load_single_stock(stock) |
多线程任务包装:加载单只,返回 (code, df, success, msg) |
get_all_stock_data() -> dict |
多线程并发加载全部标的 |
get_index_data() |
加载对比指数数据(parquet) |
_normalize_dataframe(df, stock_code) |
标准化注入的 DataFrame:日期识别/排序、补缺列、算 zdf、算复权因子 |
adjust_price(df) |
价格复权处理(当前 adj_type='none' 时原样返回) |
7.3 自定义数据注入(7 种接口)
| 方法 | 说明 |
|---|---|
add_stock_data_from_dataframe(stock_code, df) -> bool |
从 DataFrame 注入 |
add_stock_data_from_excel(stock_code, file_path, sheet_name=0, **kwargs) -> bool |
从 Excel 注入 |
add_stock_data_from_csv(stock_code, file_path, **kwargs) -> bool |
从 CSV 注入(自动尝试 utf-8/gbk 等编码) |
add_stock_data_from_json(stock_code, file_path, orient='records', **kwargs) -> bool |
从 JSON 注入 |
add_stock_data_from_dict(stock_code, data) -> bool |
从字典注入 |
add_stock_data_from_parquet(stock_code, file_path, columns=None, **kwargs) -> bool |
从 Parquet 注入 |
add_stock_data_from_bytes(stock_code, file_bytes, file_type='csv', **kwargs) -> bool |
从字节流注入(适合上传) |
7.4 自定义数据管理
| 方法 | 说明 |
|---|---|
clear_custom_data() |
清空全部自定义数据,use_custom_data 置 False |
remove_stock_data(stock_code) -> bool |
移除指定标的自定义数据 |
get_custom_data_keys() -> list |
返回已注入数据的全部标的代码 |
has_custom_data(stock_code) -> bool |
检查某标的是否已注入数据 |
7.5 动量打分与基准
| 方法 | 说明 |
|---|---|
calculate_momentum_score(df) |
计算单只标的动量分数(加权线性回归×R²) |
calculate_benchmark_curve(all_data) |
计算等权持有不动基准净值曲线 |
7.6 交易计算
| 方法 | 说明 |
|---|---|
calculate_buy_shares(amount, price) |
买入股数与成本计算(100 份整数倍、先扣佣金) |
calculate_sell_shares_by_amount(shares, price, sell_amount) |
按金额卖出(止盈) |
calculate_sell_shares_all(shares, price) |
全部卖出(动量卖出) |
7.7 回测执行
| 方法 | 说明 |
|---|---|
vectorized_backtest() |
向量化回测主引擎(逐日轮动) |
run_backtest() |
回测主入口,等价于 vectorized_backtest() |
7.8 绩效与报告
| 方法 | 说明 |
|---|---|
calculate_performance_metrics(df, df_index) -> dict |
计算核心绩效指标 |
calculate_annual_performance(portfolio_df) -> dict |
计算逐年收益统计 |
get_annual_performance_df() |
年度统计转 DataFrame |
get_performance_metrics() |
获取绩效(未回测则自动回测) |
get_equity_curve_data() |
获取净值曲线数据 |
get_annual_performance() |
获取年度统计 |
generate_report() -> str |
生成文本回测报告 |
generate_equity_curve_data() |
生成净值曲线 dict |
generate_trade_data() |
生成成交数据 dict |
generate_position_data() |
生成持仓数据 dict |
generate_account_data() |
生成账户数据 dict |
7.9 数据获取接口
| 方法 | 说明 |
|---|---|
get_trade_data() |
获取成交数据 {'trades': [...], 'statistics': {...}} |
get_position_data() |
获取持仓数据 {'positions': [...], 'statistics': {...}} |
get_account_data() |
获取账户数据 {'account_history': [...], 'statistics': {...}} |
get_daily_positions() |
获取每日持仓明细(records) |
get_trade_log() |
获取交易日志(records) |
get_portfolio_history() |
获取组合历史(records) |
get_stock_results() |
获取各标的回测结果 |
get_backtest_summary() |
获取回测摘要(配置+绩效+年度+全量数据) |
get_all_data() |
获取全部数据(JSON 可序列化) |
get_backtrader_data() |
兼容接口,按「指数/成交/持股/账户/绩效/年度/净值/策略参数」组织 |
7.10 结果保存
| 方法 | 说明 |
|---|---|
save_backtrader_data(user='test') |
分类保存为 Excel + JSON 到 策略模型/动量策略/{user}/ |
save_to_json(filename=None, include_raw_data=False) |
保存全量结果为单个 JSON 文件 |
7.11 内部工具方法(供二次开发参考)
| 方法 | 说明 |
|---|---|
_convert_to_serializable(obj) |
把 numpy/pandas/Timestamp 等递归转为 JSON 可序列化类型 |
八、输出数据结构详解
8.1 绩效指标字段(performance_metrics)
| 字段 | 说明 | 备注 |
|---|---|---|
start_date / end_date |
回测起止 | 字符串 YYYY-MM-DD |
total_days |
交易日数 | int |
total_cash |
初始资金 | float |
final_value |
最终总资产 | 元 |
total_return |
总收益率 | 小数(0.25 = 25%) |
total_pnl |
总盈亏额 | 元 |
total_commission |
总手续费 | 元 |
remaining_cash |
期末现金 | 元 |
final_holdings |
期末总持仓份额 | 份 |
annual_return |
年化收益率 | 小数 |
max_drawdown |
最大回撤 | 负数,小数 |
max_drawdown_date |
最大回撤发生日 | 字符串 |
sharpe_ratio |
夏普比率 | — |
sortino_ratio |
索提诺比率 | — |
calmar_ratio |
卡尔玛比率 | — |
win_rate |
日胜率 | 小数 |
volatility |
年化波动率 | 小数 |
benchmark_start_value / benchmark_final_value |
基准起止净值 | 等权持有不动 |
benchmark_total_return |
基准总收益率 | 小数 |
benchmark_max_drawdown |
基准最大回撤 | 小数 |
excess_return |
超额收益 | 小数(策略−基准) |
excess_return_pct |
超额收益百分比 | 数值 |
mom_daily/min_mom/max_mom/buy_rank/mom_type/mom_value/sell_zdf/sell_amount/use_custom_data |
回放的策略参数 | 便于留档 |
8.2 净值曲线字段(equity_curve_data)
| 字段 | 说明 |
|---|---|
dates |
日期字符串列表 |
total_value |
每日总资产 |
cumulative_pnl |
每日累计盈亏 |
cash |
每日现金 |
holdings |
每日持仓份额 |
net_value |
策略净值(总资产/初始资金) |
drawdown |
回撤百分比(%) |
total_return |
每日累计收益率 |
daily_return |
每日收益率 |
benchmark_net_value / benchmark_return |
基准净值/收益 |
statistics |
起点/终点/最高/最低/总收益/最大回撤等摘要 |
benchmark_statistics |
基准净值摘要 |
8.3 成交统计(trade_data['statistics'])
total_trades、buy_trades、sell_trades、total_buy_amount、total_sell_amount、total_commission、first_trade_date、last_trade_date。
8.4 账户统计(account_data['statistics'])
起始/结束日期、总天数、初始资金、期末总资产/现金/持仓、总盈亏、总收益率、最高/最低总资产、最高/最低净值、波动率、基准期末净值与总收益。
8.5 文本报告内容(generate_report())
策略参数 → 最终持仓 → 汇总持仓 → 收益指标(总/年化/盈亏/波动/手续费)→ 基准对比 → 风险指标(回撤/夏普/索提诺/卡尔玛)→ 交易统计(笔数/胜率)→ 年度收益统计表。
8.6 导出目录结构
策略模型/动量策略/{user}/
├── 指数数据/ 指数数据.xlsx + .json
├── 成交数据/ 成交数据.xlsx + .json
├── 持股数据/ 持股数据.xlsx + .json
├── 账户数据/ 账户数据.xlsx + .json
├── 绩效指标/ 绩效指标.xlsx + .json
├── 净值曲线/ 净值曲线.xlsx + .json
├── 策略参数/ 策略参数.xlsx + .json
└── 年度收益统计/ 年度收益统计.xlsx + .json
九、完整实战示例
9.1 示例 1:经典动量轮动(百分比模式)
from xg_mom_backtrader.xg_mom_backtrader import xg_mom_backtrader
bt = xg_mom_backtrader(
start_date='20230101',
end_date='20261201',
stock_list=['159915.SZ', '513100.SH', '518880.SH', '510300.SH', '512880.SH'],
index_stock='000300.SH',
cash=500000,
comm=0.0003,
mom_type='百分比', # 每期动用总资产的 XX%
mom_value=0.8, # 每次动用总资产的 80% 用于买入
mom_daily=25, # 动量窗口 25 天
min_mom=0, # 动量分数下限
max_mom=5, # 动量分数上限
buy_rank=3, # 持有并买入排名前 3 的标的
sell_zdf=0.15, # 当日涨 15% 触发止盈
sell_amount=20000, # 每次止盈卖 2 万元
max_workers=8,
)
bt.run_backtest()
print(bt.generate_report())
bt.save_backtrader_data(user='动量轮动示例')
9.2 示例 2:固定金额模式(关闭止盈)
bt = xg_mom_backtrader(
start_date='20240101',
end_date='20261201',
stock_list=['159915.SZ', '513100.SH', '518880.SH'],
cash=200000,
comm=0.0001,
mom_type='金额', # 每期动用固定金额
mom_value=100000, # 每期动用 10 万元买入
mom_daily=60, # 更长动量窗口
min_mom=0,
max_mom=5,
buy_rank=1, # 只持有第一名
sell_zdf=8888, # 设很大的值关闭止盈,仅靠排名轮动
sell_amount=1000,
max_workers=4,
)
bt.run_backtest()
print(bt.generate_report())
9.3 示例 3:自定义数据注入回测
bt = xg_mom_backtrader(
start_date='20230101',
end_date='20261201',
stock_list=['159915.SZ', '513100.SH', '518880.SH', '510300.SH'],
cash=300000,
comm=0.0002,
mom_type='百分比',
mom_value=1.0,
mom_daily=20,
min_mom=0,
max_mom=5,
buy_rank=2,
sell_zdf=0.1,
sell_amount=5000,
use_custom_data=True, # 显式声明使用自定义数据
)
# 用不同格式注入数据
bt.add_stock_data_from_csv('159915.SZ', 'data/159915.csv')
bt.add_stock_data_from_excel('513100.SH', 'data/513100.xlsx', sheet_name='Sheet1')
bt.add_stock_data_from_json('518880.SH', 'data/518880.json')
bt.add_stock_data_from_dataframe('510300.SH', df_510300) # 内存 DataFrame
print("已注入:", bt.get_custom_data_keys())
bt.run_backtest()
print(bt.generate_report())
bt.save_to_json('result/momentum_backtest.json')
9.4 示例 4:提取结果做二次分析 + 画净值曲线
bt.run_backtest()
metrics = bt.get_performance_metrics()
curve = bt.get_equity_curve_data()
trades = bt.get_trade_data()
print(f"总收益率: {metrics['total_return']*100:.2f}%")
print(f"年化收益: {metrics['annual_return']*100:.2f}%")
print(f"最大回撤: {metrics['max_drawdown']*100:.2f}%")
print(f"夏普比率: {metrics['sharpe_ratio']:.3f}")
print(f"胜率: {metrics['win_rate']*100:.2f}%")
print(f"总交易笔数: {metrics.get('total_commission', '')}(详见 trade_data)")
import matplotlib.pyplot as plt
dates = curve['dates']
plt.figure(figsize=(12, 6))
plt.plot(dates, curve['net_value'], label='策略净值', linewidth=2)
if curve.get('benchmark_net_value'):
plt.plot(dates, curve['benchmark_net_value'], label='基准(持有不动)', linestyle='--')
plt.legend(); plt.xlabel('日期'); plt.ylabel('净值')
plt.title('动量策略净值曲线'); plt.xticks(rotation=45); plt.tight_layout(); plt.show()
plt.figure(figsize=(12, 4))
plt.fill_between(dates, 0, curve['drawdown'], color='red', alpha=0.3)
plt.plot(dates, curve['drawdown'], color='red')
plt.xlabel('日期'); plt.ylabel('回撤(%)'); plt.title('动量策略回撤'); plt.tight_layout(); plt.show()
9.5 示例 5:年度绩效查看
bt.run_backtest()
annual = bt.get_annual_performance() # dict
annual_df = bt.get_annual_performance_df() # DataFrame
print(annual_df[['year', 'total_return', 'annual_return', 'max_drawdown', 'sharpe_ratio', 'win_rate']])
十、自定义数据详解
10.1 注入后系统自动做的事
add_stock_data_from_* 最终都会调用 add_stock_data_from_dataframe → _normalize_dataframe:
- 日期列:识别含
date/日期的列或 DatetimeIndex,统一转 datetime 并升序; - 缺列补齐:
close/high/low/open/volume任一缺失 → 尝试按列名模糊匹配,否则补空列(NaN); - 代码列:自动补
证券代码; - 涨跌幅:缺
zdf时用close.pct_change()自动计算; - 复权因子:含
preClose时自动用close/preClose累计生成adj_factor。
10.2 注入成功判定与验证
- 注入成功返回
True,并把use_custom_data置为True; - 用
get_custom_data_keys()查看已注入的代码; - 用
has_custom_data('159915.SZ')判断是否已注入。
> 建议:构造时把 use_custom_data=True 传入,可避免中途依赖本地文件;若使用本地数据则保持默认 False。
十一、常见问题与 FAQ
| 问题 | 解决方案 |
|---|---|
| 提示数据/文件不存在 | 检查 data/历史数据/{code}.parquet 与 data/指数数据/{index}.parquet;或改用 add_stock_data_from_* 注入自定义数据 |
| 某标的动量分数恒为 0 | 数据行数需 ≥ mom_daily 且 ≥ 10,否则返回 0;检查区间内数据量是否足够 |
| 买入很少 / 一直空仓 | 确认候选分数落在 [min_mom, max_mom] 内;max_mom 过高会排除强势股,过低又可能全被排除(此时系统会兜底取前 buy_rank,请留意日志) |
持仓数量超过 buy_rank |
系统每天都会把排名跌出前 N 的先卖、新进前 N 的后买,正常情况下持仓数 ≤ buy_rank |
| 自定义数据注入了但没生效 | 用 get_custom_data_keys() 验证;确认构造或注入后 use_custom_data=True;代码(含后缀 .SZ/.SH)必须与 stock_list 完全一致 |
| 收益率为 0 或异常 | 检查价格列全为正、cash 初始值、买入是否因金额不足/不够一手而失败 |
| 手续费很高 | 降低换手可同时调大 mom_daily(更平滑)与 sell_zdf(减少止盈触发);检查 comm 是否正确 |
| 如何关闭止盈 | 把 sell_zdf 设成很大的数(如 8888),仅保留排名轮动卖出 |
十二、高级技巧与二次开发
12.1 动量窗口与调仓频率匹配
- 中短线:
mom_daily10–25,buy_rank1–3; - 中长线:
mom_daily60–120,buy_rank3–10; - 想降低换手可同时加大
mom_daily与buy_rank。
12.2 分数区间风控
用 min_mom/max_mom 做"动量温度"过滤:过热(分数过高,追高风险大)与弱势(分数过低/为负)标的都可被排除,从而过滤噪声行情。
12.3 自定义动量打分
继承 xg_mom_backtrader 并重写 calculate_momentum_score(df),即可换成任意动量/因子打分逻辑(如 RSI 动量、MACD 排序、双均线斜率等),排名与买卖流程完全复用。
from xg_mom_backtrader.xg_mom_backtrader import xg_mom_backtrader
class MyMom(xg_mom_backtrader):
def calculate_momentum_score(self, df):
# 自定义:改用 20日涨跌幅 作为动量分数
if len(df) < 21:
return 0
return float(df['close'].iloc[-1] / df['close'].iloc[-21] - 1)
bt = MyMom(start_date='20230101', end_date='20261201', buy_rank=2, mom_daily=20)
bt.run_backtest()
print(bt.generate_report())
12.4 自定义交易/止盈规则
继承后重写 vectorized_backtest() 或复用其框架,自定义买卖金额分配、加止盈/止损组合等逻辑。
十三、技术说明与提示
performance_metrics中的比率多为小数(如total_return=0.25表示 25%),报告打印时已 ×100;直接从 dict 取值时注意换算。total_return、win_rate、max_drawdown、volatility均为小数;excess_return_pct已是百分比数值。- 年化按 365.25 天折算,夏普/波动按 250 个交易日年化,无风险利率固定 0.02。
- 回测按共同交易日推进,标的上市/停牌造成的日期错位会被自动对齐剔除。
- 源码当前固定
adj_type='none',若需前复权/后复权可在adjust_price中扩展。
1. 指数数据读取接口
新增了以下方法用于加载自定义指数数据:
方法 说明
add_index_data_from_dataframe(df) 从DataFrame加载指数数据
add_index_data_from_csv(file_path) 从CSV文件加载指数数据
add_index_data_from_excel(file_path, sheet_name) 从Excel文件加载指数数据
add_index_data_from_json(file_path) 从JSON文件加载指数数据
add_index_data_from_dict(data) 从字典加载指数数据
add_index_data_from_parquet(file_path) 从Parquet文件加载指数数据
add_index_data_from_bytes(bytes, file_type) 从字节数据加载指数数据
2. 指数数据管理方法
方法 说明
clear_index_data() 清空自定义指数数据
has_index_data() 检查是否有自定义指数数据
get_index_data_info() 获取指数数据信息
小果动量策略回测系统,让动量/轮动策略开发更高效、更透明。如需源码、更多因子库或技术支持,欢迎联系小果(微信:xg_quant)。