小果定投量化策略回测系统专家
一、系统概述
使用教程:https://gitcode.com/qq_50882340/xg_dt_backtrader
1.1 系统定位
小果定投量化策略回测系统(xg_dt_backtrader)是一套专为定期定额定投 + 涨跌做T增厚收益设计的专业级量化回测框架。它以「每隔 N 个交易日定额买入一次」为底仓积累主线,叠加「当日大跌买入 / 当日大涨卖出」的做T规则在震荡中反复降低成本,并以多标的独立资金池 + 组合级汇总的方式输出完整绩效。研究者可用它验证不同定投频率/金额/做T阈值下的收益差异,回答"怎么定投更划算"这类问题。
1.2 核心策略逻辑(每个交易日)
系统按每只标的独立账户逐日决策,资金池 = 总资金 ÷ 标的数:
① 定投信号:距上次定投满 dt_interval 个交易日 → 当天按 dt_type/dt_value 定额买入(底仓积累)
② 做T买入:当日涨跌幅 ≤ buy_zdf(如跌 3%)且现金充足 → 买入 trade_value 金额(逢跌吸筹)
③ 做T卖出:当日涨跌幅 ≥ sell_zdf(如涨 3%)且已有持仓 → 卖出 trade_value 金额(逢涨止盈)
④ 全部成交均按 100 份整手、先扣手续费;随后记录现金/持仓/累计成本/浮盈亏
⑤ 组合级汇总:把各标的在共同交易日的现金、持仓、市值相加,叠加基准曲线输出净值
先做T后定投;做T卖出只减持仓、做T买入与定投只增持仓;定投是「固定节奏」,做T是「涨跌触发器」,两者互不干扰。
1.3 定投与做T的协同逻辑
- 定投(长期底仓):纪律化买入,摊平成本,解决"择时难"问题;
- 做T(波段增强):在既有底仓上做"高抛低吸",单次 T 金额 =
trade_value,收益来自震荡价差; - 定投买入的资金计入累计成本
investment_cost,用于计算持仓浮盈亏;做T买入同样计入成本,做T卖出只回收现金不减少成本口径(成本口径用于观察投入额)。 - 每个标的的资金池是独立上限:当现金不足以支付当期定投金额时自动跳过(记
⚠️定投资金不足),不会挪用其它标的资金。
1.4 设计哲学
- 解耦与复用:数据加载、定投信号、做T信号、交易撮合、绩效评估各环节解耦,可独立替换。
- 透明可解释:每个交易日的买卖动作(
定投买入/做T买入/做T卖出)、价格、手续费、原因均写入日志可追溯。 - 灵活可扩展:三种定投方式、可调做T阈值、标的与指数双重自定义数据源、支持子类重写做个性化规则。
二、安装与运行环境
2.1 安装包
教程 https://gitcode.com/qq_50882340/xg_dt_backtrader
2.2 依赖
pandasnumpypyarrow(读取本地 parquet 历史数据用)openpyxl(导出 Excel 用)
2.3 包结构
推荐以「包」方式导入:
xg_dt_backtrader/
├── __init__.py # 空文件即可
└── xg_dt_backtrader.py # 主模块(定投回测类)
2.4 导入方式
from xg_dt_backtrader.xg_dt_backtrader import xg_dt_backtrader
2.5 数据目录要求(使用内置数据源时)
系统默认从脚本同级的 data/ 目录读取本地数据:
data/
├── 历史数据/
│ ├── 513100.SH.parquet # 标的日线数据
│ └── ...
└── 指数数据/
├── 000300.SH.parquet # 指数数据(parquet 优先)
├── 000300.SH.xlsx # 也支持 Excel / CSV / JSON
└── ...
若不想准备本地数据,可用自定义数据注入(标的 + 指数各自独立注入,见「十、自定义数据详解」),系统自动切换到内存数据源。
2.6 数据字段要求
必须字段:
| 字段名 | 说明 | 是否必须 |
| :--- | :--- | :--- |
| date | 日期 | ✅ 必须 |
| close | 收盘价 | ✅ 必须 |
| open | 开盘价 | ✅ 必须 |
| high | 最高价 | ✅ 必须 |
| low | 最低价 | ✅ 必须 |
| volume | 成交量 | ✅ 必须 |
推荐字段:
| 字段名 | 说明 | 是否必须 |
| :--- | :--- | :--- |
| preClose | 前收盘价 | ⚠️ 推荐(用于复权,缺失时自动跳过复权) |
| zdf | 涨跌幅 | ⚠️ 推荐(不存在时自动用 close.pct_change() 计算) |
| amount | 成交金额 | ⚠️ 可选 |
| 证券代码 | 股票代码 | ⚠️ 可选(缺失时自动添加) |
| 证券名称 | 股票名称 | ⚠️ 可选 |
三、初始化参数完整参考
以下为
__init__方法全部参数详细说明。
3.1 时间与标的 (Time & Universe)
| 参数 | 类型 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| start_date | str | '20260701' | 回测起始日期,格式 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 定投参数 (DCA / Regular Investment)
| 参数 | 类型 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| dt_interval | int | 20 | 定投间隔(交易日数):每隔 N 个交易日定投一次,首个定投日为数据第 1 天 |
| dt_type | str | '金额' | 定投方式:'金额' / '数量' / '百分比' |
| dt_value | float | 1000 | 定投值,含义随 dt_type 变化(每标每期) |
dt_type 与 dt_value 匹配表:
| dt_type | dt_value 含义 | 每期定投买入金额逻辑 |
| :--- | :--- | :--- |
| '金额' | 每标每期固定金额(元) | 买入金额 = dt_value(现金不足则跳过) |
| '数量' | 每标每期固定份额(份) | 买入金额 = dt_value 份 × 当日收盘价 |
| '百分比' | 该标资金池的比例(%数) | 买入金额 = (cash÷标的数) × dt_value ÷ 100 |
例:2 只标的、总资金 10 万 → 每标资金池 5 万。
dt_type='百分比', dt_value=2表示每期每标投入 5 万 × 2% = 1000 元。无论哪种方式,实际买入都以「现金是否足够」且「凑满一手(100份)」为前提,按 100 份整数倍成交。
3.4 做T参数 (T+0 Band Trading)
| 参数 | 类型 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| sell_zdf | float | 0.03 | 做T卖出阈值:当日涨跌幅 ≥ 此值触发卖出(0.03=涨3%),卖出 trade_value 金额 |
| buy_zdf | float | -0.03 | 做T买入阈值:当日涨跌幅 ≤ 此值触发买入(-0.03=跌3%),买入 trade_value 金额 |
| trade_value | float | 1000 | 做T单次交易金额(元):每次高抛/低吸动用多少钱 |
做T信号判定表:
| 当日涨跌幅 | 条件 | 触发动作 |
| :--- | :--- | :--- |
| zdf ≤ buy_zdf(跌超3%) | 现金 ≥ trade_value | 做T买入 trade_value 金额 |
| zdf ≥ sell_zdf(涨超3%) | 持仓 > 0 | 做T卖出 trade_value 金额(超出持仓则全卖) |
设置建议:把
buy_zdf设成sell_zdf的相反数并配套(如 ±3%),在震荡市实现"跌了买、涨了卖"的网格做T。若只想定投不想做T,可把sell_zdf/buy_zdf设成极端值(如 ±99)或 0 结合trade_value判断。
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 | 是否打印详细过程日志 |
四、数据加载与处理详解
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()。
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 |
| ③ | data/指数数据/{index_stock}.xlsx | 本地 Excel(自动识别日期列) |
| ④ | data/指数数据/{index_stock}.csv | 本地 CSV(自动尝试 utf-8/gbk/gb2312/latin-1 编码) |
| ⑤ | data/指数数据/{index_stock}.json | 本地 JSON(orient='records') |
指数数据只用于输出展示/留档(导出目录的「指数数据」),不参与策略基准对比;策略对照基准是「等权持有不动」曲线。
4.4 基准曲线(等权重持有不动)
calculate_benchmark_curve(all_data):取全部标的的共同交易日,每只归一化为「首日=1」后逐日等权平均,得到"等权买入持有不动"净值曲线,作为策略的对照基准,用于计算超额收益。
五、定投与做T信号生成详解
5.1 定投信号(generate_dca_signals)
| 项 | 说明 |
| :--- | :--- |
| 定投日 | 每只标的按自身交易日序列,取第 0, dt_interval, 2×dt_interval, ... 行 → 每隔 dt_interval 个交易日定投一次 |
| dt_type='金额' | 定投金额 = dt_value |
| dt_type='百分比' | 定投金额 = 该标资金池 × dt_value ÷ 100 |
| dt_type='数量'(其它值) | 定投金额 = dt_value 份 × 当日 close |
| 输出列 | 在原 df 追加 is_dca_day(是否定投日)与 dca_amount(当期应投金额) |
5.2 做T信号(generate_trade_signals)
| 项 | 说明 |
| :--- | :--- |
| 做T买信号 is_buy_t | zdf ≤ buy_zdf 且 zdf 非空 |
| 做T卖信号 is_sell_t | zdf ≥ sell_zdf 且 zdf 非空 |
| 单次金额 | t_amount = trade_value |
| 输出列 | 追加 is_buy_t、is_sell_t、t_amount |
5.3 信号触发顺序(每日循环内)
做T买入 → 做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 日交易记录字段(trade_log)
| 字段 | 类型 | 说明 |
| :--- | :--- | :--- |
| date | datetime | 交易日期 |
| stock | str | 标的代码 |
| type | str | 交易类型:定投买入 / 做T买入 / 做T卖出 |
| price | float | 成交价格 |
| amount | float | 成交金额(买入为含手续费成本;卖出为实收金额) |
| shares | int | 成交份额 |
| cash_after | float | 成交后现金 |
| holdings_after | float | 成交后持仓 |
| reason | str | 交易原因,如 定投、跌幅-3.50%、涨幅3.50% |
| zdf | float | 当日涨跌幅 |
| commission | float | 该笔手续费 |
七、完整函数清单
框架类
xg_dt_backtrader共 60 个方法,以下按功能模块列出,便于二次开发和深度集成。
7.1 构造与配置
| 方法 | 说明 |
| :--- | :--- |
| __init__(...) | 构造定投回测实例(含做T规则与数据源开关) |
| 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 → excel → csv → json 五级回退 |
| _normalize_dataframe(df, stock_code) | 标准化注入的 DataFrame:日期识别/排序、补缺列、算 zdf、算复权因子 |
| 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, 'source': str}(无数据时返回 {'has_data': False}) |
7.7 定投与做T信号生成
| 方法 | 说明 |
| :--- | :--- |
| generate_dca_signals(df, per_stock_cash) | 生成定投信号:追加 is_dca_day / dca_amount 列 |
| generate_trade_signals(df) | 生成做T信号:追加 is_buy_t / is_sell_t / t_amount 列 |
7.8 交易撮合计算
| 方法 | 说明 |
| :--- | :--- |
| calculate_buy_shares(amount, price) | 买入份额与成本计算(100 份整数倍、先扣佣金) |
| calculate_sell_shares(shares, price, amount) | 按金额卖出份额与实收计算(超持仓则全卖) |
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() | 获取各标的独立回测结果(含 daily_records、交易数组、终值等) |
| 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 文件(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 | 做T交易次数 | int(含做T买入+卖出) |
| 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 | 当年做T次数 |
| 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、dca_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、stock_details(逐标的现值/现金/收益)。
8.6 账户统计(account_data['statistics'])
起始/结束日期、总天数、初始资金、期末总资产/现金/持仓、累计投入、总盈亏、总收益率(百分数)、最高/最低总资产与净值、波动率、基准期末净值与总收益。
8.7 文本报告内容(generate_report())
策略参数(标的/指数/复权/定投/做T/手续费/数据源)→ 各标的盈亏明细 → 汇总持仓 → 收益指标(总/年化/盈亏/波动/手续费)→ 基准对比(含超额)→ 风险指标(回撤/夏普/索提诺/卡尔玛)→ 交易统计(总次数/做T次数/胜率)→ 年度收益统计表。
8.8 导出目录结构
策略模型/定投策略/{user}/
├── 指数数据/ 指数数据.xlsx + .json
├── 成交数据/ 成交数据.xlsx + .json
├── 持股数据/ 持股数据.xlsx + .json
├── 账户数据/ 账户数据.xlsx + .json
├── 绩效指标/ 绩效指标.xlsx + .json
├── 净值曲线/ 净值曲线.xlsx + .json
├── 策略参数/ 策略参数.xlsx + .json
└── 年度收益统计/ 年度收益统计.xlsx + .json
九、完整实战示例
9.1 示例1:经典定期定额定投(金额模式 + 默认做T)
from xg_dt_backtrader.xg_dt_backtrader import xg_dt_backtrader
bt = xg_dt_backtrader(
start_date='20250701',
end_date='20261201',
stock_list=['513100.SH', '513500.SH'], # 纳指ETF / 标普500ETF
index_stock='000300.SH',
cash=100000,
comm=0.0001,
dt_interval=20, # 每 20 个交易日定投一次
dt_type='金额', # 金额定投
dt_value=1000, # 每标每期定投 1000 元
sell_zdf=0.03, # 涨 3% 做T卖出
buy_zdf=-0.03, # 跌 3% 做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_dt_backtrader(
start_date='20240101',
end_date='20261201',
stock_list=['159915.SZ', '510300.SH', '518880.SH'],
cash=300000,
comm=0.0001,
dt_interval=30, # 月度级别定投
dt_type='百分比', # 按资金池比例定投
dt_value=2, # 每期投入该标资金池的 2%
sell_zdf=0.99, # 极大值 → 关闭做T卖出
buy_zdf=-0.99, # 极大值 → 关闭做T买入
trade_value=1000,
max_workers=4,
)
bt.run_backtest()
print(bt.generate_report())
print("做T交易次数:", bt.get_performance_metrics().get('t_trades', 0)) # 应为 0
9.3 示例3:数量定投 + 注入自定义标的数据
bt = xg_dt_backtrader(
start_date='20230101',
end_date='20261201',
stock_list=['513100.SH', '513500.SH'],
cash=200000,
dt_interval=10,
dt_type='数量', # 按固定份额定投
dt_value=100, # 每期定投 100 份
sell_zdf=0.05,
buy_zdf=-0.05,
trade_value=2000,
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/dca_backtest.json')
9.4 示例4:注入自定义指数数据(自定义对比指数)
import pandas as pd
# 构造或读取指数 DataFrame(含 date/close 即可)
index_df = pd.read_csv('data/index_000300.csv')
bt = xg_dt_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':..., 'source': 'dataframe'}
bt.run_backtest()
print(bt.generate_report())
# 恢复本地指数
# bt.clear_index_data()
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"做T交易次数: {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['total_invested']:,.2f}")
# 年度绩效
annual_df = bt.get_annual_performance_df()
print(annual_df[['year', 'total_return', 'annual_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_dataframe(以INDEX为占位代码),只要求含date与close(close>0); - 注入成功后
use_custom_index_data自动置 True,get_index_data()优先返回注入数据; get_index_data_info()额外返回source(dataframe/csv/excel/json/...)标识来源。
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;指数支持 parquet/xlsx/csv/json 多格式回退,或改用 add_*_data_from_* 注入 |
| 定投一直没成交 | 每期金额可能不足一手(100 份 × 价格);现金低于当期定投额会自动跳过;检查 dt_interval 是否大于区间内交易日数 |
| 做T几乎不触发 | 默认需当日涨跌幅 ≥ sell_zdf(涨 3%)或 ≤ buy_zdf(跌 3%);做T买入还需现金 ≥ trade_value,卖出需已有持仓 |
| 某标的现金先用完 | 每标资金池 = cash ÷ 标的数,独立封顶;降低 dt_value/dt_type='百分比' 或调大 cash |
| 自定义标的注入了但没生效 | 用 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 阈值、加大 trade_value 或调小 comm |
| 收益率/份额如何看 | 比率字段多为小数(如 total_return=0.25 表示 25%);get_position_data() 的 total_return 是百分数口径,注意区分 |
| 份额为什么不是整百 | 做T卖出若持仓不足 100 份会一次性全卖(尾仓处理);正常买卖均按 100 份整数倍 |
| 如何只定投不做T | 把 sell_zdf、buy_zdf 设成极大值(如 0.99 / -0.99),做T信号永不触发 |
十二、高级技巧与二次开发
12.1 参数调优建议
| 参数 | 建议范围 | 说明 |
| :--- | :--- | :--- |
| dt_interval | 5–60(交易日) | 越小定投越频繁、越接近"每日/每周定投";越大越接近"季度定投" |
| dt_type | 金额 / 百分比 / 数量 | 金额最简单直观;百分比模式定投额随资金池自动封顶 |
| sell_zdf / buy_zdf | ±1% ~ ±5% | 阈值越小做T越频繁,注意与手续费平衡 |
| trade_value | 与 dt_value 同级 | 做T单笔金额,越大单次 T 收益越高但占用现金越多 |
| comm | 0.0001–0.0005 | 做T策略对手续费高度敏感,务必按真实费率设置 |
12.2 定投节奏设计
- 日定投:
dt_interval=1(每个交易日买入); - 周定投:
dt_interval≈5;双周定投:≈10;月定投:≈20~22; - 定投间隔与做T阈值独立,可组合出「周定投 + 日做T」等混合形态。
12.3 自定义定投/做T规则
继承 xg_dt_backtrader 后重写信号方法,即可换成任意定投/加仓逻辑:
from xg_dt_backtrader.xg_dt_backtrader import xg_dt_backtrader
class MyDCA(xg_dt_backtrader):
def generate_dca_signals(self, df, per_stock_cash):
df = super().generate_dca_signals(df, per_stock_cash)
# 例:只在"净值低于 120 日线"时定投(越跌越买)
df['ma120'] = df['close'].rolling(120).mean()
df.loc[df['close'] > df['ma120'], 'is_dca_day'] = False
df.loc[df['close'] > df['ma120'], 'dca_amount'] = 0
return df
def generate_trade_signals(self, df):
df = super().generate_trade_signals(df)
# 例:增加"持仓浮盈达 5% 才做T卖出"的风控
# ... 自定义列 is_sell_t 即可
return df
bt = MyDCA(start_date='20240101', end_date='20261201', dt_interval=10, dt_value=1000)
bt.run_backtest()
print(bt.generate_report())
12.4 自定义交易/成本规则
重写 calculate_buy_shares() / calculate_sell_shares() 可自定义整手规则、印花税、最低佣金等;重写 vectorized_backtest() 可在不改变数据层的前提下整体替换交易引擎。
十三、技术说明与提示
performance_metrics中的比率多为小数(如total_return=0.25表示 25%);报告打印时已 ×100;position_data/account_data里的total_return是百分数口径,直接从 dict 取值时注意区分。- 年化按 365.25 天折算;夏普/索提诺/波动率按 250 个交易日年化;无风险利率固定 0.02。
total_invested只统计定投买入金额,做T买入计入成本但不计入该字段;t_trades为做T总笔数(买+卖)。- 回测以每只标的数据的自身交易日序列生成定投日;组合净值按标的间共同交易日汇总。
- 指数数据仅用于输出留档,策略对照基准为「等权持有不动」曲线。
- 源码当前固定
adj_type='none',若需前复权/后复权可在adjust_price中扩展。
小果定投量化策略回测系统,让定投/做T策略开发更高效、更透明。如需源码、更多因子库或技术支持,欢迎联系小果(微信:xg_quant)。
Scan to join WeChat group