返回 Skill 列表
extension
分类: 数据与分析无需 API Key

小果资产配置平衡策略回测系统助手

小果(微信:xg_quant)资产配置平衡策略回测系统助手,专注于"固定市值比率配置 + 偏离再平衡 + 涨跌幅做T"策略的深度开发、回测与优化。 本系统基于"按固定权重/金额建仓 → 每N个交易日检测偏离 → 超出阈值拉回目标 → 涨跌幅做T增厚收益"核心架构,提供从数据治理、 配置目标设定、再平衡引擎、做T风控、绩效归因到结果导出的全链路解决方案。支持百分比权重 / 固定金额两种配置方式、 自定义数据注入、个股独立偏离度、完备的手续费处理与丰富的输出接口。 适用人群:量化研究员、资产配置策略开发者、ETF/指数轮动投资者、再平衡策略研究者、基金投研人员、智能定投用户。 触发关键词:资产配置、再平衡、平衡策略、偏离调仓、固定权重、股债平衡、网格调仓、做T、止盈止损、 百分比配置、金额配置、xg_zcph_backtrader、小果量化、绩效归因。

person作者: user_676c825dhubcommunity

小果资产配置平衡策略回测系统专家

教程 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 依赖

  • pandas
  • numpy
  • pyarrow(读取 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) 加载单只标的数据时按两步取数:

  1. 自定义数据源:当 use_custom_data=True 且该代码已注入 → 从内存 _custom_data_cache 读取;
  2. 本地文件:否则读取 data/历史数据/{code}.parquet

两种路径都会执行统一的清洗流程:

  • 截取 [start_date, end_date] 区间;
  • 按日期升序排序;
  • 剔除 close <= 0open <= 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 时清零并触发一次再平衡:

  1. 刷新目标
    • 百分比:目标 = 当前总资产 × weight_list[i](随资产涨跌动态跟随);
    • 金额:默认保持初始固定目标金额;若某标的目标金额 ≥ 总资产 95% 则改为 总资产 × 显示权重
  2. 逐标的检测偏离current_value = 持仓 × 当日收盘diff = current_value − 目标threshold = 目标 × deviation_list[i]
  3. 触发调仓|diff| > threshold 且目标>0):
    • diff > 0(超配)→ 卖出 floor(|diff|/price/100)×100 份(不超持仓);
    • diff < 0(低配)→ 买入 floor(|diff|/price/100)×100 份(受现金约束)。
  4. 调仓按 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 手续费处理总则

  1. 买入:先扣除手续费,再用剩余金额买入,实际成本 = 市值 + 手续费(下单金额 100 万、佣金 1 万 → 实际买入 99 万);
  2. 卖出:手续费从卖出金额中直接扣除,实收 = 卖出市值 − 手续费;
  3. 卖出数量:永不超持仓,取整后不足一手则跳过或全卖。

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_tradesbuy_tradessell_tradesrebalance_buy(再平衡买入次数)、rebalance_sell(再平衡卖出次数)、stop_profit(止盈次数)、stop_loss(止损次数)、total_buy_amounttotal_sell_amounttotal_commissionfirst_trade_datelast_trade_date

8.4 每日持仓(daily_positions / get_daily_positions()

每条记录含 datecashholdingstotal_valuedaily_pnlcumulative_pnltotal_return,以及 stock_details(每只标的的 stock/price/zdf/holdings/value/cash/weight/target),其中 target 在百分比模式显示 xx.x%、金额模式显示 ¥xxweight 为该标的当前市值占比。

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}.parquetdata/指数数据/{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%),interval 20–40,减少无谓调仓;
  • 高频/纪律化deviation_list 取 0.03–0.08(3%–8%),interval 5–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_returnwin_ratemax_drawdownvolatility 均为小数;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)。