/~learn/s/183038da
> 日期:2026-10-12 > 背景:为择时策略池新增 30年国债ETF(511090.SH) 与 10年国债ETF(511260.SH) 两个品种。 > 直接沿用「现存策略 + GA 参数空间」回测时,曲线异常、多数策略失效,故本专项调研国债该类「稳定向上、低波动」品种的专属择时策略、更长历史数据锚定方案与参数空间扩展。
---
国债是低波动慢牛品种,普通权益类择时策略(含成交量类)在这里几乎必然失效:短回看期策略无险可避、时间加权年化在短序列上过拟合、成交量在债券上是纯噪声。有效方向是拉长回看期的趋势跟踪族 + 波动率目标化,并用中债长序列财富指数替代只有 3 年的 ETF 数据做锚定。
---
| 品种 | 代码 | 追踪指数 | 备注 |
| ------ | ------ | --------- | ------ |
| 30年国债ETF | 511090.SH | CSI009036(中债-30年国债指数) | 上市仅约 3 年,数据过短 |
| 十年国债ETF | 511260.SH | CSI930675(中债-10年国债指数) | 数据约 8 年 |
| 国债ETF | 511010.SH | SSE000012(上证5年期国债指数) | 参考对照 |
> 结论:511090 只有 ~3 年数据,直接 GA 必然过拟合 → 必须寻找更长历史的锚定序列。
---
---
用 akshare 中债官方总收益指数(ak.bond_treasury_index_cbond(indicator='财富', period=...))作为锚定序列:
| 锚定代码 | 中债指数 | 起始 | 行数 | 对应 ETF |
| --------- | --------- | ------ | ------ | --------- |
CBOND30Y | 中债-30年国债财富指数 | 2011-01-04 | ~3939 | 511090.SH |
CBOND10Y | 中债-10年国债财富指数 | 2008-01-02 | ~4690 | 511260.SH |
CBOND30Y 与 511090 日收益:相关 0.887、β 1.03 → 锚定序列能高度代表 ETF 走势,可用于长历史回测。
在 df_loader.py fetch_data 增加分支:
CBOND_TREASURY_PERIODS = {
'CBOND30Y': '30Y', 'CBOND10Y': '10Y', 'CBOND7-10Y': '7-10Y',
'CBOND5Y': '5Y', 'CBOND0-1Y': '0-1Y', 'CBOND0-3Y': '0-3Y', 'CBOND1-5Y': '1-5Y',
}
# fetch_data('CBOND30Y') → 直接走 akshare,open=high=low=close、volume=amount=0
调用方式与普通 code 完全一致:fetch_data('CBOND30Y', start_date, end_date),后处理(缺失补充、新鲜度检查)对 CBOND 友好(末日期 2026-10-10,不触发滞后告警)。
---
| # | 策略族 | 核心逻辑 | 期次 | 实现状态 |
| --- | ------- | --------- | ------ | --------- |
| 1 | 长周期时序动量(TSMOM) | close.pct_change(lookback) > threshold 进场,短周期转负出场 | Phase1 | ✅ 已实现 |
| 2 | 唐奇安通道突破(Donchian) | 突破 N 日新高进场、跌破 M 日新低出场 | Phase1 | ✅ 已实现 |
| 3 | 波动率目标化(VolTarget) | 按已实现波动率倒数缩放仓位(小数仓位) | Phase1 | ✅ 已实现 |
| 4 | 绝对动量 + 长均线过滤 | TSMOM 叠加长均线方向过滤 | Phase1 | ✅ 已并入策略 1/2 的 ma_filter_window |
| 5 | Carry / 期限利差 | 用中债收益率曲线斜率/期限利差择时 | Phase2 | ✅ 已实现 CarryTermSpreadStrategy(30Y 入池;10Y 未过门控,见 §7.3) |
---
三个纯价格策略,均照 VolatilityChannelStrategy.py 的无未来函数范式:所有指标输入 .shift(1),末尾 position.shift(signal_offset)。
| 策略类 | 文件 | 参数 |
| ------- | ------ | ------ |
LongTermMomentumStrategy | LongTermMomentumStrategy.py | lookback(120~750) / threshold(0~0.05) / exit_lookback(20~250) / ma_filter_window[0,120,250,500] |
DonchianBreakoutStrategy | DonchianBreakoutStrategy.py | entry_window(60~750) / exit_window(20~400) / ma_filter_window[0,120,250,500] |
VolTargetStrategy | VolTargetStrategy.py | vol_window(20~250) / target_vol[0.03~0.08] / trend_window[0,60,120,250,500] / max_position(0.3~1.0) |
现存权益类参数空间窗口多在 20~200 日;国债需整体拉长到 60~750 日才覆盖论文建议的 >3 年回看期。已在 params_grid.py 末尾新增三个专属参数 dict。
> ⚠️ GA 构造契约:ParamsOptGeneticDevelop.evaluate() 里 strategy_class(strategy_df, params),params 的 key 直接来自参数 dict → 策略 __init__ 形参名必须与参数 dict 的 key 完全一致**,否则 TypeError。
| 策略配置 | trades | 胜率 | 累计收益 |
| --------- | -------- | ------ | --------- |
| LongTermMomentum(lookback=252, ma=250) | 12 | 0.50 | +119.0% |
| LongTermMomentum(lookback=500, exit=120) | 6 | 0.50 | +98.4% |
| Donchian(252/126, ma=250) | 7 | 0.57 | +111.9% |
| VolTarget(60, 0.05, ma=250) | 319 | 0.55 | +117.2% |
| VolTarget(20, 0.04) | 493 | 0.53 | +124.6% |
| buy&hold 基准 | — | — | ~+155% |
> 单策略均低于 buy&hold 属正常——择时牺牲部分收益换取暴露/回撤下降。真实价值由 GA 多目标(年化/时间衰减年化/凯利/回撤/资金占用)择优。
---
strategy_configs:删除 3 个成交量类(VolumePriceMovingAverages / VolumeFourMovingAverages / VolumeRSIAndMA),追加 3 个国债策略。codes_list 改为 [['CBOND30Y'], ['CBOND10Y']](支持 GA_CODES=CBOND30Y 环境变量拆并行)。
# 沙箱内运行 venv 必须剥离 PYTHONHOME/PYTHONPATH
/usr/bin/env -u PYTHONHOME -u PYTHONPATH .venv/bin/python \
-m unittest adata.aohan.quant.backtest.deep_learning.params_deep_seek.ParamsDeepSeek.test_opt_mult_market_params
# 按标的分进程并行
GA_CODES=CBOND30Y /usr/bin/env -u PYTHONHOME -u PYTHONPATH .venv/bin/python -m unittest ...
GA_CODES=CBOND10Y /usr/bin/env -u PYTHONHOME -u PYTHONPATH .venv/bin/python -m unittest ...
结果输出目录:adata/aohan/quant/backtest/deep_learning/genetic_opt_params/
---
| # | 事项 | 结果 |
| --- | ------ | ------ |
| 1 | Carry / 期限利差策略 | ✅ 已实现 CarryTermSpreadStrategy(接中债收益率曲线 ak.bond_china_yield,用长端-短端利差 generating 仓位),并入 params_grid;30Y 体入池(tenor=30, signal_mode=1, window=221, threshold=0.0551) |
| 2 | 未来函数复检 | ✅ 全部入池策略过 shift(1) 关卡:位置序列 pos.shift(1) 对比 baseline 保留率 ≈ 100%,无未来函数(复用 workspace/_check_future_func.py) |
| 3 | 入池 | ✅ 已写 timing_config.py:511090(30Y)9 条、511260(10Y)7 条,weight 口径 = 估分/100,形式对齐既有择时品种(详见 §7.3) |
| 4 | ETF 实盘映射 | ✅ 映射关系确立:511090 ↔ CBOND30Y(相关 0.887 / β 1.03)、511260 ↔ CBOND10Y;实盘卡片用真实 ETF 展示,GA 用长序列优化参数 |
| 5 | 成交量类最终处置 | ✅ 国债回测中剔除;本期保留 params_deep_seek.short_strategies 中的引用(不影响国债),归为待清理低优先项(见 §7.2-④) |
..._261013.csv)。
- 结论口径:单看 100 代结果与旧池互有高低(GA 随机性所致),故采用 「32 代 ∪ 100 代」合并集:按 (code, strategy, individual) 去重取 fitness 最大,逐策略在门控内重取估分最高解 → 合并集严格不劣于旧池。解析脚本 workspace/_score_treasury_ga.py(merged 模式)。
- 净收益:6 条策略估分提升并更新入池 —— 511090 的 RSIAndMA(52.7→60.6) / CarryTermSpread(49.6→54.4) / VolTarget(17.0→21.6);511260 的 DonchianBreakout(20.2→29.7) / LongTermMomentum(18.3→23.2) / VolTarget(13.9→17.8)。其余策略维持原解(不劣化)。
- ⚠️ 解析脚本 BUG(本轮已修):合并模式曾误把 passed_sorted 写成对未排序 rows 过滤,导致取到「插入顺序第一个门控通过者」而非估分最高者(曾误报 CBOND30Y/LongTermMomentum 最优 37.4,实为 49.83)。已改为 pv = [r for r in rows_sorted if gate(r)]。
- ⚠️ 入口纠错:params_deep_seek.py 没有 main() 也没有 if __name__=='__main__',唯一可执行入口是 unittest 方法 ParamsDeepSeek.test_opt_mult_market_params。之前文档里写的 -c "exec(open(...).read()); main()" 会 NameError,不可用。正确命令:
``bash
# 沙箱内运行 venv 必须剥离 PYTHONHOME/PYTHONPATH;单进程按标的分拆
GA_CODES=CBOND30Y GA_GENERATIONS=100 GA_POP_SIZE=100 \
/usr/bin/env -u PYTHONHOME -u PYTHONPATH .venv/bin/python \
-m unittest adata.aohan.quant.backtest.deep_learning.params_deep_seek.ParamsDeepSeek.test_opt_mult_market_params
`
(GA_CODES/GA_STRATEGIES/GA_GENERATIONS/GA_POP_SIZE 支持按标的/策略/代数/种群并行拆分;结果 CSV 位于 .../genetic_opt_params/,文件名 {code}_{Strategy}_params_opt_0_{MMDDYY}.csv,每代追加一行且多次运行会追加表头行,分析时需过滤 individual=='individual' 并去重。)
在国债上零交易 —— 根因已实证定位(本轮解决):
- 策略数学:夹角由原始价差 MA.diff() 经 arctan 得到(calculate_angle),是尺度相关量,不随价格归一化。
- 尺度效应:债券价格 ~100、日均线变动仅 ~0.01–0.1 → 夹角只有 1–6°;而权益指数(如 HSI 数万点)日均线变动数十点、arctan 饱和接近 ±90°,夹角差常达数十度。
- 实证(workspace/_probe_angle_trades.py,复刻 GA 评估契约):
- CBOND30Y / CBOND10Y:随机 60 组 GA 参数 → 成交率 0%(近全部 No trades occurred)。
- 手工把 angle_buy=angle_sell=1 并让买/卖窗口配对、穿透方向取 more/less 时:CBOND30Y 出 91 笔、CBOND10Y 出 81 笔 → 说明不是信号算不出来,而是随机参数命中不了。
- 对照组 HSI:随机 60 组 成交率 50% → 证明该失效是低波动/低价格品种特有。
- 结论:非国债数据缺陷,而是「夹角尺度相关」+「11 维搜索空间可行域过窄」的叠加,在低波动品种上被放大到成交率≈0。
- 处置:不改动该共享策略(对 arctan 前做归一化会改变其在所有现存品种上的行为,回归风险高,且非本专项目标);维持不补入池。若日后要救活,方向是「斜率归一化(除以价格或滚动 std)后再取角度」。
资金占用指标修复 —— 已落地(本轮复验):初版 calculate_funds_usage_rate() 对分数仓位统计恒为 0 → 已改为 position > 0 判定,对二值仓位策略结果不变,对波动率目标这类分数仓位策略才能正确反映资占(已复验代码在位)。
- params_deep_seek.strategy_configs(GA 长侧共享清单)中,成交量类(VolumePrice* / VolumeFour* / VolumeRSI*)与 DateBasedStrategy 已在 commit 2198a82 被整批移出,换入 4 个国债策略主体 —— 即此清单现已不含成交量类策略。⚠️ 注意:该清单为全 code 共享,故该移除对权益品种 GA 也生效;如后续要重跑权益 GA 需按需补回。
- short_strategies(仅 enable_short=True 时生效)仍引用 2 个成交量策略,国债回测 enable_short=False 不受影响。
- 结论:国债侧「剔除成交量类」目标已达成;是否对 short_strategies 做全局物理删除维持待定(会波及短侧其它品种,非本专项必需,风险>收益)。
条目已注释停用(timing_config.py L836-846)。真实构建 build_allocation_manager() 核查(workspace/_verify_treasury_board.py):511090 实注册 9 条(权重和 5.15)、511260 实注册 7 条(权重和 2.32),归一化占比正确;且无其他 board 含 511260/511090(default_code_list 也未含),无重复注册或状态污染。
对 (年化, 时间衰减年化, 凯利, 平均持仓回撤(取绝对值), 资金占用) 重打分;weight = 估分/100(board 内归一化,属相对权重)。
- ⚠️ 口径纠偏(2026-10-13 校准):第 4 项权重乘的是 avg_average_holding_drawdown 的绝对值,不是 avg_max_drawdown(见 ParamsOptGeneticDevelop.py L366-381 的 WEIGHT_NAMES 顺序:annual → exp_decay → kelly → average_holding_drawdown → funds)。用旧池 261012 CSV 反推已逐条复现注释估分(如 FourMovingAverages 81.6 / Donchian 80.5 / DualGroup 78.1 …),校准脚本 workspace/_score_treasury_ga.py。
- 未标注的策略沿用 32 代解(合并集下未找到更优解,估分与旧池持平),确保「严格不劣于旧池」。
的 codes_to_process 加入 511090 / 511260(原 L2192 注释 # '511260' 国债暂时搁置 已移除)。 的 _classify_board A股白名单补 511090(原已有 511260)。 合并式生成两张择时卡片(charts/timing/511090.html / 511260.html)并写回 meta.json(26→28 品种),避免整套 push 管线重跑且保留其他品种条目。 页正常渲染两卡片(名称/板块正确);/timing/chart?code=511090 与 511260 页 plotly 均在 srcdoc iframe 内正常绘制(3 子图 / 13 SVG 层)。
用生产回测引擎 run_baseline 对现状策略池跑完整回测(脚本 workspace/_run_treasury_backtest.py),四个口径全部跑通:
| 标的 | 区间 | 策略池 年化 / 最大回撤 / Calmar / 夏普 | 买入持有 年化 / 最大回撤 / Calmar / 夏普 |
| ------ | ------ | ------ | ------ |
| 511090 真实ETF | 2023-06 ~ 2026-10(3.3y) | 5.99% / -4.29% / 1.40 / 1.03 | 7.92% / -9.30% / 0.85 / 0.99 |
| 511090 代理CBOND30Y | 2011-01 ~ 2026-10(16.2y) | 5.56% / -5.09% / 1.09 / 1.11 | 5.94% / -16.37% / 0.36 / 0.85 |
| 511260 真实ETF | 2017-08 ~ 2026-10(9.1y) | 2.54% / -2.51% / 1.02 / 0.59 | 3.37% / -4.62% / 0.73 / 0.70 |
| 511260 代理CBOND10Y | 2008-01 ~ 2026-10(19.3y) | 4.05% / -3.76% / 1.08 / 1.43 | 4.32% / -6.82% / 0.63 / 1.12 |
结论(诚实口径):
两列,DataCacheManager.load_best 直出的 df 缺 OHLC,processor 会抛 KeyError: 'Last row is missing required column: open'。脚本通过 monkeypatch engine.load_backtest_df,对 CBOND30Y/CBOND10Y 改走 df_loader.fetch_data(其 CBOND 分支补齐 open=high=low=close、volume=amount=0)绕过。---
> 背景:用户发现界面「策略的昨/今/明仓位」在不同数据路径下用了不同窗口(部分仍是最新 1 年),长窗口策略(尤其国债)在 1 年窗口下暖机不足会算错。
,默认 10 年)| 路径 | 位置 | 修复前 | 修复后 |
| ------ | ------ | -------- | -------- |
| A. DB 昨/今/明仓位(界面展示数据源) | show_marker.py L2384-2399 bt_data 预取 | 非国债已用 self.strat_date | 保持(国债走 TREASURY_TIMING_ANCHOR 长历史锚定) |
B. timing 图表 HTML(charts/timing/*.html) | show_marker.py L3309 full_start | datetime.now() - 365天 | self.strat_date(默认 10 年) |
| C. 截面选基引擎 | show_marker.py L2296 | years_back=1 | years_back=DEFAULT_STRATEGY_WINDOW_YEARS |
的 start_date 形参只在 df is None 时才用于拉数据;show_marker 传入 df=df_pre,故真实窗口由 bt_data 决定。同理 generate_timing_chart_html() 传入 df 时 start_date 失效 —— 改窗口必须改 df 的来源,而不是改形参。、FourMovingAverages(…169)、VolTarget(vol_window 20~250) 等长窗口策略,在 1 年 ≈ 242 交易日 < 250 的窗口下指标暖机区占比过高,末段仓位系统性失真 → 图表曲线末点 ≠ 页面显示的 昨/今/明 仓位。国债尤甚(论文建议回看期 > 3 年,见 §4.1)。 中 511090/511260 已按长窗口写入(各 9/7 条)。
原实现只选单一「最优」源,排序键(新鲜 + 行数足优先)让短源压过长源 → 长窗口请求实际只拿到 1 年数据。):按优先级多源互补拼接 —— 重叠日以「最优源」为准,非重叠日补足历史;护栏 _sources_compatible 拦截量级错配源(如「指数点位 vs ETF 价格」,拼接处断裂校验)。)、HSI→9076 行(tushare+excel)。132 个多源 code 全量扫描:无重复日期、无 NaN、无新增坏跳变(25 个 BIGJUMP 经逐一比对,峰值/日期与合并前逐字节一致 → 均为源自身未复权遗留问题,非合并引入);护栏正确跳过了 510050.SH/510880/512100/980092.CNI 等量级错配源。
ETF 场内价会因分红除息产生向下缺口,直接拿 ETF 价做长窗口趋势信号会误判为「下跌」。故国债择时锚定中债官方总收益财富指数(含息、无除权缺口,见 §3.1),与 GA 优化同口径。
---