小果网格策略回测系统专家
一、系统概述
使用教程:https://gitcode.com/qq_50882340/xg_gd_backtrader
1.1 系统定位
小果网格策略回测系统(xg_gd_backtrader)是一套专为网格交易(Grid Trading)+ 做T增厚收益设计的专业级量化回测框架。它以「按比例初始建仓」建立底仓,随后「价格每触及一个网格档位就低买高卖」在震荡行情中反复收割价差,并叠加「当日大涨止盈卖出 / 当日大跌止损买入」的做T规则在盘中降低成本。它以每只标的独立网格参数逐日撮合,输出完整绩效。
与其它策略系统最大的不同在于:网格参数是按标的逐一下发的一组列表(gd_*_list 与 stock_list 一一对应),且支持百分比、差值、ATR 三种步长口径与金额、百分比、数量三种下单口径,可高度定制每只标的的网格形态。
1.2 核心策略逻辑(每个交易日)
系统按每只标的独立账户逐日决策,每标资金池 = 总资金 ÷ 标的数:
① 初始建仓:首个交易日按 init_position_ratio 比例 × 总资金 买入底仓(占总资金 X%)
② 网格卖出:当前价 ≥ 卖出触发价 sell_trigger 且该标的卖出步长已启用 → 卖出一档 gd_value 金额(高抛)
③ 网格买入:当前价 ≤ 买入触发价 buy_trigger 且该标的买入步长已启用 → 买入一档 gd_value 金额(低吸)
④ 做T止盈:当前价 ≥ 上次做T价 × (1 + sell_zdf) → 卖出 trade_value 金额(当日涨幅止盈)
⑤ 做T止损:当前价 ≤ 上次做T价 × (1 + buy_zdf) → 买入 trade_value 金额(当日跌幅吸筹)
⑥ 全部成交按 100 份整手、先扣手续费;随后记录现金/持仓/网格中枢/触发价/累计成本/浮盈亏
⑦ 组合级汇总:把各标的在共同交易日的现金、持仓、市值相加,叠加基准曲线输出净值
> 网格买卖与做T互不冲突:网格按「与中枢价相差一个步长」触发,做T按「与上次做T成交价相差止盈/止损阈值」触发;每笔触发后都把中枢价/上次做T价重置为当前成交价。
1.3 网格触发机制(关键概念)
| 概念 | 含义 | 说明 |
|---|---|---|
grid_price 网格中枢 |
最近一次网格成交价 | 每触发一笔买卖即重置为当前成交价 |
buy_trigger 买入触发价 |
grid_price − 买入步长 |
价格跌至此触发网格买入 |
sell_trigger 卖出触发价 |
grid_price + 卖出步长 |
价格涨至此触发网格卖出 |
步长类型 gd_bc_type |
百分比 / 差值 / ATR |
决定网格档位间距口径 |
下单类型 gd_type |
金额 / 百分比 / 数量 |
决定每档买入/卖出的金额口径 |
> ⚠️ 参数符号约定(源码关键):gd_buy_bc_list 存的是卖出步长的正数(触发条件 ≥ 0,价格涨超卖出触发价就卖出);gd_sell_bc_list 存的是买入步长的负数(触发条件 < 0,价格跌破买入触发价就买入)。calculate_grid_step 内部:buy_step = abs(sell_bc)、sell_step = buy_bc。
1.4 三种步长类型对照
| 步长类型 | 档位间距口径 | 网格价格计算 | 适用 |
|---|---|---|---|
百分比 |
按价格比例 | 买=中枢×(1−步长)、卖=中枢×(1+步长) | 价格波动剧烈、用比例定档 |
差值 |
按绝对价格差 | 买=中枢−步长、卖=中枢+步长 | 价格稳定、用固定价差定档 |
ATR |
按 ATR 波动倍数 | 买=中枢−倍×ATR、卖=中枢+倍×ATR | 波动率自适应、动态定档 |
1.5 设计哲学
- 解耦与复用:数据加载、网格中枢、买卖触发、交易撮合、绩效评估各环节解耦,可独立替换。
- 透明可解释:每日记录的
grid_price/buy_trigger/sell_trigger与每笔action(触发原因)可完整还原决策过程。 - 灵活可扩展:逐标的网格参数列表、三种步长/下单口径、标的与指数双重自定义数据源、可继承重写做个性化网格。
二、安装与运行环境
2.1 安装包
教程 https://gitcode.com/qq_50882340/xg_gd_backtrader
2.2 依赖
pandasnumpypyarrow(读取本地 parquet 历史数据用)openpyxl(导出 Excel 用)
2.3 包结构
推荐以「包」方式导入:
xg_gd_backtrader/
├── __init__.py # 空文件即可
└── xg_gd_backtrader.py # 主模块(网格回测类)
2.4 导入方式
from xg_gd_backtrader.xg_gd_backtrader import xg_gd_backtrader
2.5 数据目录要求(使用内置数据源时)
系统默认从脚本同级的 data/ 目录读取本地数据:
data/
├── 历史数据/
│ ├── 513100.SH.parquet # 标的日线数据
│ └── ...
└── 指数数据/
├── 000300.SH.parquet # 指数数据(parquet 优先)
└── ...
> 若不想准备本地数据,可用自定义数据注入(标的 + 指数各自独立注入,见「十、自定义数据详解」),系统自动切换到内存数据源。
2.6 数据字段要求
必须字段:
| 字段名 | 说明 | 是否必须 |
|---|---|---|
date |
日期 | ✅ 必须 |
close |
收盘价 | ✅ 必须 |
open |
开盘价 | ✅ 必须 |
high |
最高价 | ✅ 必须 |
low |
最低价 | ✅ 必须 |
volume |
成交量 | ✅ 必须 |
推荐字段:
| 字段名 | 说明 | 是否必须 |
|---|---|---|
preClose |
前收盘价 | ⚠️ 推荐(用于复权,缺失时自动跳过复权) |
zdf |
涨跌幅 | ⚠️ 推荐(不存在时自动用 close.pct_change() 计算) |
amount |
成交金额 | ⚠️ 可选 |
证券代码 |
股票代码 | ⚠️ 可选(缺失时自动添加) |
证券名称 |
股票名称 | ⚠️ 可选 |
三、初始化参数完整参考
> 以下为 __init__ 方法全部参数详细说明。网格列表参数(gd_*_list)与 stock_list 逐一一对应。
3.1 时间与标的 (Time & Universe)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
start_date |
str |
'20250701' |
回测起始日期,格式 YYYYMMDD |
end_date |
str |
'20500101' |
回测结束日期,格式 YYYYMMDD |
stock_list |
list |
['513100.SH','513500.SH'] |
候选标的池,元素需带交易所后缀(.SZ/.SH) |
index_stock |
str |
'000300.SH' |
对比指数代码(仅用于输出指数数据;注入自定义指数后以注入数据为准) |
3.2 资金与费用 (Capital & Cost)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cash |
float |
100000 |
初始总资金(元),回测中每只标的资金池 = cash ÷ 标的数 |
comm |
float |
0.0001 |
手续费率(万1 = 0.0001),买卖双向收取 |
3.3 网格节奏与做T风控(全局配置)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
gd_interval |
int |
1 |
网格检查间隔(交易日数),源码当前以日频为粒度,默认每个交易日都检查 |
sell_zdf |
float |
0.03 |
做T止盈阈值:当前价 ≥ 上次做T价 ×(1+sell_zdf) 触发卖出 trade_value 金额(0.03=涨3%) |
buy_zdf |
float |
-0.03 |
做T止损阈值:当前价 ≤ 上次做T价 ×(1+buy_zdf) 触发买入 trade_value 金额(-0.03=跌3%) |
trade_value |
float |
1000 |
做T单次交易金额(元):每次做T止盈/止损动用多少钱 |
3.4 网格列表参数(与 stock_list 一一对应)
> 下列 *_list 列表长度必须与 stock_list 完全一致,否则构造时 _validate_list_params() 会抛 ValueError。每个列表的第 i 个元素对应 stock_list[i]。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
gd_bc_type_list |
list[str] |
['百分比','百分比'] |
每只标的网格步长类型:'百分比' / '差值' / 'ATR' |
gd_buy_bc_list |
list[float] |
[0.03,0.02] |
每只标的卖出步长(正数,价格涨超该档触发卖出) |
gd_sell_bc_list |
list[float] |
[-0.02,-0.015] |
每只标的买入步长(负数,价格跌破该档触发买入) |
gd_atr_ratio_list |
list[float] |
[2.0,2.0] |
每只标的 ATR 倍数(gd_bc_type='ATR' 时生效) |
gd_atr_period_list |
list[int] |
[14,14] |
每只标的 ATR 周期(gd_bc_type='ATR' 时生效) |
gd_type_list |
list[str] |
['金额','金额'] |
每只标的下单类型:'金额' / '百分比' / '数量' |
gd_value_list |
list[float] |
[1000,1500] |
每只标的下单值,含义随 gd_type 变化 |
init_position_ratio_list |
list[float] |
[0.1,0.15] |
每只标的初始建仓比例(占总资金比例),和为 1 时满仓,超过 1 会告警 |
3.5 运行与数据源 (Runtime & Data Source)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
max_workers |
int |
4 |
多线程加载数据时的线程数 |
use_custom_data |
bool |
False |
是否使用自定义标的数据源(注入成功后自动置 True) |
use_custom_index_data |
bool |
False |
是否使用自定义指数数据源(注入成功后自动置 True) |
3.6 内部固定参数(构造时自动设置,一般不修改)
| 参数 | 值 | 说明 |
|---|---|---|
adj_type |
'none' |
复权方式(源码当前固定为 none,即不做前复权/后复权) |
min_shares |
100 |
最小交易单位(ETF 一手 = 100 份) |
share_multiple |
100 |
交易数量的整数倍(每次按 100 份整数倍下单) |
verbose |
True |
是否打印详细过程日志 |
3.7 网格步长/下单类型匹配表
步长类型与网格价格:
gd_bc_type |
gd_buy_bc/gd_sell_bc 含义 |
网格触发价计算 |
|---|---|---|
'百分比' |
价格比例(如卖 0.03=3%、买 -0.02=跌2%) | 买=中枢×(1−abs(sell_bc))、卖=中枢×(1+buy_bc) |
'差值' |
绝对价格差 | 买=中枢−abs(sell_bc)、卖=中枢+buy_bc |
'ATR' |
步长 = ATR倍数 × ATR值 | 买=中枢−步长、卖=中枢+步长 |
下单类型与每档金额:
gd_type |
gd_value 含义 |
每档买卖金额逻辑 |
|---|---|---|
'金额' |
固定金额(元) | 每档投入 = gd_value |
'百分比' |
资金池的比例(%数) | 每档投入 = (cash÷标的数) × gd_value ÷ 100 |
'数量' |
固定份额(份) | 每档投入 = gd_value 份 × 当前价(自动按 100 份整手) |
> 例:gd_buy_bc_list=[0.03](卖出档 3%)、gd_sell_bc_list=[-0.02](买入档 2%)、gd_type='金额', gd_value=1000 → 中枢价 1.5 时,价格涨到 1.5×1.03 卖出 1000 元、跌到 1.5×0.98 买入 1000 元,成交后中枢价重置为新价格,依次递推滚动。
四、数据加载与处理详解
4.1 标的(股票/ETF)数据来源优先级
get_stock_data(stock_code) 取数顺序:
- 自定义标的源:
use_custom_data=True且该代码已注入 → 从内存_custom_data_cache读取; - 本地 parquet:读取
data/历史数据/{code}.parquet。
两种路径均执行统一清洗:截取 [start_date, end_date] 区间 → 日期升序 → 剔除 close<=0 或 open<=0 行 → adjust_price(当前为 none 原样返回)→ 重算 zdf = close.pct_change() → 按该标的 gd_atr_period 计算 atr/tr/atr_pct 列。
4.2 多线程加载
get_all_stock_data() 用 ThreadPoolExecutor(线程数 = max_workers)并发加载全部标的,打印每只标的的成功/失败、数据范围与价格范围,返回 {code: DataFrame}。
4.3 指数数据来源
get_index_data() 依次尝试以下来源,命中即返回:
| 优先级 | 来源 | 说明 |
|---|---|---|
| ① | 自定义指数数据 | use_custom_index_data=True 且有注入数据时优先使用 |
| ② | data/指数数据/{index_stock}.parquet |
本地 parquet |
> 指数数据只用于输出展示/留档(导出目录的「指数数据」),不参与策略基准对比;策略对照基准是「等权持有不动」曲线(calculate_benchmark_curve,取各标的共同交易日等权归一化平均)。
五、网格与做T信号生成详解
5.1 ATR 计算(calculate_atr)
每个标的在数据加载时自动追加 tr(真实波幅)、atr(Wilder 平滑平均真实波幅)、atr_pct(ATR÷close×100)三列,供 gd_bc_type='ATR' 的动态网格档位使用。
5.2 网格档位(calculate_grid_prices)
calculate_grid_prices(current_price, grid_price, atr_value, stock) 依据步长类型计算买入/卖出触发价:
| 步长类型 | 返回 (buy_price, sell_price) |
|---|---|
| 百分比 | grid_price×(1−buy_step)、grid_price×(1+sell_step) |
| 差值 | grid_price−buy_step、grid_price+sell_step |
| ATR | grid_price−buy_step、grid_price+sell_step,其中步长 = atr_ratio×atr |
5.3 触发与重置
每日循环内(每只标的):
网格卖出:若卖出步长已启用(gd_buy_bc>0) 且 current_price ≥ sell_trigger 且持仓>0 → 卖出一档
网格买入:若买入步长已启用(gd_sell_bc<0) 且 current_price ≤ buy_trigger 且现金足够 → 买入一档
(非ATR:卖出档位用 gd_buy_bc、买入档位用 gd_sell_bc 判断;ATR:两端用同一步长)
做T止盈:若 sell_zdf>0 且 current_price ≥ dt_last_price×(1+sell_zdf) → 卖出 trade_value
做T止损:若 buy_zdf<0 且 current_price ≤ dt_last_price×(1+buy_zdf) → 买入 trade_value
每笔成交后重置:grid_price = 当前价(网格)或 dt_last_price = 当前价(做T),并重算下一档触发价。买入需现金 ≥ 应投金额,卖出需持仓 > 0,最终以「实际能成交」为准。
六、交易执行详解
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)
calculate_sell_shares(shares, price, amount) -> (sell_shares, actual_amount)
- 按卖出金额折算股数(手续费从卖出金额中扣):
sell_shares = floor(amount ÷ price ÷ 100) × 100; - 折算不足 100 但按金额够一手时补足 100;卖出份额超过持仓则全部卖出;
- 返回
(卖出份数, 实收金额=市值−手续费)。
6.3 全部卖出(calculate_sell_shares_all)
calculate_sell_shares_all(shares, price) -> (sell_shares, actual_amount)
- 全部持仓按 100 份整手卖出;取整为 0 但有持仓时按实际持仓卖出;实收金额=市值−手续费。
6.4 日交易记录字段(trade_log)
| 字段 | 类型 | 说明 |
|---|---|---|
date |
datetime | 交易日期 |
stock |
str | 标的代码 |
type |
str | 交易类型:初始建仓 / 网格买入 / 网格卖出 / ATR网格买入 / ATR网格卖出 / 做T止盈卖出 / 做T止损买入 |
price |
float | 成交价格 |
amount |
float | 成交金额(买入为含手续费成本;卖出为实收金额) |
shares |
int | 成交份额 |
cash_after |
float | 成交后现金 |
holdings_after |
float | 成交后持仓 |
reason |
str | 交易原因,如 上涨触发网格卖出、下跌触发网格买入、止盈卖出、初始建仓 |
zdf |
float | 当日涨跌幅 |
commission |
float | 该笔手续费 |
七、完整函数清单
> 框架类 xg_gd_backtrader 共 65 个方法,以下按功能模块列出,便于二次开发和深度集成。
7.1 构造与配置
| 方法 | 说明 |
|---|---|
__init__(...) |
构造网格回测实例(含逐标的网格参数列表与做T规则) |
_validate_list_params() |
校验各 *_list 长度与 stock_list 一致,并生成 self.stock_params、打印各标的网格配置与初始建仓资金 |
get_config() -> dict |
返回当前全部配置参数(含网格列表参数、已注入标的 key、指数注入状态) |
7.2 数据加载与处理
| 方法 | 说明 |
|---|---|
get_stock_data(stock_code) |
加载单只标的(自定义源优先,其次本地 parquet),返回含 atr/tr/atr_pct 的清洗后 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、算复权因子 |
_normalize_index_dataframe(df) |
标准化注入的指数 DataFrame(含 date+close,自动识别日期列/索引) |
calculate_atr(df, period) |
计算 ATR:追加 tr / atr(Wilder 平滑)/ atr_pct 三列 |
adjust_price(df) |
价格复权处理(当前 adj_type='none' 时原样返回) |
calculate_benchmark_curve(all_data) |
计算等权持有不动基准净值曲线 |
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 注入(自动尝试多编码) |
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 自定义指数注入(7 种接口)
| 方法 | 说明 |
|---|---|
add_index_data_from_dataframe(df) -> bool |
从 DataFrame 加载指数数据 |
add_index_data_from_excel(file_path, sheet_name=0, **kwargs) -> bool |
从 Excel 加载指数数据 |
add_index_data_from_csv(file_path, **kwargs) -> bool |
从 CSV 加载指数数据(自动尝试多编码) |
add_index_data_from_json(file_path, orient='records', **kwargs) -> bool |
从 JSON 加载指数数据 |
add_index_data_from_dict(data) -> bool |
从字典加载指数数据(格式 {'date': [...], 'close': [...]}) |
add_index_data_from_parquet(file_path, columns=None, **kwargs) -> bool |
从 Parquet 加载指数数据 |
add_index_data_from_bytes(file_bytes, file_type='csv', **kwargs) -> bool |
从字节数据加载指数数据(csv/excel/json,适合上传场景) |
7.6 指数数据管理
| 方法 | 说明 |
|---|---|
clear_index_data() |
清空自定义指数数据(恢复读取本地指数文件) |
has_index_data() -> bool |
检查是否已有自定义指数数据 |
get_index_data_info() -> dict |
获取指数数据信息:{'has_data': bool, 'rows': int, 'start_date': str, 'end_date': str, 'columns': list}(无数据时返回 {'has_data': False}) |
7.7 网格核心计算
| 方法 | 说明 |
|---|---|
calculate_grid_step(current_price, atr_value, stock) |
按该标的步长类型计算买入/卖出步长 {'buy_step', 'sell_step'} |
calculate_grid_prices(current_price, grid_price, atr_value, stock) |
计算买入/卖出触发价 (buy_price, sell_price) |
calculate_order_amount(total_cash, current_price, current_holdings, stock) |
按该标的下单类型计算每档应投金额 |
7.8 交易撮合计算
| 方法 | 说明 |
|---|---|
calculate_buy_shares(amount, price) |
买入份额与成本计算(100 份整数倍、先扣佣金) |
calculate_sell_shares(shares, price, amount) |
按金额卖出份额与实收计算(超持仓则全卖) |
calculate_sell_shares_all(shares, price) |
全部卖出计算(含尾仓处理) |
7.9 回测执行
| 方法 | 说明 |
|---|---|
vectorized_backtest() |
向量化回测主引擎(多标的独立跑通 + 组合汇总) |
run_backtest() |
回测主入口,等价于 vectorized_backtest() |
7.10 绩效与数据生成
| 方法 | 说明 |
|---|---|
calculate_performance_metrics(df, df_index) -> dict |
计算核心绩效指标 |
calculate_annual_performance(portfolio_df) -> dict |
计算逐年收益统计 |
generate_equity_curve_data() |
生成净值曲线 dict |
generate_trade_data() |
生成成交数据 dict |
generate_position_data() |
生成持仓数据 dict |
generate_account_data() |
生成账户数据 dict |
7.11 数据获取接口
| 方法 | 说明 |
|---|---|
get_performance_metrics() |
获取绩效指标(未回测则自动回测) |
get_equity_curve_data() |
获取净值曲线数据 |
get_annual_performance() |
获取年度收益统计(dict) |
get_annual_performance_df() |
获取年度收益统计(DataFrame,带 year 列) |
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.12 结果保存
| 方法 | 说明 |
|---|---|
save_backtrader_data(user='test') |
分类保存为 Excel + JSON 到 策略模型/网格策略/{user}/ |
save_to_json(filename=None, include_raw_data=False) |
保存结果为单个 JSON 文件(默认文件名含 wangge_backtest_ 前缀;include_raw_data=True 时含指数原始数据) |
7.13 报告生成
| 方法 | 说明 |
|---|---|
generate_report() -> str |
生成格式化文本回测报告 |
7.14 内部工具方法(供二次开发参考)
| 方法 | 说明 |
|---|---|
_convert_to_serializable(obj) |
把 numpy/pandas/Timestamp/datetime 等递归转为 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_invested |
网格买入累计投入额 | 元(仅统计 网格买入) |
t_trades |
网格交易次数 | int(含网格买入+卖出,含 ATR 网格) |
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 |
超额收益百分比 | 数值 |
8.2 年度收益统计字段(annual_performance)
| 字段 | 说明 |
|---|---|
start_date / end_date / total_days |
当年统计区间与交易日数 |
total_cash / final_value / total_pnl |
当年末总资产与区间盈亏 |
total_invested |
当年网格买入投入额(当年新增成本口径) |
t_trades |
当年网格交易次数 |
total_commission / remaining_cash / final_holdings |
当年手续费 / 期末现金 / 期末持仓 |
total_return / annual_return |
当年收益率(区间口径) |
max_drawdown / max_drawdown_date |
当年最大回撤 |
sharpe_ratio / sortino_ratio / calmar_ratio |
当年风险调整收益 |
win_rate / volatility |
当年日胜率 / 波动率 |
benchmark_* |
当年基准起止净值、总收益、最大回撤 |
excess_return / excess_return_pct |
相对基准超额收益 |
8.3 净值曲线字段(equity_curve_data)
| 字段 | 说明 |
|---|---|
dates |
日期字符串列表 |
total_value |
每日总资产 |
cumulative_pnl |
每日累计盈亏 |
cash |
每日现金 |
holdings |
每日持仓份额 |
investment_cost |
每日累计投入成本(含手续费) |
net_value |
策略净值(总资产/初始资金) |
drawdown |
回撤百分比(%) |
position_ratio |
持仓占比(近似,份数口径) |
total_return |
每日累计收益率 |
daily_return |
每日收益率 |
benchmark_net_value / benchmark_return |
基准净值 / 基准日收益 |
statistics |
起点/终点/总天数/初始资金/期末总资产/最高最低资产/总收益/最大回撤及日期摘要 |
benchmark_statistics |
基准起止净值、总收益、最大回撤摘要 |
8.4 成交统计(trade_data['statistics'])
total_trades、buy_trades、sell_trades、total_buy_amount、total_sell_amount、net_invested(净投入=买-卖)、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。每日明细还含 investment_cost、daily_pnl、cumulative_pnl、total_return(百分数口径)、stock_details(逐标的现值/现金/持仓/网格中枢/收益率)。
8.6 账户统计(account_data['statistics'])
起始/结束日期、总天数、初始资金、期末总资产/现金/持仓、累计投入、总盈亏、总收益率(百分数)、最高/最低总资产与净值、波动率、基准期末净值与总收益。
8.7 各标的独立结果(get_stock_results)
每只标的含完整数组:cash/holdings/total_value/daily_pnl/cumulative_pnl/investment_cost/grid_price/buy_trigger/sell_trigger/dt_last_price/is_grid_triggered(每日序列)、trade_*(成交序列)、daily_records(每日明细含 action 触发原因)、final_holdings/final_cash/final_value/final_price/final_return/init_position_ratio/init_cash。
8.8 文本报告内容(generate_report())
策略参数(标的/指数/复权/区间/资金/网格间隔/手续费/数据源)→ 做T参数(止盈/止损/金额)→ 各标的盈亏明细 → 汇总持仓 → 收益指标(总/年化/盈亏/波动/手续费)→ 基准对比(含超额)→ 风险指标(回撤/夏普/索提诺/卡尔玛)→ 交易统计(总次数/网格次数/做T次数/胜率)→ 年度收益统计表。
8.9 导出目录结构
策略模型/网格策略/{user}/
├── 指数数据/ 指数数据.xlsx + .json
├── 成交数据/ 成交数据.xlsx + .json
├── 持股数据/ 持股数据.xlsx + .json
├── 账户数据/ 账户数据.xlsx + .json
├── 绩效指标/ 绩效指标.xlsx + .json
├── 净值曲线/ 净值曲线.xlsx + .json
├── 策略参数/ 策略参数.xlsx + .json
└── 年度收益统计/ 年度收益统计.xlsx + .json
九、完整实战示例
9.1 示例1:经典百分比网格 + 做T(本地数据)
from xg_gd_backtrader.xg_gd_backtrader import xg_gd_backtrader
bt = xg_gd_backtrader(
start_date='20250701',
end_date='20261201',
stock_list=['513100.SH', '513500.SH'], # 纳指ETF / 标普500ETF
index_stock='000300.SH',
cash=100000,
comm=0.0001,
gd_interval=1,
gd_bc_type_list=['百分比', '百分比'],
gd_buy_bc_list=[0.03, 0.02], # 卖出档 3% / 2%
gd_sell_bc_list=[-0.02, -0.015], # 买入档 2% / 1.5%
gd_type_list=['金额', '金额'],
gd_value_list=[1000, 1500], # 每档网格投入
init_position_ratio_list=[0.1, 0.1], # 初始建仓 10% / 10%
sell_zdf=0.03, # 涨 3% 做T止盈卖出
buy_zdf=-0.02, # 跌 2% 做T止损买入
trade_value=1000, # 每次做T 1000 元
max_workers=8,
)
bt.run_backtest()
print(bt.generate_report())
bt.save_backtrader_data(user='经典网格')
9.2 示例2:纯网格 + 关闭做T
bt = xg_gd_backtrader(
start_date='20240101',
end_date='20261201',
stock_list=['159915.SZ', '510300.SH'],
cash=200000,
comm=0.0001,
gd_bc_type_list=['百分比', '百分比'],
gd_buy_bc_list=[0.04, 0.04], # 卖出档 4%
gd_sell_bc_list=[-0.03, -0.03], # 买入档 3%
gd_type_list=['金额', '金额'],
gd_value_list=[2000, 2000],
init_position_ratio_list=[0.2, 0.2],
sell_zdf=0.99, # 极大值 → 关闭做T止盈
buy_zdf=-0.99, # 极大值 → 关闭做T止损
trade_value=1000,
max_workers=4,
)
bt.run_backtest()
print(bt.generate_report())
# 网格交易次数(做T已关闭,仅网格)
print("网格交易次数:", bt.get_performance_metrics().get('t_trades', 0))
9.3 示例3:ATR 动态网格 + 注入自定义标的数据
bt = xg_gd_backtrader(
start_date='20230101',
end_date='20261201',
stock_list=['513100.SH', '513500.SH'],
cash=200000,
comm=0.0001,
gd_interval=1,
gd_bc_type_list=['ATR', 'ATR'], # 用 ATR 动态定档
gd_atr_ratio_list=[2.0, 2.0], # 2 倍 ATR 为步长
gd_atr_period_list=[14, 14], # 14 日 ATR
gd_buy_bc_list=[0.03, 0.03], # 非 ATR 时用,ATR 模式下忽略
gd_sell_bc_list=[-0.02, -0.02], # 非 ATR 时用,ATR 模式下忽略
gd_type_list=['金额', '金额'],
gd_value_list=[2000, 2000],
init_position_ratio_list=[0.15, 0.15],
use_custom_data=True, # 显式声明使用自定义数据
)
# 不同格式注入
bt.add_stock_data_from_csv('513100.SH', 'data/513100.csv')
bt.add_stock_data_from_dataframe('513500.SH', df_513500) # 内存 DataFrame
print("已注入:", bt.get_custom_data_keys())
bt.run_backtest()
print(bt.generate_report())
bt.save_to_json('result/grid_backtest.json')
9.4 示例4:注入自定义指数数据(自定义对比指数)
import pandas as pd
# 构造或读取指数 DataFrame(含 date/close 即可)
index_df = pd.read_csv('data/index_000300.csv')
bt = xg_gd_backtrader(
start_date='20250701',
end_date='20261201',
stock_list=['513100.SH', '513500.SH'],
index_stock='CUSTOM_INDEX', # 占位代码(不影响,指数以注入为准)
cash=100000,
use_custom_index_data=True, # 关键:启用自定义指数
)
bt.add_index_data_from_dataframe(index_df) # 或 add_index_data_from_csv('xxx.csv')
print("指数已注入:", bt.has_index_data())
print(bt.get_index_data_info()) # {'has_data': True, 'rows': N, 'start_date':..., 'columns': [...]}
bt.run_backtest()
print(bt.generate_report())
9.5 示例5:提取结果做二次分析 + 画净值/回撤曲线
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['total_invested']:,.2f}")
print(f"网格交易次数: {metrics['t_trades']}")
print(f"总手续费: ¥{metrics['total_commission']:,.2f}")
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('网格+做T 策略净值曲线'); 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.6 示例6:按标的查看网格中枢与回测明细
bt.run_backtest()
# 各标的独立结果(含每日网格触发价)
for stock, res in bt.get_stock_results().items():
print(f"{stock}: 最终资产¥{res['final_value']:,.2f}, "
f"最终持仓{res['final_holdings']:.0f}份, 末档网格中枢{res['grid_price'][-1]:.4f}")
# 查看某标的最后一个交易日的网格中枢与买/卖触发价
res = bt.get_stock_results()['513100.SH']
print("最后一日:", res['daily_records'][-1]['date'])
print(" 中枢价:", res['daily_records'][-1]['grid_price'])
print(" 买入触发:", res['daily_records'][-1]['buy_trigger'])
print(" 卖出触发:", res['daily_records'][-1]['sell_trigger'])
print(" 动作:", res['daily_records'][-1]['action'])
# 年度绩效
annual_df = bt.get_annual_performance_df()
print(annual_df[['year', 'total_return', 'max_drawdown', 'sharpe_ratio', 't_trades']])
十、自定义数据详解
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 指数注入说明
- 指数注入走独立的一套
_normalize_index_dataframe,只要求含date与close(close>0); - 注入成功后
use_custom_index_data自动置 True,get_index_data()优先返回注入数据; get_index_data_info()返回has_data/rows/start_date/end_date/columns。
10.3 注入成功判定与验证
| 数据种类 | 注入方法 | 验证方法 |
|---|---|---|
| 标的 | add_stock_data_from_* |
返回 True;get_custom_data_keys() / has_custom_data(code) |
| 指数 | add_index_data_from_* |
返回 True;has_index_data() / get_index_data_info() |
> 建议:构造时把 use_custom_data=True / use_custom_index_data=True 传入,可避免中途依赖本地文件。
十一、常见问题与 FAQ
| 问题 | 解决方案 |
|---|---|
| 提示数据/文件不存在 | 检查 data/历史数据/{code}.parquet;或改用 add_*_data_from_* 注入;指数支持 parquet 及注入多格式 |
| 报错 "xxx 长度必须与 stock_list 长度一致" | 网格列表参数(gd_bc_type_list 等 8 个列表)长度必须与 stock_list 完全一致,逐一对齐 |
| 一直没有网格成交 | 检查步长与行情波动:价格需先涨/跌破中枢一个档位才触发;gd_buy_bc_list 需为正数、gd_sell_bc_list 需为负数;可减小步长(如 0.01)或增大初始建仓 |
| 网格方向反了(该买却卖) | 确认符号:gd_buy_bc_list 存卖出档正数、gd_sell_bc_list 存买入档负数;不要写反 |
| 做T几乎不触发 | 默认需当日价格相对上次做T价涨 ≥ sell_zdf(如 3%)或跌 ≤ buy_zdf;做T买入还需现金 ≥ trade_value,卖出需已有持仓 |
| 某标的现金先用完 | 每标资金池 = cash ÷ 标的数,独立封顶;调小 gd_value/init_position_ratio 或调大 cash |
| 初始建仓占总资金比例之和超 100% | 构造时会打印 ⚠️ 告警;应保证 sum(init_position_ratio_list) ≤ 1 |
| 自定义标的注入了但没生效 | 用 get_custom_data_keys() 验证;确认 use_custom_data=True;代码(含后缀 .SZ/.SH)须与 stock_list 完全一致 |
| 自定义指数注入了但没生效 | 用 has_index_data() 验证,确认 use_custom_index_data=True(或注入成功后自动置位) |
| 手续费偏高 | 网格/做T越频繁手续费越高:放宽网格步长与 ±sell_zdf/buy_zdf 阈值、加大 gd_value/trade_value 或调小 comm |
| 收益率/份额如何看 | 比率字段多为小数(如 total_return=0.25 表示 25%);position_data/account_data 里的 total_return 是百分数口径,注意区分 |
| 如何只做网格、关闭做T | 把 sell_zdf、buy_zdf 设成极大值(如 0.99 / -0.99),做T信号永不触发 |
十二、高级技巧与二次开发
12.1 参数调优建议
| 参数 | 建议范围 | 说明 |
|---|---|---|
gd_buy_bc_list |
0.01–0.05 | 网格卖出档间距,越小网格越密、交易越频繁 |
gd_sell_bc_list |
−0.05 ~ −0.01 | 网格买入档间距,与卖出档可不对称(买入档可略大于卖出档赚价差) |
gd_atr_ratio_list |
1.5–3.0 | ATR 倍数,越大档位越宽、触发越少 |
gd_atr_period_list |
10–30 | ATR 平滑周期 |
gd_value / trade_value |
与资金池匹配 | 每档网格/做T投入金额,越大单次价差收益越高但占用现金越多 |
init_position_ratio_list |
0.05–0.30 | 初始建仓比例,总和 ≤ 1;比例越高越早进入网格状态 |
comm |
0.0001–0.0005 | 网格是高频策略,对手续费高度敏感,务必按真实费率设置 |
12.2 网格设计经验
- 震荡市网格最优:网格天生适合"箱体震荡",单边大涨易过早卖光、单边大跌易满仓被套,建议结合做T止盈止损对冲单边风险;
- 买入档宽于卖出档:让每次低吸赚到的价差能覆盖手续费并留出利润,通常
|卖出档| < |买入档|; - 留足现金:初始建仓建议 ≤ 60%,保证下跌时有充足弹药逐档补仓;
- ATR 自适应:用 ATR 动态档位可避免在波动放大时档位过密、波动萎缩时长期不触发。
12.3 自定义网格/做T规则
继承 xg_gd_backtrader 后重写信号/撮合方法,即可换成任意网格加仓逻辑:
from xg_gd_backtrader.xg_gd_backtrader import xg_gd_backtrader
class MyGrid(xg_gd_backtrader):
def calculate_order_amount(self, total_cash, current_price, current_holdings, stock):
# 例:越跌买越多(金字塔加仓)
params = self.stock_params[stock]
drop = (params['gd_buy_bc'] * 2) # 示意,可按当前价距中枢的跌幅放大下单
return super().calculate_order_amount(total_cash, current_price, current_holdings, stock) * 2
def calculate_grid_prices(self, current_price, grid_price, atr_value, stock):
# 例:自定义档位(如双倍距离触发的变体)
return super().calculate_grid_prices(current_price, grid_price, atr_value, stock)
bt = MyGrid(start_date='20240101', end_date='20261201',
stock_list=['513100.SH'], gd_value_list=[1000],
init_position_ratio_list=[0.1])
bt.run_backtest()
print(bt.generate_report())
12.4 自定义交易/成本规则
重写 calculate_buy_shares() / calculate_sell_shares() / calculate_sell_shares_all() 可自定义整手规则、印花税、最低佣金等;重写 vectorized_backtest() 可在不改变数据层的前提下整体替换交易引擎。
十三、技术说明与提示
performance_metrics中的比率多为小数(如total_return=0.25表示 25%);报告打印时已 ×100;position_data/account_data里的total_return是百分数口径,直接从 dict 取值时注意区分。- 年化按 365.25 天折算;夏普/索提诺/波动率按 250 个交易日年化;无风险利率固定 0.02。
total_invested只统计网格买入金额(含 ATR 网格买入;不含初始建仓与做T买入),t_trades为网格总笔数(含网格买+卖、ATR 网格,不含做T)。- 网格参数以每只标的独立列表下发;组合净值按标的间共同交易日汇总,每标资金池 =
cash ÷ 标的数。 - 符号约定:
gd_buy_bc_list为卖出步长正数、gd_sell_bc_list为买入步长负数;初始建仓比例之和建议 ≤ 1。 - 指数数据仅用于输出留档,策略对照基准为「等权持有不动」曲线。
- 源码当前固定
adj_type='none',若需前复权/后复权可在adjust_price中扩展。 - 做T参数
sell_zdf/buy_zdf是全局的(所有标的共用),与逐标的网格参数独立。
小果网格策略回测系统,让网格/做T策略开发更高效、更透明。如需源码、更多因子库或技术支持,欢迎联系小果(微信:xg_quant)。