小果综合动量回测系统专家
一、系统概述
使用教程:https://gitcode.com/qq_50882340/xg_more_mom_backtrader
1.1 系统定位
小果综合动量回测系统(xg_more_mom_backtrader)是一套集成多种动量模型、可叠加大盘择时/动量区间/开仓/平仓/止盈多级过滤的专业级量化动量轮动回测框架。它以「过去 N 日的历史走势」为每只标的计算一个动量分数,按分数降序排名轮动(持有第 max_rank 至第 min_rank 名),并通过若干可独立开关的条件层决定「何时允许买入 / 何时强制卖出 / 何时落袋止盈」。
与纯均值方差(MPT 资产配置)系统不同,综合动量系统靠趋势/强弱排序择时选标的,而非按风险收益做配置——本质是「动量选强势、排名轮动持有、多条件风控」的信号交易型策略。系统支持同一资金池整体运作(不把 cash 均分),每日对全池计算动量、更新目标持仓并执行交易。
> 动量核心思想:强者恒强(追涨)/ 弱者恒弱(杀跌)——历史一段时期内相对强势的标的,未来更可能延续强势;本系统用多种动量模型把“谁强谁弱”数量化,只持有池内排名靠前的强势标的。
1.2 核心策略逻辑(每 interval 交易日执行)
综合动量框架在每个检查交易日运行四步主循环:
① 计算动量分:对每只标的使用所选 mom_models 计算当日动量分数,降序排名
② 动量区间过滤(可选):仅保留分数 ∈ [mini_value, max_value] 的标的
③ 取排名区间:目标持仓 = 排名第 max_rank ~ min_rank 名(排除区间则退化为前 min_rank)
④ 四步交易执行:
├─ 大盘择时(可选):指数条件不满足 → 清仓非 index_not_trader 标的
├─ 轮动卖出:清仓不在目标排名内的持仓(排他 index_not_trader)
├─ 轮动买入:对目标排名内但未持有的标的,按 mom_type 分配资金买入(需过买入条件)
├─ 平仓条件(可选):触发卖出条件的持仓强制平仓
└─ 盘中止盈:当日 zdf ≥ sell_zdf 时按 sell_amount 落袋止盈
interval 控制调仓检查频率(默认 1 = 每个交易日检查);非检查日仅做市值/持仓记账,不发生交易。买入资金由 mom_type(金额/数量/百分比)与 mom_value 决定,按目标数量均分预算。
1.3 六大动量模型(mom_models 核心)
动量分由 calculate_momentum 按 mom_models 分发到对应打分方法:
| 模型 | 方法 | 打分逻辑 | 关键参数 |
|---|---|---|---|
| 动量1 | calculate_momentum_score_1 |
线性回归动量(加权):对近 mom_daily 天 log(close) 做带权线性回归(权重 linspace(1,2)),年化斜率 × R² 作为分数 |
mom_daily |
| 动量2 | calculate_momentum_score_2 |
价格涨幅:现价 ÷ N 日前收盘价 − 1 |
period |
| 动量3 | calculate_momentum_score_3 |
均线偏离:现价 ÷ 近 N 日均线 − 1 |
period |
| 动量4 | calculate_momentum_score_4 |
滞后均线:现价 ÷ N 日前 M 日均线 − 1 |
period, short_ma |
| 动量5 | calculate_momentum_score_5 |
均线交叉:近 N 日均线 ÷ 近 M 日均线 − 1(短/长) |
short_ma, long_ma |
| 动量6 | calculate_momentum_score_6 |
滞后均线交叉:近 N 日均线 ÷ A 日前 M 日均线 − 1 |
period, short_ma, long_ma |
> 📌 各打分方法数据不足(少于各自所需周期)时返回 0;未识别 mom_models 时回退到动量1。
1.4 多级条件开关(可独立组合)
| 开关 | 触发函数 | 开启参数 | 作用 |
|---|---|---|---|
| 大盘择时 | check_index_condition |
enable_index_timing=True |
指数现价相对 index_mean_line 均线关系决定是否允许建仓;不满足则清仓非 index_not_trader 标的 |
| 动量区间 | (主循环内) | enable_mom_filter=True |
仅保留分数 ∈ [mini_value, max_value] 的标的参与排名 |
| 开仓条件 | check_buy_condition |
enable_buy_condition=True |
仅当标的满足买入条件(涨幅/均线/均线之上/低点/高点)才允许买入 |
| 平仓条件 | check_sell_condition |
enable_sell_condition=True |
触发卖出条件(跌幅/均线/均线之下/高点/低点)的持仓强制清仓 |
| 盘中止盈 | (主循环内) | 常开 | 当日 zdf ≥ sell_zdf → 按 sell_amount 金额卖出部分落袋 |
> ⚠️ 大盘择时清仓与平仓条件会排除 index_not_trader 名单内的标的(默认纳指/黄金 ETF 常驻不受大盘涨跌拖累);而轮动卖出/买入与止盈则对所有目标标的作用。
1.5 参数与配置维度总览
基础(mom_type/mom_value) → 模型选择(mom_models/各周期) → 区间过滤(enable_mom_filter)
→ 排名轮动(max_rank/min_rank) → 大盘择时(enable_index_timing/均线/条件/偏移)
→ 开平仓条件(enable_buy/sell_condition) → 盘中止盈(sell_zdf/sell_amount) → 频率(interval)
- 资金分配:
mom_type ∈ {金额,数量,百分比},mom_value对应「元/份/总资产比例」。 - 轮动范围:
max_rank~min_rank(默认均 1,即只持最强 1 只)。 - 数据源:标的 + 指数均支持本地内置与 7 种自定义注入两套通道,
use_custom_data/use_custom_index_data切换。
1.6 设计哲学
- 模型可插拔:6 种动量模型解耦为独立方法,
calculate_momentum统一分发,可继承新增第 7 种模型。 - 过滤可叠加:大盘择时/动量区间/开平仓/止盈各自独立开关,自由组合出从纯动量轮动到多重风控的任意策略。
- 强弱量化:以相对排名而非绝对涨跌作为交易依据,天然适配“追强弃弱”的轮动逻辑。
- 透明可追溯:每日记录现金/持仓/市值/各标的明细,成交带
reason解释触发原因。
二、安装与运行环境
2.1 安装包
小果综合动量回测系统以单个类文件 xg_more_mom_backtrader.py 形式提供,无独立安装步骤。将脚本置于工作目录后,在同一目录(或旁路)下放置数据文件即可使用。
使用教程:https://gitcode.com/qq_50882340/xg_more_mom_backtrader
2.2 依赖
Python ≥ 3.8- 第三方库:
pandas、numpy(动量回归用np.polyfit/math) - 本地数据源可选依赖:
pyarrow(读取.parquet历史数据) - 无需
scipy(本系统不涉及 SLSQP 优化,与均值方差系统不同) - 无需安装
xg_tdx_func(不涉及通达信公式引擎)
安装依赖:
pip install pandas numpy pyarrow
2.3 包结构
工作目录/
├── xg_more_mom_backtrader.py # 主类文件(class xg_more_mom_backtrader)
├── data/
│ ├── 历史数据/ # 本地标的日线 parquet(代码.parquet)
│ ├── stock_data/ # 可选备选路径
│ ├── 指数数据/ # 指数数据(parquet/xlsx/csv/json 多格式回退)
│ └── {代码}.parquet # 也可直接放工作目录根
└── 策略模型/
└── 综合动量策略/<user>/ # save_backtrader_data 导出目录(自动创建)
2.4 导入方式
> ⚠️ 本系统导入为带包路径的完整写法:from xg_more_mom_backtrader.xg_more_mom_backtrader import xg_more_mom_backtrader(把 scripts/ 视为包目录,xg_more_mom_backtrader.py 作为其子模块)。若你的脚本与 xg_more_mom_backtrader.py 同级、可直接使用模块名导入时,也可写 from xg_more_mom_backtrader import xg_more_mom_backtrader。推荐统一使用带包路径写法:
from xg_more_mom_backtrader.xg_more_mom_backtrader import xg_more_mom_backtrader
bt = xg_more_mom_backtrader(
start_date='20250101',
end_date='20261231',
stock_list=['159915.SZ', '513100.SH', '518880.SH'],
cash=100000,
)
> 说明:把 scripts/ 设为 PYTHONPATH(或将脚本目录加入 sys.path)后,from xg_more_mom_backtrader import xg_more_mom_backtrader 亦可。文档示例统一采用带包路径写法,便于在任意相对路径下引用。
2.5 数据目录要求(使用内置数据源时)
当未注入自定义数据(use_custom_data=False)时,标的日线 parquet 依次尝试以下路径:
data/历史数据/{stock_code}.parquet
data/stock_data/{stock_code}.parquet
data/{stock_code}.parquet
找到即用;均找不到则该标的被跳过。_load_single_stock 要求单标的行数 > 50 行(动量计算需足够历史)。
指数数据(index_stock)在本地下依次尝试 data/指数数据/{index_stock}.parquet、.xlsx、.csv、.json(CSV 自动试多编码),用于大盘择时与绩效对比。
2.6 数据字段要求
标的(股票/ETF)日线数据需包含以下字段:
| 字段 | 说明 | 是否必须 |
|---|---|---|
date |
交易日期 | ✅ 必须 |
close |
收盘价 | ✅ 必须(>0) |
open |
开盘价 | ✅ 必须(>0) |
high |
最高价 | ✅ 可选(缺失自动=close/NaN) |
low |
最低价 | ✅ 可选(缺失自动=close/NaN) |
volume |
成交量 | ✅ 必须 |
zdf |
涨跌幅 | ⚠️ 推荐(缺失自动用 close.pct_change() 补) |
preClose |
前收盘价 | ⚠️ 可选(有则补 return/adj_factor) |
amount |
成交金额 | ⚠️ 可选 |
证券代码 / 证券名称 |
代码/名称 | ⚠️ 可选(自动补) |
> 动量计算基于 date + close + zdf;数据行数需 > 各动量模型所需周期 + 50 效果最佳(_load_single_stock 已强制 >50 行)。
>
> 指数数据仅需 date + close(close>0),用于大盘择时判断与基准对比输出留档。
三、初始化参数完整参考
> 以下为 __init__ 方法全部参数的详细说明,按功能分组。
3.1 基础参数 (Base)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
start_date |
str |
'20250101' |
回测起始日期,格式 YYYYMMDD |
end_date |
str |
'20500101' |
回测结束日期,格式同上 |
stock_list |
list |
['159915.SZ','513100.SH','518880.SH'] |
候选资产池,元素需带交易所后缀(可用自定义代码) |
index_stock |
str |
'000300.SH' |
基准指数代码(用于大盘择时与绩效对比) |
cash |
float |
100000 |
初始总资金(元,整体资金池,不均分) |
comm |
float |
0.0001 |
手续费率(万1 = 0.0001,双边) |
max_workers |
int |
4 |
数据加载并发线程数 |
interval |
int |
1 |
调仓检查间隔(交易日,1=每天) |
3.2 大盘择时参数 (Index Timing, enable_index_timing=True 时生效)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enable_index_timing |
bool |
False |
是否开启大盘择时功能 |
index_mean_line |
int |
20 |
大盘择时均线周期(日) |
index_not_trader |
list |
['513100.SH','518880.SH'] |
择时排除标的(不受大盘择时限制的常驻标的) |
index_condition_type |
str |
'大于均线' |
择时条件:大于均线/小于均线/均线之上/均线之下 |
index_offset |
float |
0.0 |
择时偏移量(%),配 均线之上/均线之下 |
3.3 动量交易参数 (Momentum Trading)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
mom_type |
str |
'百分比' |
交易类型:金额(元) / 数量(份) / 百分比(总资产%) |
mom_value |
float |
1 |
交易值(金额模式:元;数量模式:份;百分比模式:总资产比例) |
3.4 动量模型选择参数 (Momentum Model)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
mom_models |
str |
'动量1' |
动量模型:动量1~动量6(见 1.3 表) |
mom_daily |
int |
25 |
动量1:线性回归回看天数 |
period |
int |
20 |
动量2/3/4/6:N 周期(日) |
short_ma |
int |
3 |
动量4/5/6:短均线周期 |
long_ma |
int |
20 |
动量5/6:长均线周期 |
3.5 动量区间过滤参数 (Momentum Filter, enable_mom_filter=True 时生效)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enable_mom_filter |
bool |
False |
是否开启动量区间过滤 |
max_value |
float |
5 |
动量分数最大值 |
mini_value |
float |
0 |
动量分数最小值 |
3.6 轮动排序参数 (Rank Rotation)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
max_rank |
int |
1 |
持有排名起始位(从1开始) |
min_rank |
int |
1 |
持有排名结束位 |
| 组合 | — | max_rank=2, min_rank=5 |
表示持有第 2~5 名 |
3.7 交易条件参数 (Buy/Sell Condition, 各自开关启用时生效)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enable_buy_condition |
bool |
False |
是否开启开仓买入条件 |
enable_sell_condition |
bool |
False |
是否开启平仓卖出条件 |
buy_condition_type |
str |
'涨幅' |
买入条件:涨幅/均线/均线之上/低点/高点 |
buy_period |
int |
20 |
买入条件计算周期 |
buy_period_ratio |
float |
0.1 |
买入阈值(%,配 涨幅) |
buy_offset |
float |
0.0 |
买入偏移(%,配 均线之上) |
sell_condition_type |
str |
'跌幅' |
卖出条件:跌幅/均线/均线之下/高点/低点 |
sell_period |
int |
20 |
卖出条件计算周期 |
sell_period_ratio |
float |
-0.1 |
卖出阈值(%,配 跌幅) |
sell_offset |
float |
0.0 |
卖出偏移(%,配 均线之下) |
3.8 止盈参数 (Take-Profit)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
sell_zdf |
float |
0.03 |
止盈触发涨跌幅(%),当日涨幅 ≥ 此值触发止盈卖出 |
sell_amount |
float |
1000 |
每次止盈卖出金额(元) |
3.9 数据源开关 (Data Source)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
use_custom_data |
bool |
False |
是否使用注入的自定义标的数据(自动切换) |
use_custom_index_data |
bool |
False |
是否使用注入的自定义指数数据(自动切换) |
3.10 内部固定参数(构造时自动设置,一般不修改)
| 参数 | 默认值 | 说明 |
|---|---|---|
adj_type |
'none' |
复权类型(当前固定 none) |
min_shares |
100 |
单笔最小成交份数(1 手) |
share_multiple |
100 |
成交份数整数倍(按 100 份撮合) |
3.11 关键口径:整体资金池与资金分配
| 概念 | 公式 | 说明 |
|---|---|---|
| 单一资金池 | account_cash 全局唯一 |
现金不按标的分账,买卖共用 |
| 单标买入预算 | calculate_buy_amount_per_stock(total_asset, price) |
金额→mom_value;百分比→total_asset×mom_value;数量→mom_value×price |
| 单标实买上限 | 预算 ÷ 目标标的数,再 min(可用现金) |
目标内新开仓标的均分预算 |
| 调仓频率 | idx % interval == 0 |
非检查日仅记账 |
| 手续费 | 买:先扣 comm 再买;卖:实收 金额×(1−comm) |
双边 |
四、数据加载与处理详解
4.1 标的(股票/ETF)数据来源优先级
get_stock_data(stock_code) 依次尝试:
- 自定义数据源(
use_custom_data=True且代码在_custom_data_cache)——add_stock_data_from_*注入后优先; - 本地多路径:
data/历史数据/{代码}.parquet→data/stock_data/{代码}.parquet→data/{代码}.parquet; - 均无 → 返回空 DataFrame,标的被跳过。
加载后统一:日期过滤到 [start_date, end_date]、按日期升序、剔除 close≤0 或 open≤0 行、adjust_price(当前 adj_type='none' 原样)、补算 zdf = close.pct_change().fillna(0)。
4.2 多线程加载与动量历史要求
get_all_stock_data() 用 ThreadPoolExecutor(max_workers) 并发加载全部标的;_load_single_stock 要求单标的数据 > 50 行,不足则丢弃(动量模型需足够回看历史)。加载成功与否在 verbose 日志中逐只打印。
4.3 指数数据来源与用途
get_index_data() 优先返回注入的自定义指数数据(use_custom_index_data=True 且已注入),否则在 data/指数数据/ 下依次读 {index_stock} 的 .parquet / .xlsx / .csv / .json(多编码自动尝试)。指数用于两处:大盘择时判断(check_index_condition)与基准对比输出留档;策略对照基准实际为 calculate_benchmark_curve 生成的等权持有不动曲线。
五、动量打分与条件判断详解
5.1 动量分发(calculate_momentum)
calculate_momentum(df) 依据 mom_models 从 {'动量1':…,'动量6':…} 字典取对应打分函数;未命中回退 calculate_momentum_score_1。打分输入为截至当日(含当日)的标的历史 df。
5.2 六种动量打分口径
| 方法 | 数据门槛 | 计算口径 |
|---|---|---|
calculate_momentum_score_1 |
≥ mom_daily(且子集≥10) |
log(close) 带权线性回归斜率年化 × R² |
calculate_momentum_score_2 |
≥ period |
close[-1]/close[-period] − 1 |
calculate_momentum_score_3 |
≥ period |
close[-1]/mean(tail(period)) − 1 |
calculate_momentum_score_4 |
≥ period+short_ma |
close[-1]/mean(过去 period 日之前 short_ma 日均线) − 1 |
calculate_momentum_score_5 |
≥ max(short_ma,long_ma) |
mean(tail(short_ma))/mean(tail(long_ma)) − 1 |
calculate_momentum_score_6 |
≥ period+long_ma |
mean(tail(short_ma))/mean(过去 period 日前 long_ma 日均线) − 1 |
数据不足或价格非正时各方法返回 0,保证排序可运行。
5.3 大盘择时条件(check_index_condition)
enable_index_timing=False 或指数数据缺失/不足 → 恒 True。否则取截至当日指数段算 index_mean_line 均线,再按 index_condition_type 判断:大于均线(现价>均线)、小于均线(现价<均线)、均线之上(现价>均线×(1+offset))、均线之下(现价<均线×(1−offset))。
5.4 开仓/平仓条件(check_buy_condition / check_sell_condition)
开仓仅在 enable_buy_condition=True 时拦截(否则恒通过):
buy_condition_type |
满足买入 |
|---|---|
涨幅 |
近 buy_period 日涨幅 > buy_period_ratio |
均线 |
现价 > 近 buy_period 日均线 |
均线之上 |
现价 > 均线 × (1 + buy_offset) |
低点 |
现价 > 近 buy_period 日低点 |
高点 |
现价 > 近 buy_period 日高点 |
平仓在 enable_sell_condition=True 时触发清仓(默认关闭则恒不触发),条件与买入对称(跌幅/均线/均线之下/高点/低点)。数据不足时买入恒通过、卖出恒不触发。
5.5 排名轮动与目标持仓
- 每检查日对全部标计算动量分 → 降序
sorted_stocks; - 若开动量区间过滤,仅保留
mini_value ≤ score ≤ max_value;过滤后为空则退化为取前min_rank名; - 目标持仓 = 过滤后名单中第
max_rank~min_rank名(max_rank超出长度则回退前min_rank名)。
六、交易执行详解
6.1 交易顺序(每检查日)
- 大盘择时清仓:指数条件不满足 → 对持仓中非
index_not_trader标的全仓卖出; - 轮动卖出:清仓不在目标排名内、且非
index_not_trader的持仓(全仓卖出,type=轮动卖出); - 轮动买入:对目标排名内、未持有、且通过买入条件的标的,按预算买入(type=
轮动买入); - 条件平仓:对持仓中非
index_not_trader且触发平仓条件的标的清仓(type=条件平仓); - 盘中止盈:对每只持仓,当日
zdf ≥ sell_zdf→ 按sell_amount卖出部分(type=止盈卖出)。
6.2 买入撮合(calculate_buy_shares)
buy_amount = amount / (1 + comm) # 先扣手续费
shares = 向下取整(buy_amount / price) # 再按 share_multiple(100) 对齐
若 shares < min_shares(100) → 0
实际成本 = shares×price × (1+comm),循环确保 ≤ amount
6.3 卖出撮合(calculate_sell_shares_all / calculate_sell_shares_by_amount)
calculate_sell_shares_all(shares, price):全仓卖出,按 100 对齐(不足 100 则按实际持仓卖出),实收金额×(1−comm),用于大盘择时/轮动/条件平仓。calculate_sell_shares_by_amount(shares, price, sell_amount):按金额卖出(止盈用),由sell_amount÷price算份数、对齐 100、上界为持仓,实收扣手续费。
6.4 成交记录字段(trade_log)
每笔成交记录:date / stock / type / price / shares / amount / commission / reason。
type 取值全集:大盘择时清仓 / 轮动卖出 / 轮动买入 / 条件平仓 / 止盈卖出;reason 示例:排名跌出第1-1名,卖出XXX、排名第1-1名,买入XXX、平仓条件触发,卖出XXX、涨幅5.20%触发止盈、大盘择时条件不满足,清仓XXX。
七、完整函数清单
> 以下为 xg_more_mom_backtrader 类的全部方法,按功能分组。共 69 个方法(含私有方法),无模块级顶层函数。
7.1 构造与配置
| 方法签名 | 说明 |
|---|---|
__init__(start_date, end_date, stock_list, ...) |
构造回测实例,配置基础/择时/动量/过滤/排序/条件/止盈/数据源等全部参数 |
_convert_to_serializable(obj) |
将对象(Timestamp/datetime/ndarray/DataFrame/NaN)递归转 JSON 可序列化 |
get_config() -> dict |
获取当前策略的全部配置参数(含动量模型/轮动/条件/止盈/数据源状态) |
7.2 自定义标的注入(7 种接口)
| 方法签名 | 说明 |
|---|---|
add_stock_data_from_dataframe(stock_code, df) |
从 DataFrame 注入标的日线数据 |
add_stock_data_from_excel(stock_code, file_path, sheet_name=0, **kwargs) |
从 Excel 注入 |
add_stock_data_from_csv(stock_code, file_path, **kwargs) |
从 CSV 注入 |
add_stock_data_from_json(stock_code, file_path, orient='records', **kwargs) |
从 JSON 注入 |
add_stock_data_from_dict(stock_code, data) |
从字典注入 |
add_stock_data_from_parquet(stock_code, file_path, columns=None, **kwargs) |
从 Parquet 注入 |
add_stock_data_from_bytes(stock_code, file_bytes, file_type='csv', **kwargs) |
从字节流注入 |
7.3 自定义标的数据库管理
| 方法签名 | 说明 |
|---|---|
clear_custom_data() |
清空所有已注入的自定义标的数据 |
remove_stock_data(stock_code) |
移除指定标的的自定义数据 |
get_custom_data_keys() -> list |
获取所有已注入标的代码列表 |
has_custom_data(stock_code) -> bool |
检查指定标的是否已有自定义数据 |
7.4 自定义指数注入(7 种接口)
| 方法签名 | 说明 |
|---|---|
add_index_data_from_dataframe(df) |
从 DataFrame 注入指数数据(需 date+close) |
add_index_data_from_excel(file_path, sheet_name=0, **kwargs) |
从 Excel 注入 |
add_index_data_from_csv(file_path, **kwargs) |
从 CSV 注入(自动尝试编码) |
add_index_data_from_json(file_path, orient='records', **kwargs) |
从 JSON 注入 |
add_index_data_from_dict(data) |
从字典注入 |
add_index_data_from_parquet(file_path, columns=None, **kwargs) |
从 Parquet 注入 |
add_index_data_from_bytes(file_bytes, file_type='csv', **kwargs) |
从字节流注入 |
7.5 指数数据管理
| 方法签名 | 说明 |
|---|---|
clear_index_data() |
清空自定义指数数据 |
has_index_data() -> bool |
检查是否有自定义指数数据 |
get_index_data_info() -> dict |
获取指数数据信息(行数/日期范围等) |
7.6 数据加载与处理
| 方法签名 | 说明 |
|---|---|
_normalize_dataframe(df, stock_code=None) |
标准化 DataFrame(日期列识别、close/high/low/open/volume 补齐、补 zdf/复权因子) |
get_stock_data(stock_code) |
读取单标的(优先自定义,其次本地多路径),日期过滤+列补齐 |
get_all_stock_data() |
多线程并发加载全部标的 |
_load_single_stock(stock) |
多线程任务:加载单只标的(要求 >50 行) |
adjust_price(df) |
价格复权处理(adj_type='none' 时原样返回) |
get_index_data() |
获取指数数据(优先自定义,其次本地 parquet/xlsx/csv/json 多格式回退) |
7.7 动量打分与条件判断(核心)
| 方法签名 | 说明 |
|---|---|
calculate_momentum_score_1(df) |
动量1:线性回归动量(带权 log 回归斜率年化×R²) |
calculate_momentum_score_2(df) |
动量2:现价÷N日前价 − 1 |
calculate_momentum_score_3(df) |
动量3:现价÷近N日均线 − 1 |
calculate_momentum_score_4(df) |
动量4:现价÷N日前M日均线 − 1 |
calculate_momentum_score_5(df) |
动量5:近N日均线÷近M日均线 − 1 |
calculate_momentum_score_6(df) |
动量6:近N日均线÷A日前M日均线 − 1 |
calculate_momentum(df) |
按 mom_models 分发到对应动量打分方法 |
check_index_condition(trade_date, index_df) |
检查大盘择时条件(均线关系/偏移),不满足则限制建仓 |
check_buy_condition(stock, df_until_today, price_now) |
检查开仓买入条件(涨幅/均线/均线之上/低点/高点) |
check_sell_condition(stock, df_until_today, price_now) |
检查平仓卖出条件(跌幅/均线/均线之下/高点/低点) |
calculate_buy_amount_per_stock(total_asset, target_price) |
按 mom_type/mom_value 计算单标的买入金额预算 |
7.8 交易撮合计算
| 方法签名 | 说明 |
|---|---|
calculate_buy_shares(amount, price) |
计算买入份额与含费实际成本(先扣费、100 手对齐) |
calculate_sell_shares_all(shares, price) |
全部卖出份额与实得金额(100 对齐、扣手续费) |
calculate_sell_shares_by_amount(shares, price, sell_amount) |
按金额卖出份额与实得金额(止盈用,不超持仓) |
7.9 回测执行
| 方法签名 | 说明 |
|---|---|
calculate_benchmark_curve(all_data) |
计算等权重持有不动基准净值曲线 |
vectorized_backtest() |
向量化回测主函数(动量排名→轮动→多级条件/止盈→每日记账),返回 portfolio_history |
run_backtest() |
执行完整回测(等价于 vectorized_backtest) |
7.10 绩效与数据生成
| 方法签名 | 说明 |
|---|---|
generate_equity_curve_data() |
生成净值曲线(含 net_value/drawdown/position_ratio/基准) |
generate_trade_data() |
生成成交明细与统计(trades+statistics) |
generate_position_data() |
生成每日持仓与统计(positions+statistics) |
generate_account_data() |
生成每日账户与统计(account_history+statistics) |
calculate_performance_metrics(df, df_index) |
计算核心绩效指标 |
calculate_annual_performance(portfolio_df) |
计算年度收益统计 |
7.11 数据获取接口(懒触发,自动 run_backtest)
| 方法签名 | 说明 |
|---|---|
get_annual_performance() |
获取年度收益统计 |
get_annual_performance_df() |
年度收益转 DataFrame(修复重复列名) |
get_trade_data() / get_position_data() / get_account_data() |
获取成交/持仓/账户数据 |
get_daily_positions() / get_trade_log() / get_portfolio_history() |
获取原始日持仓/成交/组合历史(dict records) |
get_performance_metrics() / get_equity_curve_data() |
获取绩效/净值 |
get_stock_results() |
获取各标的独立回测结果 |
get_backtest_summary() |
获取回测摘要(config+绩效+年度+成交+净值+各标的) |
get_all_data() |
获取全量数据(JSON 可序列化) |
get_backtrader_data() |
获取按分类组织的回测数据(指数/成交/持股/账户/绩效等) |
7.12 结果保存与报告
| 方法签名 | 说明 |
|---|---|
save_backtrader_data(user='test') |
分类保存回测结果到 策略模型/综合动量策略/{user}/(Excel+JSON) |
generate_report() |
生成格式化文本报告 |
八、输出数据结构详解
8.1 绩效指标字段(performance_metrics)
| 字段 | 类型 | 说明 |
|---|---|---|
start_date / end_date |
str |
回测起止日期 |
total_days |
int |
交易总天数 |
total_cash |
float |
初始总资金 |
final_value |
float |
最终总资产 |
total_return |
float |
总收益率(小数) |
total_pnl |
float |
总盈亏额 |
total_commission |
float |
累计手续费 |
remaining_cash / final_holdings |
float |
期末现金 / 期末总持仓份数 |
annual_return |
float |
年化收益率 |
max_drawdown |
float |
最大回撤(负数) |
max_drawdown_date |
str |
最大回撤发生日 |
sharpe_ratio / sortino_ratio / calmar_ratio |
float |
夏普 / 索提诺 / 卡玛比率 |
win_rate |
float |
胜率 |
volatility |
float |
年化波动率 |
benchmark_final_value / benchmark_start_value |
float |
基准(等权持有)期末/期初净值 |
benchmark_total_return |
float |
基准总收益率 |
benchmark_max_drawdown |
float |
基准最大回撤 |
excess_return / excess_return_pct |
float |
相对基准超额收益(小数/百分比) |
mom_models/mom_type/mom_value/mom_daily/period/short_ma/long_ma |
str/int/float |
动量参数留档 |
max_rank/min_rank/max_value/mini_value |
int/float |
轮动/过滤参数留档 |
enable_mom_filter/enable_index_timing/index_mean_line/index_condition_type/index_offset |
bool/int/str/float |
择时参数留档 |
enable_buy_condition/enable_sell_condition/buy_*/sell_* |
bool/str/int/float |
买卖条件参数留档 |
sell_zdf / sell_amount / interval / adj_type |
float/int/str |
止盈/频率/复权留档 |
use_custom_data / use_custom_index_data |
bool |
数据源开关留档 |
8.2 年度收益统计字段(annual_performance,按年份键)
| 字段 | 类型 | 说明 |
|---|---|---|
year / start_date / end_date / total_days |
int/str |
年份与年度区间 |
total_return / annual_return / total_pnl / final_value |
float |
年度收益指标 |
max_drawdown / volatility |
float |
年度风险指标 |
sharpe_ratio / sortino_ratio / calmar_ratio |
float |
年度风险调整收益 |
win_rate / t_trades / total_commission |
float/int |
年度胜率 / 成交笔数 / 手续费 |
benchmark_total_return / benchmark_* |
float |
年度基准各项 |
excess_return / excess_return_pct |
float |
年度超额收益 |
8.3 净值曲线字段(equity_curve_data)
| 字段 | 类型 | 说明 |
|---|---|---|
dates |
list |
日期字符串列表 |
total_value / cumulative_pnl |
list |
每日总资产 / 累计盈亏 |
cash / holdings |
list |
现金 / 持仓份数 |
net_value |
list |
策略净值(总资产/初始资金) |
drawdown |
list |
回撤(%) |
position_ratio |
list |
持仓占比(%) |
total_return / daily_return |
list |
累计 / 日收益率 |
benchmark_net_value / benchmark_return |
list |
基准净值 / 基准日收益率 |
statistics |
dict |
汇总(起止日/天数/初始/最大最小值/总收益/最大回撤) |
benchmark_statistics |
dict |
基准汇总(起止净值/总收益/最大回撤) |
8.4 成交统计(trade_data['statistics'])
total_trades / buy_trades / sell_trades / total_buy_amount / total_sell_amount / total_commission / first_trade_date / last_trade_date。
8.5 持仓统计(position_data['statistics'])
total_days / max_holdings / min_holdings / final_holdings / avg_holdings / first_date / last_date / final_return;每条 positions 含 stock_details(各标的 price/zdf/holdings/value/cash)。
8.6 账户统计(account_data['statistics'])
start_date / end_date / total_days / initial_cash / final_total_value / final_cash / final_holdings / total_pnl / total_return / max_total_value / min_total_value / max_net_value / min_net_value / volatility,含 benchmark_final_value / benchmark_total_return。
8.7 各标的独立结果(get_stock_results)
每标的含:cash / holdings / total_value(每日数组)、trades(该标成交)、total_commission 以及 final_holdings / final_cash / final_value。
8.8 文本报告内容(generate_report())
打印策略参数、最终持仓、收益指标、基准对比、风险指标、交易统计、年度收益表格(含基准/超额),并标注指数数据来源。
8.9 导出目录结构
策略模型/综合动量策略/<user>/
├── 指数数据/ 指数数据.xlsx + 指数数据.json
├── 成交数据/ 成交数据.xlsx + 成交数据.json
├── 持股数据/ 持股数据.xlsx + 持股数据.json
├── 账户数据/ 账户数据.xlsx + 账户数据.json
├── 绩效指标/ 绩效指标.xlsx + 绩效指标.json
├── 年度收益统计/ 年度收益统计.xlsx + 年度收益统计.json
├── 净值曲线/ 净值曲线.json
└── 策略参数/ 策略参数.xlsx + 策略参数.json
九、完整实战示例
9.1 示例1:经典动量1轮动回测(本地数据)
from xg_more_mom_backtrader.xg_more_mom_backtrader import xg_more_mom_backtrader
bt = xg_more_mom_backtrader(
start_date='20250101',
end_date='20261231',
stock_list=['159915.SZ', '513100.SH', '518880.SH'],
index_stock='000300.SH',
cash=100000,
comm=0.0001,
max_workers=4,
# 动量模型与交易
mom_models='动量1', # 线性回归动量
mom_daily=25,
mom_type='百分比',
mom_value=0.5, # 每次买入总资产的50%
# 轮动持有第1名
max_rank=1,
min_rank=1,
interval=1,
)
bt.run_backtest()
print(bt.generate_report())
bt.save_backtrader_data(user="动量1轮动")
9.2 示例2:均线交叉(动量5) + 轮动持有前2名
bt = xg_more_mom_backtrader(
start_date='20250101',
end_date='20261231',
stock_list=['159915.SZ', '510300.SH', '518880.SH', '511010.SH'],
cash=500000,
mom_models='动量5', # 短/长均线交叉
short_ma=5,
long_ma=20,
mom_type='百分比',
mom_value=0.5,
max_rank=1,
min_rank=2, # 持有动量前2名
)
bt.run_backtest()
print(bt.generate_report())
9.3 示例3:开启大盘择时 + 动量区间过滤 + 开平仓条件 + 止盈
bt = xg_more_mom_backtrader(
start_date='20250101',
end_date='20261231',
stock_list=['159915.SZ', '513100.SH', '518880.SH'],
index_stock='000300.SH',
cash=200000,
mom_models='动量2', # 现价/N日前价涨幅
period=20,
mom_type='金额',
mom_value=10000, # 每次固定买1万
max_rank=1, min_rank=1,
# 大盘择时:沪深300在20日线上方才允许买入
enable_index_timing=True,
index_mean_line=20,
index_not_trader=['513100.SH', '518880.SH'],
index_condition_type='大于均线',
# 动量区间过滤:仅留分数 0~5
enable_mom_filter=True,
mini_value=0, max_value=5,
# 开仓条件:近20日涨幅超1%
enable_buy_condition=True,
buy_condition_type='涨幅', buy_period=20, buy_period_ratio=0.01,
# 平仓条件:近20日跌幅超3%
enable_sell_condition=True,
sell_condition_type='跌幅', sell_period=20, sell_period_ratio=-0.03,
# 止盈:当日涨3%卖1000元
sell_zdf=0.03, sell_amount=1000,
interval=1,
)
bt.run_backtest()
print(bt.generate_report())
9.4 示例4:自定义标的数据注入 + 综合动量回测
bt = xg_more_mom_backtrader(
start_date='20250101',
end_date='20261231',
stock_list=['CUSTOM_A', 'CUSTOM_B', 'CUSTOM_C'],
cash=100000,
mom_models='动量1',
use_custom_data=True,
)
# 注入自定义数据(DataFrame 需含 date/open/high/low/close/volume)
# bt.add_stock_data_from_csv('CUSTOM_A', 'data/a.csv')
# bt.add_stock_data_from_dataframe('CUSTOM_B', pd.read_parquet('data/b.parquet'))
bt.run_backtest()
# 落盘分类结果(Excel+JSON)到 策略模型/综合动量策略/{user}/
bt.save_backtrader_data(user="自定义动量")
# 如需自定义 JSON 落盘,可对 get_all_data()/get_backtrader_data() 手动 dump
import json
with open('result/mom.json', 'w', encoding='utf-8') as f:
json.dump(bt.get_all_data(), f, ensure_ascii=False, indent=2)
9.5 示例5:注入自定义指数并开启大盘择时
import pandas as pd
bt = xg_more_mom_backtrader(
start_date='20250101',
end_date='20261231',
stock_list=['159915.SZ', '513100.SH'],
index_stock='CUSTOM_INDEX',
cash=100000,
enable_index_timing=True,
index_mean_line=20,
use_custom_index_data=True,
)
index_df = pd.read_csv('data/my_index.csv') # 需 date/close 列
bt.add_index_data_from_dataframe(index_df)
print("是否有指数:", bt.has_index_data(), bt.get_index_data_info())
bt.run_backtest()
print(bt.generate_report())
9.6 示例6:提取结果做二次分析 + 画净值/回撤图
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"交易笔数: {trades['statistics']['total_trades']}")
import matplotlib.pyplot as plt
dates = curve['dates']
plt.figure(figsize=(11, 5))
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.title('综合动量策略净值 vs 基准')
plt.xticks(rotation=45); plt.tight_layout(); plt.show()
十、自定义数据详解
10.1 标的注入后系统自动做的事
add_stock_data_from_* 会调用 _normalize_dataframe:识别日期列/索引→排序→补齐 close/high/low/open/volume→补 zdf→(有 preClose 时)补 return/adj_factor。注入成功后 use_custom_data 应设为 True(多数方法自动切换)。
10.2 指数注入说明
add_index_data_from_* 只需 date+close(自动剔除 close≤0、识别日期列);注入后 use_custom_index_data=True 时优先作为指数基准。
10.3 注入成功判定与验证
print("标的自定义键:", bt.get_custom_data_keys()) # 含代码即成功
print("has_custom_data:", bt.has_custom_data('CUSTOM_A'))
print("指数:", bt.has_index_data(), bt.get_index_data_info())
十一、常见问题与 FAQ
Q1: 提示「没有共同交易日」或回测终止?
系统以全部标的数据的共同交易日为回测交易日(calculate_benchmark_curve 也用共同交集)。检查各标的数据日期范围是否重叠足够;缺某标的长期停牌/无数据会显著压缩回测区间。_load_single_stock 还要求每只 >50 行,不足则被丢弃。
Q2: 为什么几乎不交易 / 一直空仓?
可能原因:大盘择时开启且指数不满足条件(所有非排除标的无法建仓);或开仓条件过严(无标的通过 check_buy_condition);或动量数据不足全部返回 0 且过滤后为空。可关闭对应开关或放宽条件。
Q3: 目标排名与实际持仓为何短暂不一致?
轮动在每 interval 日检查;且买入需过 check_buy_condition、卖出会排除 index_not_trader。若某标的排名靠前但未触发买入条件,可能不立即持有,需在后续检查日确认。
Q4: 动量分数含义与量纲?
不同模型量纲不同:动量1 是「年化回归斜率×R²」(量纲大),动量2~6 是「涨跌幅/偏离度」(≈0 附近)。若开动量区间过滤,建议按所选模型单独校准 mini_value/max_value;动量区间过滤仅对动量2~6 这类“比率”分数更直观。
Q5: 自定义数据注入后仍走本地目录?
确认注入返回 True,用 get_custom_data_keys() 验证,并把 use_custom_data=True(或确认注入方法自动切换)。指数同理需 use_custom_index_data=True。
Q6: 收益率为 0 或异常?
检查价格列是否全为正、cash 初始值、stock_list 是否都有数据、成交撮合是否因手续费过高导致 0 份、以及动量是否因历史不足返回 0 导致从未买入。
Q7: index_not_trader 的作用?
列出后这些标的不受大盘择时清仓与平仓条件影响,可常驻持有(如纳指/黄金 ETF 与大盘相关度低)。但轮动卖出/买入仍会正常作用——即若它们排名跌出目标区间,仍会被轮动卖出。
Q8: 如何只做“纯动量轮动”不叠加风控?
保持 enable_index_timing / enable_mom_filter / enable_buy_condition / enable_sell_condition 全为 False,仅由 mom_models + mom_type + max_rank/min_rank + interval 驱动即可。止盈为常开,若不需要可把 sell_zdf 设很大(如 99)使其几乎不触发。
十二、高级技巧与二次开发
12.1 参数调优建议
| 参数 | 建议范围 | 说明 |
|---|---|---|
mom_daily(动量1) |
20-60 | 回归窗口,越长越稳但对变化越滞后 |
period |
10-60 | 动量回看周期;短=灵敏、长=平滑 |
short_ma / long_ma |
3-10 / 20-60 | 均线交叉周期对 |
max_rank/min_rank |
1~池数 | 排名区间越宽越分散 |
mom_type/mom_value |
按仓位偏好 | 百分比最直观(0.3~0.8);金额/数量适合固定敞口 |
interval |
1-10 | 越大调仓越不频繁、手续费越低 |
sell_zdf/sell_amount |
0.02-0.05 / 金额 | 止盈越灵敏落袋越频繁 |
12.2 综合动量策略设计经验
- 动量模型与周期要匹配标的特性:ETF 适合均线/价格动量,波动大品种可适当缩短回看。
- 条件过滤是风控而非收益来源:开平仓/止盈主要用于规避极端行情,参数不宜过紧以免频繁空仓。
index_not_trader巧妙混搭:可把与大盘相关性低的品种设为常驻,兼顾“择时防守+常驻进攻”。- 排名轮动天然自带再平衡:随动量排名变化自动“卖弱买强”,无需额外调仓逻辑。
12.3 自定义第 7 种动量模型
继承 xg_more_mom_backtrader,新增打分方法并在 calculate_momentum 的分发字典里注册:
class MyMomentum(xg_more_mom_backtrader):
def calculate_momentum_score_7(self, df):
# 自定义动量:例如 RSI 动量
if len(df) < self.period:
return 0
delta = df['close'].diff()
up = delta.clip(lower=0).rolling(self.period).mean()
down = (-delta.clip(upper=0)).rolling(self.period).mean()
rs = up / down.replace(0, 1e-9)
rsi = 100 - 100 / (1 + rs)
return float(rsi.iloc[-1] - 50) / 50.0 # 归一化到 -1~1 附近
def calculate_momentum(self, df):
func = getattr(self, f"calculate_momentum_score_7", None) if self.mom_models == '动量7' else None
if func is None:
return super().calculate_momentum(df)
return func(df)
> 提示:在 __init__ 里把 mom_models 校验扩充或直接复写 calculate_momentum 分发字典即可启用 '动量7'。
12.4 自定义交易/成本规则
重写 calculate_buy_shares(改买入逻辑)、calculate_sell_shares_all / calculate_sell_shares_by_amount(改卖出逻辑)、或 calculate_performance_metrics(加自定义绩效指标)。
十三、技术说明与提示
- 整体资金池:
cash不均分到各标的,全池共用账户现金,按排名目标分配敞口。 - 仅依赖 pandas/numpy:动量回归用
np.polyfit,无需scipy(区别于均值方差系统)。 - 无模块级函数:全部逻辑在类内,69 个方法覆盖动量/条件/撮合/绩效全流程。
- 交易类型全集:
大盘择时清仓 / 轮动卖出 / 轮动买入 / 条件平仓 / 止盈卖出,每笔带reason。 - 多条件可组合:大盘择时 / 动量区间 / 开平仓 / 止盈各自开关,自由叠加。
- 指数多格式回退:指数本地支持 parquet/xlsx/csv/json;标的本地支持三条 parquet 路径回退。
- 数据门槛:单标的需 >50 行,动量/条件各自还有最小周期要求(不足返回 0 或恒通过/不触发)。
- 复权:当前固定
adj_type='none',如需复权可二次开发adjust_price。
小果综合动量回测系统,让动量轮动策略落地更高效、更透明。如需源码、更多因子库或技术支持,欢迎联系小果(微信:xg_quant)。