小果资产配置平衡策略回测系统专家
教程 https://gitcode.com/qq_50882340/xg_zcph_backtrader
一、系统概述
1.1 系统定位
小果资产配置平衡策略回测系统(xg_zcph_backtrader)是一套专为固定比例资产配置 + 定期再平衡策略设计的专业级量化回测框架。它把「按固定市值比率建仓 → 设定偏离阈值 → 定期检测实际偏离 → 超阈即拉回目标 → 日内涨跌幅做T增厚收益」这套经典再平衡策略逻辑标准化为可配置的流水线,让研究者能够高效地验证配置比例、偏离阈值、再平衡频率与做T规则的有效性,并输出完整的绩效报告与逐年收益统计。
1.2 资产配置平衡策略核心逻辑
每个交易日按以下主线推进:
① 初始建仓:按 dt_type 指定的目标,将现金分配到各标的(百分比 → 占总资产权重;金额 → 固定金额)
② 逐日推进:记录当日市值、现金、总资产、持仓、涨跌幅
③ 定期再平衡:每累计 interval 个交易日检查一次偏离
—— 目标市值 = 总资产 × 权重(百分比)或固定目标金额(金额)
—— 偏离 = 当前市值 − 目标市值
—— 当 |偏离| > 目标值 × 该标的偏离度 deviation_list[i] 时触发调仓
—— 超配(偏离>0) → 卖出多出部分;低配(偏离<0) → 买入补足
④ 做T增厚:每日对持仓检查日内涨跌幅
—— 当日涨幅 ≥ sell_zdf → 按 trade_value 止盈卖出
—— 当日跌幅 ≤ buy_zdf(负值)→ 按 trade_value 止损买入
⑤ 手续费:买入先扣佣金再成交;卖出从实收中扣佣;每笔 100 份整数倍
1.3 再平衡(Rebalance)触发原理
本系统区别于"买卖择时",核心是把组合市值始终拉回预设目标比例。触发条件为偏离度超过阈值:
目标市值 T_i = 总资产 × weight_list[i] # 百分比模式,每期按最新总资产刷新
当前市值 V_i = 持仓份数 × 当日收盘价
偏离 D_i = V_i − T_i
触发阈值 = T_i × deviation_list[i]
当 |D_i| > T_i × deviation_list[i] 且 T_i > 0 时 → 触发调仓
D_i > 0:超配,卖出 |D_i| 对应市值 (目标 − 当前 < 0)
D_i < 0:低配,买入 |D_i| 对应市值 (目标 − 当前 > 0)
偏离度
deviation_list[i]越大,允许的漂移越宽,调仓越少、手续费越低;越小则越频繁把组合"拉回"目标比例,纪律性越强。它相当于给每只标的单独设定一个"再平衡带宽"。
1.4 做T(T+0 滚动)逻辑
在保持目标配置的大前提下,本系统叠加了日内做T规则,用于在既定持仓上低买高卖增厚收益(默认针对涨跌幅,用 trade_value 金额做滚动):
止盈卖出:当日涨跌幅 zdf ≥ sell_zdf(如 3%)→ 卖出约 trade_value 金额(100 份整数倍,不超持仓)
止损买入:当日涨跌幅 zdf ≤ buy_zdf(如 −3%)→ 买入约 trade_value 金额(受可用现金约束)
若只想做"纯再平衡"、关闭做T,可把
sell_zdf设成很大的正数(如 8888)并将buy_zdf设成很大的负数(如 −8888)。
1.5 设计哲学
- 纪律化配置:不再依赖"择时/选强",而是相信长期按目标比例持有并定期拉回,能获得再平衡溢价(rebalancing premium)并自动高抛低吸。
- 解耦与复用:数据加载、目标设定、偏离检测、交易执行、绩效评估各环节解耦,可独立优化。
- 透明可解释:每个交易日的偏离、触发原因、调仓方向均可从
trade_log与报告追溯。 - 灵活可扩展:支持百分比/金额两种配置、个股独立偏离度、可关闭的做T、自定义数据注入,覆盖从简单到复杂的资产配置策略。
二、安装与运行环境
2.1 安装方式
教程 https://gitcode.com/qq_50882340/xg_zcph_backtrader
2.2 依赖
pandasnumpypyarrow(读取 parquet 历史数据用)openpyxl(导出 Excel 用)
2.3 包结构(推荐)
推荐以「包」方式组织(便于后续发布与多脚本复用):
xg_zcph_backtrader/
├── __init__.py # 空文件即可
└── xg_zcph_backtrader.py # 主模块(资产配置回测类)
2.4 导入方式
from xg_zcph_backtrader.xg_zcph_backtrader import xg_zcph_backtrader
若直接运行单文件,可把脚本放到任意目录后以模块方式导入,或直接 python xg_zcph_backtrader.py 运行内置示例。
2.5 数据目录要求(使用内置数据源时)
系统默认从脚本同级的 data/ 目录读取本地 parquet 数据,目录结构为:
data/
├── 历史数据/
│ ├── 159915.SZ.parquet # 标的日线数据
│ ├── 513100.SH.parquet
│ └── 518880.SH.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 | '20260101' | 回测起始日期,格式 YYYYMMDD |
| end_date | str | '20500101' | 回测结束日期,格式 YYYYMMDD |
| stock_list | list | ['159915.SZ','513100.SH','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 配置目标 (Allocation Target)
| 参数 | 类型 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| dt_type | str | '百分比' | 配置方式:'百分比'(weight_list 为权重比例)/ '金额'(weight_list 为固定金额,元) |
| weight_list | list | [0.35,0.35,0.3] | 权重/金额列表,与 stock_list 一一对应。百分比模式若合计≠1会自动归一化;金额模式为每个标的的目标金额 |
| deviation_list | list | [0.1,0.1,0.05] | 偏离度列表,与 stock_list 一一对应。当标的市值偏离目标超过 deviation_list[i] 时触发再平衡 |
3.4 再平衡频率 (Rebalance Frequency)
| 参数 | 类型 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| interval | int | 20 | 再平衡检查间隔(交易日)。每累计 interval 天做一次偏离检测与调仓 |
3.5 做T规则 (T+0 Rule)
| 参数 | 类型 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| sell_zdf | float | 0.03 | 做T止盈阈值:当日涨幅 ≥ 此值触发卖出(0.03=3%) |
| buy_zdf | float | -0.03 | 做T止损阈值:当日跌幅 ≤ 此值触发买入(−0.03=−3%),需传负值 |
| trade_value | float | 1000 | 做T每次交易金额(元),用于计算每次买卖的份额 |
3.6 运行与数据源 (Runtime & Data Source)
| 参数 | 类型 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| max_workers | int | 4 | 多线程加载数据时的线程数 |
| use_custom_data | bool | False | 是否使用自定义数据源(注入数据后系统会自动置为 True) |
3.7 内部固定参数(构造时自动设置,一般不修改)
| 参数 | 值 | 说明 |
| :--- | :--- | :--- |
| adj_type | 'none' | 复权方式(源码当前固定为 none,即不做前复权/后复权) |
| min_shares | 100 | 最小交易单位(ETF 一手 = 100 份) |
| share_multiple | 100 | 交易数量的整数倍(每次按 100 份整数倍下单) |
3.8 dt_type 与 weight_list 匹配表
| dt_type | weight_list 含义 | 目标市值刷新逻辑 | 说明 |
| :--- | :--- | :--- | :--- |
| '百分比' | 权重比例,如 [0.35,0.35,0.3] | 每期 目标 = 最新总资产 × 权重 | 权重自动归一化;随总资产涨跌动态跟随,比例始终固定 |
| '金额' | 固定目标金额,如 [30000,30000,30000] | 目标为初始设定的固定金额 | 金额模式下,当某标的目标金额 ≥ 总资产 95% 时按最新总资产 × 显示权重刷新(防失真) |
金额模式资金缩放:若
sum(weight_list) > 初始资金 cash,构造时会按比例自动缩小 weight_list,使总额匹配可用资金。金额模式下若某标的目标金额过大(≥ 总资产 95%),系统会在再平衡时把它替换为总资产 × 显示权重,以贴合实际。
3.9 长度校验
len(weight_list)必须等于len(stock_list),否则抛ValueError。len(deviation_list)必须等于len(stock_list),否则抛ValueError。
四、数据加载与处理详解
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()(第一行为 0)。
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」,再按配置权重(百分比模式用 weight_list;金额模式换算为金额比例)加权求和,得到"按既定配置买入持有、从不调仓"的净值曲线,作为策略的对照基准,用于衡量再平衡/做T带来的超额收益。
五、目标配置与偏离检测详解
5.1 初始建仓
回测首日按 dt_type 建仓:
- 百分比模式:
目标金额 = 账户现金 × weight_list[i]; - 金额模式:
目标金额 = weight_list[i](若总额超现金则先缩放)。
建仓使用 calculate_buy_shares(先扣手续费、按 100 份整数倍),每笔写入 trade_log(type=初始建仓)。
5.2 定期再平衡引擎
主循环每推进一日 rebalance_counter += 1,当 rebalance_counter >= interval 时清零并触发一次再平衡:
- 刷新目标:
- 百分比:
目标 = 当前总资产 × weight_list[i](随资产涨跌动态跟随); - 金额:默认保持初始固定目标金额;若某标的目标金额 ≥ 总资产 95% 则改为
总资产 × 显示权重。
- 百分比:
- 逐标的检测偏离:
current_value = 持仓 × 当日收盘,diff = current_value − 目标,threshold = 目标 × deviation_list[i]。 - 触发调仓(
|diff| > threshold且目标>0):diff > 0(超配)→ 卖出floor(|diff|/price/100)×100份(不超持仓);diff < 0(低配)→ 买入floor(|diff|/price/100)×100份(受现金约束)。
- 调仓按
100份整数倍执行,不足一手(<100 份)跳过。
5.3 做T引擎
每个交易日对有持仓的标的下检查日内涨跌幅:
zdf >= sell_zdf→ 调calculate_sell_shares_by_amount(持仓, price, trade_value)止盈卖出;zdf <= buy_zdf且现金充足 → 调calculate_buy_shares(trade_value, price)止损买入。
做T与再平衡可以同日发生,顺序上先做T、后记录当日状态;两种引擎独立触发、互不冲突。
六、交易执行详解
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 卖出规则
按金额卖(做T止盈/止损) —— calculate_sell_shares_by_amount(shares, price, sell_amount):
- 当日触发阈值时,卖出约
sell_amount对应市值; - 按 100 份整数倍;不足一手但按金额该买一手时补到一手;
- 卖出股数超持仓则全卖;
- 实收金额 = 卖出市值 − 手续费。
全部卖出(再平衡清超配 / 清仓) —— calculate_sell_shares_all(shares, price):
- 把指定份额全部卖出(不足一手时按实际持仓卖出);
- 实收金额 = 卖出市值 − 手续费。
6.3 手续费处理总则
- 买入:先扣除手续费,再用剩余金额买入,实际成本 = 市值 + 手续费(下单金额 100 万、佣金 1 万 → 实际买入 99 万);
- 卖出:手续费从卖出金额中直接扣除,实收 = 卖出市值 − 手续费;
- 卖出数量:永不超持仓,取整后不足一手则跳过或全卖。
6.4 日交易记录字段
每笔成交写入 trade_log,字段如下:
| 字段 | 类型 | 说明 |
| :--- | :--- | :--- |
| date | datetime | 交易日期 |
| stock | str | 标的代码 |
| type | str | 交易类型:初始建仓 / 再平衡买入 / 再平衡卖出 / 止盈卖出 / 止损买入 |
| price | float | 成交价格 |
| shares | int | 成交股数 |
| amount | float | 成交金额(买入为含手续费成本;卖出为实收金额) |
| commission | float | 该笔手续费 |
| reason | str | 交易原因,如 超配10%,卖出调仓 (目标¥.., 当前¥..)、涨幅3.50%触发止盈、跌幅3.20%触发止损买入 |
七、完整函数清单
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_benchmark_curve(all_data) | 计算"按配置权重持有不动"基准净值曲线 |
| calculate_performance_metrics(df, df_index) -> dict | 计算核心绩效指标 |
| calculate_annual_performance(portfolio_df) -> dict | 计算逐年收益统计 |
7.6 交易计算
| 方法 | 说明 |
| :--- | :--- |
| calculate_buy_shares(amount, price) | 买入股数与成本计算(100 份整数倍、先扣佣金) |
| calculate_sell_shares_by_amount(shares, price, sell_amount) | 按金额卖出(做T止盈/止损) |
| calculate_sell_shares_all(shares, price) | 全部卖出(再平衡清超配 / 清仓) |
7.7 回测执行
| 方法 | 说明 |
| :--- | :--- |
| vectorized_backtest() | 向量化回测主引擎(逐日推进 + 定期再平衡 + 每日做T) |
| run_backtest() | 回测主入口,等价于 vectorized_backtest() |
7.8 绩效与报告
| 方法 | 说明 |
| :--- | :--- |
| get_performance_metrics() | 获取绩效(未回测则自动回测) |
| get_annual_performance() | 获取逐年收益统计 dict |
| get_annual_performance_df() | 年度统计转 DataFrame |
| get_equity_curve_data() | 获取净值曲线数据 |
| 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,含每日 stock_details) |
| 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 | 超额收益百分比 | 数值 |
| interval/deviation_list/dt_type/sell_zdf/buy_zdf/use_custom_data | 回放的策略参数 | 便于留档 |
8.2 净值曲线字段(equity_curve_data)
| 字段 | 说明 |
| :--- | :--- |
| dates | 日期字符串列表 |
| total_value | 每日总资产 |
| cumulative_pnl | 每日累计盈亏 |
| cash | 每日现金 |
| holdings | 每日持仓份额 |
| net_value | 策略净值(总资产/初始资金) |
| drawdown | 回撤百分比(%) |
| position_ratio | 每日仓位比例(%) |
| total_return | 每日累计收益率 |
| daily_return | 每日收益率 |
| benchmark_net_value / benchmark_return | 基准净值/收益 |
| statistics | 起点/终点/最高/最低/总收益/最大回撤等摘要 |
| benchmark_statistics | 基准净值摘要 |
8.3 成交统计(trade_data['statistics'])
total_trades、buy_trades、sell_trades、rebalance_buy(再平衡买入次数)、rebalance_sell(再平衡卖出次数)、stop_profit(止盈次数)、stop_loss(止损次数)、total_buy_amount、total_sell_amount、total_commission、first_trade_date、last_trade_date。
8.4 每日持仓(daily_positions / get_daily_positions())
每条记录含 date、cash、holdings、total_value、daily_pnl、cumulative_pnl、total_return,以及 stock_details(每只标的的 stock/price/zdf/holdings/value/cash/weight/target),其中 target 在百分比模式显示 xx.x%、金额模式显示 ¥xx,weight 为该标的当前市值占比。
8.5 账户统计(account_data['statistics'])
起始/结束日期、总天数、初始资金、期末总资产/现金/持仓、总盈亏、总收益率、最高/最低总资产、最高/最低净值、波动率、基准期末净值与总收益。
8.6 文本报告内容(generate_report())
策略参数 → 各标的结果(目标/最终资产/盈亏/收益率)→ 交易统计 → 汇总持仓 → 收益指标(总/年化/盈亏/波动/手续费)→ 基准对比(按配置权重持有不动)→ 风险指标(回撤/夏普/索提诺/卡尔玛/胜率)→ 年度收益统计表。
8.7 导出目录结构
策略模型/资产配置平衡策略/{user}/
├── 指数数据/ 指数数据.xlsx + .json
├── 成交数据/ 成交数据.xlsx + .json
├── 持股数据/ 持股数据.xlsx + .json
├── 账户数据/ 账户数据.xlsx + .json
├── 绩效指标/ 绩效指标.xlsx + .json
├── 净值曲线/ 净值曲线.json
├── 策略参数/ 策略参数.xlsx + .json
└── 年度收益统计/ 年度收益统计.xlsx + .json
九、完整实战示例
9.1 示例 1:经典资产配置平衡(百分比模式 + 做T)
from xg_zcph_backtrader.xg_zcph_backtrader import xg_zcph_backtrader
bt = xg_zcph_backtrader(
start_date='20230101',
end_date='20261201',
stock_list=['159915.SZ', '513100.SH', '518880.SH'], # 创业板ETF + 纳指ETF + 黄金ETF
dt_type='百分比', # 按权重配置
weight_list=[0.35, 0.35, 0.3], # 35% / 35% / 30%
deviation_list=[0.1, 0.1, 0.05], # 每只允许偏离 10% / 10% / 5%
interval=20, # 每 20 个交易日检查一次再平衡
index_stock='000300.SH',
cash=100000,
sell_zdf=0.03, # 涨 3% 止盈做T
buy_zdf=-0.03, # 跌 3% 止损做T
trade_value=1000, # 每次做T 1000 元
comm=0.0001, # 手续费 万1
max_workers=8,
)
bt.run_backtest()
print(bt.generate_report())
bt.save_backtrader_data(user='资产配置示例')
9.2 示例 2:固定金额模式(按金额配置)
bt = xg_zcph_backtrader(
start_date='20240101',
end_date='20261201',
stock_list=['159915.SZ', '513100.SH', '518880.SH'],
dt_type='金额', # 按固定金额配置
weight_list=[30000, 30000, 40000], # 每只目标金额 3万/3万/4万
deviation_list=[0.08, 0.08, 0.08], # 统一偏离带宽 8%
interval=30, # 每月再平衡一次
cash=100000,
sell_zdf=0.03,
buy_zdf=-0.03,
trade_value=1000,
comm=0.0001,
max_workers=4,
)
bt.run_backtest()
print(bt.generate_report())
9.3 示例 3:纯再平衡(关闭做T)+ 自定义数据注入
bt = xg_zcph_backtrader(
start_date='20230101',
end_date='20261201',
stock_list=['159915.SZ', '513100.SH', '518880.SH'],
dt_type='百分比',
weight_list=[0.5, 0.3, 0.2],
deviation_list=[0.05, 0.05, 0.05],
interval=20,
cash=200000,
sell_zdf=8888, # 设很大 → 不触发做T止盈
buy_zdf=-8888, # 设很负 → 不触发做T止损
trade_value=1000,
comm=0.0002,
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_dataframe('518880.SH', df_518880) # 内存 DataFrame
print("已注入:", bt.get_custom_data_keys())
bt.run_backtest()
print(bt.generate_report())
bt.save_to_json('result/zcph_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"再平衡买入/卖出/止盈/止损: "
f"{trades['statistics']['rebalance_buy']}/"
f"{trades['statistics']['rebalance_sell']}/"
f"{trades['statistics']['stop_profit']}/"
f"{trades['statistics']['stop_loss']}")
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_df = bt.get_annual_performance_df()
print(annual_df[['year', 'total_return', 'annual_return', 'max_drawdown', 'sharpe_ratio', 'win_rate']])
# 各标的独立结果
for code, res in bt.get_stock_results().items():
print(f"{code}: 期末持仓 {res['final_holdings']} 份, 期末市值 ¥{res['final_value']:,.2f}")
十、自定义数据详解
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_* 注入自定义数据 |
| 首日建仓很少 / 一直空仓 | 确认初始 cash 充足、首日各标的有有效价格;金额模式下若 sum(weight_list) > cash 会被缩放,留意缩放日志 |
| 调仓次数异常多/手续费高 | 说明偏离带宽 deviation_list 过小或 interval 过小;调大带宽与间隔可显著降换手 |
| 调仓次数很少/几乎不动 | 偏离带宽 deviation_list 过大导致几乎不触发;或 interval 过大;做T阈值 sell_zdf/buy_zdf 设置过宽也可能减少滚动 |
| 持仓市值长期偏离目标 | 可能因目标金额被"≥总资产95%"刷新规则改写;或单次调仓受现金/整手约束不足一手被跳过 |
| 做T总不触发 | 检查 sell_zdf(正)与 buy_zdf(负)阈值是否合理;做T只在"有持仓"时卖出、只在该标的有持仓时检查;止损买入受可用现金约束 |
| 自定义数据注入了但没生效 | 用 get_custom_data_keys() 验证;确认构造或注入后 use_custom_data=True;代码(含后缀 .SZ/.SH)必须与 stock_list 完全一致 |
| 收益率为 0 或异常 | 检查价格列全为正、cash 初始值、买入是否因金额不足/不够一手而失败;检查 weight_list/deviation_list 长度与标的一一对应 |
| 如何关闭做T只做纯再平衡 | 把 sell_zdf 设很大的正数(如 8888)、buy_zdf 设很大的负数(如 −8888) |
| weight_list/deviation_list 报长度错误 | 两者长度必须都等于 stock_list 长度,否则构造抛 ValueError |
十二、高级技巧与二次开发
12.1 偏离带宽与再平衡频率的权衡
- 中频/交易成本敏感:
deviation_list取 0.1–0.2(10%–20%),interval20–40,减少无谓调仓; - 高频/纪律化:
deviation_list取 0.03–0.08(3%–8%),interval5–20,更紧贴目标比例; - 想验证"再平衡溢价",可把基准对比聚焦在
excess_return(策略 vs 持有不动)。
12.2 做T参数与行情匹配
- 高波动标的可把
sell_zdf/buy_zdf设小(如 ±1%–2%)、trade_value略大以捕捉更多日内滚动; - 低波动标的设大阈值避免频繁触发被手续费侵蚀;
- 想评估做T贡献,可用同一配置分别开/关做T回测对比总收益与手续费。
12.3 配置比例与再平衡的超额来源
- 目标权重在回撤期"自动补跌配资产、上涨期卖出超配资产",天然实现高抛低吸;
- 两种资产的波动率差异越大、相关性越低,再平衡潜在收益越明显;可用
excess_return衡量该溢价是否兑现。
12.4 自定义目标/调仓逻辑
继承 xg_zcph_backtrader 并重写 vectorized_backtest() 中的目标刷新或偏离检测部分,即可换成自定义目标(如目标日期/风险平价权重、趋势过滤后再平衡等),数据加载、交易执行、绩效输出完全复用。
from xg_zcph_backtrader.xg_zcph_backtrader import xg_zcph_backtrader
class MyRebalance(xg_zcph_backtrader):
def _refresh_targets(self, total_asset):
# 自定义目标刷新逻辑(示例:始终对半配置)
return {s: total_asset * 0.5 for s in self.stock_list}
bt = MyRebalance(start_date='20230101', end_date='20261201',
stock_list=['159915.SZ', '513100.SH'],
weight_list=[0.5, 0.5], deviation_list=[0.05, 0.05])
bt.run_backtest()
print(bt.generate_report())
12.5 自定义做T / 交易规则
继承后可在主循环中追加自定义触发条件(如移动止盈、跌破均线加仓、最大回撤风控线等),再复用 calculate_buy_shares / calculate_sell_shares_* 成交并写入 trade_log。
十三、技术说明与提示
performance_metrics中的比率多为小数(如total_return=0.25表示 25%),报告打印时已 ×100;直接从 dict 取值时注意换算。total_return、win_rate、max_drawdown、volatility均为小数;excess_return_pct已是百分比数值。- 年化按 365.25 天折算,夏普/波动按 250 个交易日年化,无风险利率固定 0.02。
- 回测按共同交易日推进,标的停牌/上市造成的日期错位会被自动对齐剔除。
- 做T的"止损买入"是加仓滚动(低吸),并非清仓止损,语义上区别于"割肉止损",请勿混淆。
- 金额模式目标刷新存在"目标 ≥ 总资产 95% 才改写"的保护逻辑;超大单标的务必留意该行为。
- 源码当前固定
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() 获取指数数据信息
小果资产配置平衡策略回测系统,让配置/再平衡/做T策略开发更高效、更透明。如需源码、更多因子库或技术支持,欢迎联系小果(微信:xg_quant)。
Scan to join WeChat group