跳至正文
全部策略

策略

Double-7s 组合(pinkfish,F. Aultman,基于 Connors 与 Alvarez):当收盘价高于200-SMA且处于7日收盘低点时买入;处于7日收盘高点时卖出;在SPY/QQQ/DIA/IWM上等权配置,使用USEQ日线柱,仅做多

结局: 已放弃

Double7sPortfolioUseq

结局总结

Double7sPortfolioUseq 将 Connors/Alvarez 的 Double-7s 规则应用于四只广泛覆盖市场的US指数 ETF,在零佣金的USEQ交易场所仅做多并等权配置,采用200-SMA市场状态过滤器。它始终未通过验证:所有3次第3层沙盒冒烟测试都在300秒时超时,包括已将每根柱工作改为增量计算的第2次迭代。第-3次迭代的性能分析记录显示,耗时来自回测运行器扫描目录工具,而非策略。运行在回测、优化、分析师或风险审查开始前便被放弃,因此从未测得其优势。

假设

忠实呈现 pinkfish 的“double-7s-portfolio”示例(Farrell Aultman,MIT许可;https://github.com/fja05680/pinkfish/blob/18a0d557ead25e0e82de3ece86bcab6c325a59ed/examples/portfolios/double-7s-portfolio/strategy.py).)。它将 Larry Connors 和 Cesar Alvarez 在《Short Term Strategies that Work》中的 Double 7规则应用于 ETF 组合。该规则仅做多,基于日收盘价进行均值回归:当ETF高于其200日SMA时,若收盘价处于7日收盘低点则买入;收盘价处于7日收盘高点时卖出。原示例针对 ETF 编写,因此使用零佣金的USEQ交易场所和有数十年历史的DAILY柱。原示例从调用方获取代码列表,不会硬编码列表。我们采用 Connors 测试该规则时使用的四只广泛覆盖市场的US指数 ETF:SPY、QQQ、DIA和IWM。共4个标的,符合课程173提出的约10个标的上限。列表是固定选择,但这四只都是自1993-2000以来一直存在且保持流动性的指数追踪基金,因此不存在从单只股票中进行幸存者筛选。默认选项完全照搬原示例:period=7,stop_loss_pct=1.0(原示例将其读取为100%,因此止损位于0且永远不会触发;仍保留该参数),margin=1(不使用杠杆),use_regime_filter=True,use_vola_weight=False(等权1/N),use_adj=False。此处无法遵循use_adj:我们的USEQ价格已按拆股调整,盈亏中不包含股息。这是与原示例已知且幅度较小的偏差,因为原示例使用未调整的收盘价,pinkfish 自身的账目也不计入股息。费用计算:USEQ不收佣金,这些 ETF 的价差和冲击成本往返约为0.02-0.05%。已发布的 Double-7s 在SPY上的结果显示,持仓3-8个交易日的平均单笔交易毛收益约为+0.6至+1.0%。即使发布后该收益衰减一半(约0.4%),仍约为成本的10x。预计该策略在4只 ETF 上的生命周期交易量为每年每ETF约8-12笔,持续约25年,因此总计约800-1,100笔交易。策略有空仓状态。每只ETF约有25-35%的时间处于市场中。当没有ETF处于高于其200-SMA的7日低点时,整个组合为空仓;在收盘价低于200-SMA的熊市阶段,组合也会空仓。

第3次迭代,属于第-3层sandbox_timeout。交易规则与第2次迭代完全相同;仅修改了文档字符串,记录诊断结果。我通过run_backtest_in_subprocess在相同的2400天、4个标的窗口上运行第2次迭代的原样代码,并用 py-spy 对子进程进行采样。所有墙钟耗时都花在 src/backtesting/runner.py:535 -> ParquetDataCatalog.instruments(instrument_ids=...) -> fsspec glob 上;该操作会遍历 data/catalog/data 中每种工具类别目录,包括56,006个binary_option(Polymarket)目录。主机负载在18核上约为77。子进程使用了约9%个CPU,受磁盘 I/O 阻塞;达到900秒限制(3x沙盒预算)时,扫描仍未结束,尚未处理任何柱。策略每根柱的计算成本已为常数:每个标的使用带滚动和SMA200的 deque,并进行 O(7) 的最高/最低价计算;每个标的约有2.4千根日线柱。因此,这次超时源自目录和主机基础设施(课程155 (b)),而非策略代码,修改代码无法解决。应在运行器中修复:按类别/标的 ID 查询工具,而非对整个目录使用 glob;或者清理 Polymarket 目录。与之前相同,与原示例存在以下偏差:无法遵循use_adj(USEQ价格已按拆股调整,盈亏不计入股息);数据结束时的强制平仓交由引擎在停止时收盘;use_vola_weight=False(等权),符合原示例默认设置。

verification_loop:sandbox_timeout:验证失败(第3层——沙盒回测):冒烟测试在约20000根柱的窗口(SPY.USEQ-1-DAY-LAST-EXTERNAL中的2400天)上超过了300秒的墙钟时间限制。这通常意味着每根柱都执行随历史长度增加而变慢的工作,例如反复扫描完整的资金费率/补充序列,或在每根柱上于calculate_signal()/on_bar()内重建列表并调用 min()/sorted()。请在 __init__ 中预先计算排序后的时间戳数组ONCE并使用 bisect,或按时间戳缓存查询结果,使每根柱的计算成本为 O(log n) 而非 O(n)。

实现

Stratmill 对 pinkfish 的 double-7s-portfolio 的翻译(F. Aultman,MIT)。使用 Connors 与 Alvarez 的 Double 7规则,应用于SPY/QQQ/DIA/IWM日线USEQ柱,仅做多,按1/N 等权配置。当ETF收盘价处于7日收盘低点且高于其200日SMA时买入;收盘价处于7日收盘高点时卖出,或跌破止损时卖出(默认stop_loss_pct=1.0将止损设在0,因此永不触发,与原示例一致)。

验证结果

sandbox_timeout:验证失败(第3层——沙盒回测):冒烟测试在约20000根柱的窗口(SPY.USEQ-1-DAY-LAST-EXTERNAL中的2400天)上超过了300秒的墙钟时间限制。这通常意味着每根柱都执行随历史长度增加而变慢的工作,例如反复扫描完整的资金费率/补充序列,或在每根柱上于calculate_signal()/on_bar()内重建列表并调用 min()/sorted()。请在 __init__ 中预先计算排序后的时间戳数组ONCE并使用 bisect,或按时间戳缓存查询结果,使每根柱的计算成本为 O(log n) 而非 O(n)。

结局总结

如果沙盒超时发生在首根柱之前的目录扫描阶段,而非策略代码中,那么重写策略也无法解决;需要修复基础设施,而不是继续反复修改代码。

经过3次迭代后,该项目在验证循环中被放弃:每次第3层沙盒回测都在2400天的SPY.USEQ日线窗口上超过300秒的墙钟时间限制。第-3次迭代的记录称,性能分析发现耗时来自 ParquetDataCatalog.instruments() 对目录的 glob 扫描(约56千个 Polymarket 目录);扫描发生在任何策略代码运行之前,并非每根柱的策略工作所致。

仅做多、等权的 Double-7s 均值回归组合,使用SPY/QQQ/DIA/IWM日线USEQ柱(翻译自 pinkfish 的 Connors 与 Alvarez 规则示例):当ETF高于其200日SMA时,若收盘价处于7日收盘低点则买入;收盘价处于7日收盘高点时卖出。

没有绩效数据。回测报告为空,未运行优化,也未生成收益率、夏普比率或交易笔数。

分析

double-7s-portfolio

Stratmill 是研究与模拟交易工具,不提供财务建议,也不是经纪商。回测与模拟结果均为假设性结果。交易存在亏损风险。