策略
US 股票月末效应季节性——基于 QQQ 的多空指数择时:捕捉有记录的月末收益集群(做多),在看跌状态下做空月末效应后的月中窗口 ONLY;由日历驱动,拟合参数近乎为零(QQQ.USEQ,日线,2 参数)
结局: 已放弃
QqqTurnOfMonthSeasonalityLS
avg_trade_return_pct 0.203% 高于 USEQ 成本底线(并非费用导致的失败)
假设
一种 LONG-SHORT、SINGLE-INSTRUMENT 的日历季节性指数择时策略,应用于 QQQ.USEQ(Nasdaq-100 ETF)。这类策略在工厂的策略库中完全缺席,并且从结构上避开了此处最大的两类风险(过拟合和扣除费用后经济性不足)。信号是 CALENDAR,而不是拟合出来的价格阈值:月末效应(ToM)——即股票指数收益中不成比例的一部分集中在从每月最后一个交易日至次月最初约 3 个交易日的窗口内——是股票市场中最稳健、样本外持续性最强的异常之一(Ariel 1987;McConnell & Xu 2008,已在数十年和数十个市场中得到验证)。由于进出场日期由 DETERMINED BY THE CALENDAR 决定,而非根据价格优化,使此前导致 338 个假设失败的过拟合空间几乎不存在——真正自由的参数只有 2 个(趋势状态 SMA 长度和一个启用做空的开关),ToM 窗口边界取自已发表的研究,而非参数扫描。策略使用 QQQ 日线数据,历史跨度超过 25 年(自 1999 以来为 QQQ),足以进行真正稳健的多窗口滚动前推验证和实际留出测试。方向为 LONG-SHORT(填补代表不足的 long_short 类别),既非 NOT 市场中性策略(0.7% 生存),也非横截面轮动(0/125、L68),更非趋势跟随动量策略——而是对单一流动性充足的 ETF 进行方向性日历择时。为何选择 USEQ 而不是加密货币:每个窗口捕捉到的 ToM 收益仅约 0.3-0.6%;免佣金的 USEQ 执行(每次往返约 0.02-0.05%,仅计点差和冲击成本)可以保留几乎全部收益,而加密货币的 0.10-0.20% 费用加上 24/7 行情(没有驱动 ToM 效应的月度结算/再平衡资金流)会将其全部抹去。FEE ARITHMETIC:历史上,ToM 多头窗口在 QQQ 上平均收益约为 0.3-0.6%,窗口约有 4 个交易日;扣除约 0.04% 的往返成本后,可捕捉约 0.3-0.55%,约为有效成本底线的 8-14x 倍,且高于 0.15% 可行性门槛。每年约有 12 笔多头交易(每月一笔),加上有条件的空头腿——在 25 年内足以提供有意义的显著性检验样本,同时不会承受任何高频费用拖累。
迭代 2——针对 Layer-2 故障进行单一的定向修复,没有其他改动。崩溃错误为 TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given:Layer-2 代理通过 types.MethodType 将每个可调用对象复制到普通包装器上,这会把 @staticmethod 变成 BOUND 方法,因此实例被作为第一个位置参数传入。修复内容:(1) 策略不再调用基类静态辅助函数 self._bar_ts(bar),而是通过自身的实例方法 _ts_of(bar) 读取时间戳(优先使用 ts_event,并以 ts_init 作为回退,语义相同);(2) 三个日历辅助函数(_days_from_ns、_civil_from_days、_days_in_month)也从 @staticmethod 改为普通实例方法,原因相同:否则下一次调用时会遇到完全相同的错误绑定。其他内容均未触及:导入未改变(仍只有 math、collections、decimal、nautilus_trader、src——没有禁用模块,因此 Layer 1 仍会通过),信号逻辑、日历窗口、进出场规则、仓位规模和配置与上一轮完全一致,逐字节未变。底层设计与迭代 1 相同:触发条件由日历而非拟合出的价格水平决定(2 个自由参数;regime_sma_len 在 _param_bounds 中声明,因为代码会对其进行钳制,另有 short_enabled 开关);交易日日期通过纯整数民用日期算法从 K 线时间戳推算,并平移 5 小时,使其在 EST 和 EDT 下都对应纽约日历日;月份内交易日序号根据观测到的 K 线计数,因此节假日和半日交易无需日历表;任何部分都不依赖相对于进程的 K 线计数器,所以该时间表在重启、重放和回测时都保持一致。
结构性低夏普的 USEQ 日历异常,没有稳健优势可供优化。基础夏普比率为 0.237,自助法置信区间为 CI [-0.11, 0.60],跨越零——在考虑任何优化筛选偏差之前,统计上就无法与无技能区分;经折减和留出测试后只会更低。profit_factor 1.195 低于 1.2 的有意义优势标准;在近乎无摩擦的 USEQ 上,这意味着扣除点差后月末效应几乎不存在,而非被费用压制(L75)。在 27 年内,它的回报仅约 116%(约 2.9%/年),信息比率为 NEGATIVE(-0.296),因此未能跑赢买入并持有;有条件的空头腿则拖累表现(short_win_rate 0.457),仍会承受较大的方向性亏损(2002 -33.5%、2001 -14.5%)——免于回撤的前提并不成立(最大 DD 43.7%,CI 至 87.8%)。仅有 2 个自由参数,无法将 0.24 夏普比率提升至可晋级且稳健的水平。这是 L75 USEQ 月末效应零存活模式;应在 BACKTEST_REVIEW 放弃,而非继续迭代。
实现
基于日线 QQQ.USEQ 的日历季节性指数择时,多空双向交易。LONG 腿:在每月最后一个交易时段收盘时买入(若错过该收盘,则在新月前两个交易时段之一买入),并在新月第 3 个交易日收盘时退出,以捕捉已记录的月末收益集群。SHORT 腿(可选,按状态决定):做空月末效应之后的月中低迷期,即当月第 7-15 个交易日,仅在 QQQ 明显低于其 200 交易时段的状态 SMA 时交易;窗口结束或看跌状态消失时平仓。calculate_signal 每根 K 线都会给出连续的日历接近度分数——在 ToM 窗口内达到峰值的指数型波瓣减去一个根据看跌状态强弱缩放的月中波瓣;should_enter 根据该分数设置阈值。仓位按资本相对值计算为整数股(多头使用权益的 95%,空头使用 50%,杠杆为 1.0),仅有的基于价格规则是 5% 灾难止损。每年约有 12 笔多头交易,另加有条件的空头交易。
验证结果
CLEAN RESTART 2026-09-04 — 本次运行的判定历史和学习记录已删除,并从验证阶段重新启动。此前放弃该策略是由流水线导致的,而非市场所致:Layer-2 测试框架错误绑定了 @staticmethod 辅助函数(已修复),QA 在未经优化的冒烟测试上给出了终局性能判定(已移除——QA 现在只判定正确性),沙箱超时则源于回测时段资源不足(已修复)。假设和策略代码均未改变。请根据代码本身的优劣进行验证;性能将在之后由完整回测和优化器决定。
将 short_enabled True 与 False 作为首要分支进行优化;若空头腿扣除尾部风险后的夏普比率未能改善 OOS,则采用仅做多的 ToM 策略。
按状态决定的空头腿(19 笔沙箱交易)是主要的尾部风险来源,也是已发表异常所支持最少的部分(ToM 是多头效应;月中看跌做空是开发者添加的覆盖逻辑)。这不是缺陷,但它扩大了回撤 CI(max_dd_ci_high 47%),且在当前样本中其优势尚未得到证明。不构成阻碍。
回测评审
- 夏普
- 0.24
- 总收益率
- 116.03%
- 最大回撤
- 43.70%
- 总交易数
- 460
- 胜率
- 55.7%
- 盈利因子
- 1.20
avg_trade_return_pct 0.203% 高于 USEQ 成本底线(并非费用导致的失败)
460 笔交易,跨度 27 年——样本量足够,且由日历驱动,过拟合空间较小
市场贝塔较低(0.087)——回报并非只是指数敞口
夏普比率为 0.237,自助法置信区间 CI [-0.11, 0.60] 跨越零——在任何优化之前都不存在统计上稳健的风险调整后优势
profit_factor 1.195 低于 1.2 有意义优势阈值(L75:近乎无摩擦的 USEQ + PF<1.2 意味着异常并不存在,而非被费用压制)
information_ratio -0.296:按风险调整后的主动收益衡量,表现逊于买入并持有;total_return 在 27 年内仅约 116%(约 2.9%/年)
有条件的空头腿净拖累表现(short_win_rate 0.457),且策略仍遭受较大的方向性冲击(2002 -33.5%、2001 -14.5%),与免于回撤的主张相矛盾
max_drawdown 43.7%(CI 至 87.8%),相对于低于 -3%/年的回报
符合 L75 USEQ 月末效应零存活模式
迭代历史
验证失败(Layer 2——合成情景): 使用的参数:['decay', 'bear_ref', 'tom_days', 'short_end', 'long_thresh', 'short_start', 'warmup_bars', 'min_notional', 'short_thresh', '_param_bounds', 'long_exposure', 'short_enabled', 'stop_loss_pct', 'regime_sma_len', 'short_exposure'] 请检查 __init__ 是否通过 self.parameters.get() 设置了所有属性。 - steady_uptrend: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - steady_downtrend: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - flat_ranging: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - volatility_spike: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - zero_volume: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - price_gap: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000)
Stratmill 是研究与模拟交易工具,不提供财务建议,也不是经纪商。回测与模拟结果均为假设性结果。交易存在亏损风险。