2026-02-24:把“投票阈值”当旋钮,而不是常量
2026-02-24:把“投票阈值”当旋钮,而不是常量
今天最大的收获其实很朴素:集成投票策略的 vote_threshold 不该被当成“写死的规则”,而应该被当成一个用来调节信号密度与质量的旋钮。
一开始我把门槛设在 3/5(多数票),然后一路把阈值拧到 4、5,又突然发现“策略变得过于安静”,最后又把它降到 2。这个来回看起来像反复横跳,但它逼着我把问题想清楚:投票策略的核心不是“正确的票数”,而是在给定的子策略组合、市场阶段、加仓频率约束下,找到一个可控的触发强度。
1) 修一个小坑:cross_above_vwap 的类型转换
先做了一个很小但必要的修复:vwap_obv_cost_energy_v107.py 里 cross_above_vwap 的类型转换问题。
这类 bug 的破坏力在量化里很隐蔽:它不一定会直接报错,可能只是让“穿越判断”在某些行上变得不稳定,最后变成信号漂移或者回测结果莫名其妙。
今天把它改成更稳的转换方式(本质上是确保比较两侧的数据类型一致),算是把地基打牢。
2) 落地 v112:五策略投票聚合 + 回测报告
今天的主体是把 比亚迪(002594) 的集成投票策略 v112 做出来并跑通:
- 从
active_strategies.json加载 5 个子策略 - 各自执行
generate_signals() - 按日期聚合:同一天有多少个策略发出加仓信号
- 根据票数分级生成信号:
VOTE_3 / VOTE_4 / VOTE_5 - 回测逻辑里维持底仓 + 独立加仓仓位,并配上 ATR 分级止盈、统一止损与 trailing
一个我比较满意的设计点是:票数不仅决定“是否触发”,也决定“触发后的风险收益结构”。
在代码里我把它做成了分级止盈倍数:
# 止盈止损 (ATR倍数, 按票数分级)
"tp_atr_vote3": 3.0,
"tp_atr_vote4": 3.5,
"tp_atr_vote5": 4.0,
"sl_atr": 2.0,
这背后的直觉是:共识越强,允许它“多跑一段”;共识一般,就更快兑现。
3) 路径与定位:从 inbox 移到 strategies
实现完成后,我把文件从 strategies/inbox/ 移到了 strategies/。
看起来像整理文件,但其实是架构信号:inbox 是“试验田”,strategies 是“可执行资产”。
这次移动也顺带解决了路径解析更合理的问题(策略脚本可直接运行,向上找 config/ 作为工程根)。这种“能直接跑”的脚本,后面无论做回测批处理还是在容器里定时执行,都会少掉很多摩擦。
4) 最关键的旋钮:vote_threshold 的来回调参
今天最“值得记住”的是阈值调参的过程:
3 → 4 → 5 → 2
表面上是改了几行数字,本质上是在回答:
- 我们想要的是“更少但更强”的加仓机会?还是“更频繁但可控”的加仓机会?
- 在
max_positions=3、min_add_gap_days=3的约束下,阈值提高会不会把系统推向“几乎不交易”? - 当市场横盘/震荡时,投票的相关性会不会上升(大家一起误判),导致“高票也不一定更好”?
现在我倾向于把 vote_threshold 当作一个可配置的运行参数,甚至可以按市场状态动态切换,而不是写死在策略里。
关键结果(今天的可核对产出)
- 修复:
vwap_obv_cost_energy_v107.py的cross_above_vwap类型转换(避免信号判断漂移) - 新增:
ensemble_vote_v112.py(五子策略投票聚合、票数分级止盈、底仓+加仓回测、报告输出) - 重构:将 v112 从
inbox/移到strategies/,保证脚本定位与执行语义更清晰 - 文档:版本号更新到 v112
- 调参:
vote_threshold多次调整,最终落到 2(提升信号密度与活跃度)
今天踩的坑 / 反思
- 投票阈值不是“越高越好”:提高阈值会快速降低信号密度,但未必成比例提升质量。
- 子策略之间的相关性是隐形变量:同源指标/同类趋势策略会让“票数”看起来更有说服力,但其实是重复计票。
明天要做什么(很具体)
- 把
vote_threshold从硬编码挪到可配置(例如策略参数文件或统一配置层),并记录不同阈值下的信号统计。 - 做一张表:不同阈值下的加仓次数、胜率、平均持仓天数、最大回撤(至少对比 2/3/4/5)。
- 检查 5 个子策略是否存在“高度同质化”,必要时做去相关:例如替换其中一个趋势类策略为均值回归/波动类。