读见

我试图用机器学习战胜标普 500,结果 bug 比模型教会我的更多

原题I tried to beat the S&P 500 with machine learning. The bugs taught me more than the model.

约 12 分钟约 2,996 字原文英文已翻译

读前速览

5 个要点

一次用机器学习战胜标普 500 的严谨尝试发现,数据 bug 夸大了结果;修复后,模型的优势在统计上不显著,并在 2020 年后消退。

  1. 预先登记通过/失败标准对于避免多重测试中的自欺至关重要。

    作者在每次测试前都写下了什么算通过,这防止了在结果令人失望时移动目标线。

  2. 三个隐蔽的数据 bug 夸大了模型的表面优势,修复后 t 统计量从 4.5 降至 2.5。

    退市损失被重复计算、泄漏测试无法检测基本面数据中的前视偏差,以及缺失数据本身就能预测退市,这些都让模型看起来比实际更好。

  3. 清洗后模型的更高收益几乎完全来自更高的风险,而非技能。

    模型组合的 beta 为 1.37,最大回撤为 -52.4%;其 alpha 为 +6.5%,t 统计量为 1.5,在统计上不显著。

  4. 基于当前指数或 ETF 持仓的回测存在后见之明偏差,无法告诉你你本可以赚到多少。

    限制在当前 SPUS 持仓中改善了结果,但同样股票的等权重篮子也跑赢了 SPY,表明是名单本身带来了额外收益。

  5. K 线形态在机器学习和基于规则的测试中都没有增加价值。

    添加 K 线特征略微降低了排名相关性,而四种经典看涨形态在统计显著性上未能击败无形态基准。

完整摘要

作者花了数周时间,用预先设定的通过/失败标准,针对标普 500 测试了 CAN SLIM 规则、动量、波动率目标、LightGBM 排序模型和 K 线形态。排序模型最初显示出 4.5 的 t 统计量,但审计发现了三个 bug:退市损失被重复计算、泄漏测试漏掉了基本面数据中的前视偏差,以及缺失数据本身就能预测退市。修复这些问题后,t 统计量降至约 2.5。清洗后的模型年化收益为 24.8%,而 SPY 为 13.8%,但 beta 为 1.37,最大回撤为 -52.4%;其 alpha 为 +6.5%,t 统计量为 1.5,并不显著。该优势在 2020 年后消退。限制在符合伊斯兰教法的股票中改善了回测,但等权重基准篮子同样跑赢了 SPY,表明存在后见之明偏差。K 线形态没有增加任何价值。作者冻结了一个模型,在 SPUS 持仓上进行前瞻性纸面测试,预设成功标准为:在 50 个决策日后,排名相关性 ≥ +0.02 且 t ≥ 2。主要教训是:把任何看起来不错的结果都当作 bug,直到它经受住攻击。

目录 · 12 节
查看原文

简短版 The short version

我没能构建出一个可以证明自己跑赢标普 500 的选股模型,而我发现最有用的东西,是一份漏洞清单——这些漏洞会让糟糕的结果看起来很出色。几周来,我测试了 CAN SLIM 规则、一项动量研究、一个波动率目标叠加策略、一个机器学习排名模型,最后还测试了蜡烛图形态。每项测试运行前,我都预先写明了通过或失败的判定标准。I could not build a stock picker I can prove beats the S&P 500, and the most useful thing I found was a list of bugs that made weak results look strong. Over several weeks I tested CAN SLIM rules, a momentum study, a volatility-targeting overlay, a machine-learning ranking model and, last, candlestick patterns. Every test had its pass/fail bar written down before it ran.

排名模型呈现出微弱但看起来真实的信号:秩相关约为 +0.03 至 +0.04,t 统计量为 2.5。作为投资组合,它的年收益率约为 20% 至 25%,而 SPY 为 13.8%。但其市场敞口约为 SPY 的 1.4 倍,最大回撤接近 -50%。其风险调整后收益与 SPY 在统计上没有显著差异。The ranking model has a small, real-looking signal: a rank correlation of about +0.03 to +0.04, with a t-statistic of 2.5. As a portfolio it earned roughly 20% to 25% a year against SPY's 13.8%. It did that with about 1.4 times SPY's market exposure and a worst drawdown near -50%. Its risk-adjusted return was statistically indistinguishable from SPY's.

因此,我冻结了一个版本,并开始对符合伊斯兰教法的股票进行前瞻性纸面测试,以 SPUS 为基准。测试于 2026 年 10 月 16 日开始,目前尚无结论。之后,我分别在模型内部和作为简单交易规则测试了蜡烛图形态,但它们没有带来任何增益。这篇文章将讲述我是如何走到这一步的、过程中出了哪些问题,以及我会和不会作出哪些主张。So I froze one version and started a forward paper test on Shariah-compliant stocks, benchmarked to SPUS. It began on 16 October 2026, so it has no verdict yet. Candlestick patterns, tested afterwards both inside the model and as plain trading rules, added nothing. This post is about how I got here, what broke along the way, and what I will and will not claim.

设置方法:先编写柱状图,然后运行测试 The setup: write the bar first, then run the test

目标很简单:在扣除成本后,以风险调整后的表现跑赢 SPY,并采用一套可以无人值守运行的规则。我使用了 EODHD 的每日价格数据和 SEC EDGAR 的公司申报文件。机器学习部分基于一张按日期整理的时点数据表,其中每个日期约有 450 只流动性较高的美国股票,包括已退市股票;自 2010 年以来,数据总计约 180 万行。The goal was simple: beat SPY after costs, on a risk-adjusted basis, with rules I could run unattended. I used daily prices from EODHD and company filings from SEC EDGAR. The machine-learning work ran on a point-in-time table of about 450 liquid US stocks per date, including delisted names, with roughly 1.8 million rows from 2010 onward.

纪律比任何模型都重要。每次测试前,我都会写下达到什么标准才算通过。通常,这意味着在 2015—2020 年和 2021 年至今这两个时期都跑赢 SPY,最大回撤不比 SPY 更深,而且夏普比率差值的自助法置信区间始终高于零。The discipline mattered more than any model. Before each test I wrote down what would count as a pass. Usually that meant beating SPY in both 2015-2020 and 2021 onward, a worst drawdown no deeper than SPY's, and a bootstrap confidence interval for the Sharpe difference that stayed above zero.

然后我运行了一次并报告结果,包括失败项。在同一段历史数据上测试几十次后,总会碰巧出现看起来不错的结果。固定标准是我所知道的唯一能防止自欺欺人的办法。Then I ran it once and reported the result, including the failures. After dozens of tests on the same history, something will look good by luck. Fixed bars are the only defence I know against fooling myself.

尝试 1:CAN SLIM 规则输给了无所作为 Attempt 1: CAN SLIM rules lost to doing nothing

在2020年至2026年间,以如今的标普500成分股为对象,我的纯价格版 CAN SLIM 六年亏损了12.1%,而 SPY 上涨了158%。我按自己的理解编写了这套方法:市场方向过滤条件、相对强度、底部形态、突破时的成交量、7%的止损线,以及持有8周的规则。这个测试样本本身就已经美化了该策略的表现,因为这些成分股都是幸存者。On today's S&P 500 members from 2020 to 2026, my price-only version of CAN SLIM lost 12.1% over six years while SPY gained 158%. I coded the method as I understood it: a market-direction gate, relative strength, base patterns, breakout volume, a 7% stop and an 8-week hold rule. That test list already flatters the strategy, because today's members are the survivors.

交易统计数据说明了原因。在 255 笔交易中,只有 16% 获利。仅有 37% 的交易曾达到 +5%,而达到 +20% 的只有 6%。大多数突破行情根本没有持续走高。The trade statistics showed why. Of 255 trades, only 16% won. Just 37% ever reached +5%, and only 6% reached +20%. Most breakouts simply did not run.

两项调整带来了改善。删除一条在市场“承压”时将止损位移至盈亏平衡点的规则后,亏损降至 3.5%。允许在市场承压时买入,但仍禁止在低于 200 日均线时买入,使 2015—2019 年留出样本期的收益从 +3.8% 升至 +34.3%,并将最大回撤从 23.3% 降至 13.4%。五年中有四年表现更好,但大部分利润来自其中一年(2017 年),而 SPY 的收益为 +72.9%。Two changes helped. Dropping a rule that moved stops to breakeven when the market was "under pressure" cut the loss to 3.5%. Allowing new buys in that pressure state, while still blocking them below the 200-day average, lifted a 2015-2019 hold-out from +3.8% to +34.3% and cut the worst drawdown from 23.3% to 13.4%. It was better in four of five years, but one year (2017) made most of the profit, and SPY made +72.9%.

真正令人意外的是这道筛选条件。仅在确认处于上升趋势时持有 SPY,2020 至 2026 年间的回报为 +24.6%,而买入并持有的回报为 +158%。只跳过低于 200 日均线的那些日子,仍能获得 +108% 的回报,并将最大回撤从 33.7% 降至 19.8%。这道筛选条件中真正有用的是 200 日均线。The real surprise was the gate. Holding SPY only in confirmed uptrends returned +24.6% over 2020-2026, against +158% for buy and hold. Skipping only the days below the 200-day average kept +108% and cut the worst drawdown from 33.7% to 19.8%. The 200-day line was the useful part of the gate.

添加严格的盈利和销售额筛选条件后,候选项减少了 96%,只剩下 18 笔交易,数量太少,无法得出任何结论。Adding strict earnings and sales filters removed 96% of candidates and left 18 trades, too few to say anything.

尝试 2:动能减弱,波动率叠加层得以保留 Attempt 2: momentum faded, the volatility overlay survived

排名动量策略通过了首次检验,却在遭遇攻击时彻底失效。我按过去 12 个月的回报率对股票进行排名,并跳过最近一个月。它以 3.3 的合并 t 统计量越过了入场门槛,但剔除 2010—2014 年表现最好的五个行业后,超额收益便消失了。它在 2003—2009 年的留出样本中也未能通过检验(t 值为 -0.2,最大回撤为 -61%)。Rank momentum passed its first test and then fell apart under attack. I ranked stocks on 12-month return, skipping the latest month. It cleared its entry bar with a pooled t-statistic of 3.3, but the edge vanished when I removed the five best industries in 2010-2014. It also failed a 2003-2009 hold-out (t of -0.2 and a -61% drawdown).

逐年拆分解释了原始结果:其超额收益的62%来自2024—2026年,而这段时间只占全部月份的12%。这恰恰是如今的指数成分股名单最偏向赢家的时期。A by-year breakdown explained the original result: 62% of its excess return came from 2024-2026, only 12% of the months. That is exactly where a list of today's index members is most biased toward winners.

波动率目标叠加策略表现更稳健,但并不能因此跑赢市场。该规则根据20日波动率调整SPY敞口,使其趋近于15%的波动率目标,敞口上限为1倍,并设有10个百分点的区间,交易在下一交易日收盘时执行。与固定配置80% SPY的组合相比,其夏普比率高出0.10,但置信区间(-0.03至+0.26)略微未能排除零值。The volatility-targeting overlay held up better, though not as a way to beat the market. The rule scales SPY exposure toward a 15% volatility target using 20-day volatility, capped at 1x, with a 10-point band and trades at the next close. Against a constant 80% SPY mix its Sharpe ratio was 0.10 higher, but the confidence interval (-0.03 to +0.26) just missed excluding zero.

不过,回撤方面的优势始终如一。最大回撤为 34.7%,而恒定配置组合为 49.7%;2008 年的回撤为 34.2%,而 SPY 为 54.8%。这是一种有效的风险控制手段,而非阿尔法收益的来源。The drawdown benefit was consistent, though. The worst drawdown was 34.7% against 49.7% for the constant mix, and in 2008 it was 34.2% against 54.8% for SPY. It is a sound risk control, not a source of alpha.

尝试 3:一个 t 统计量为 4.5 的排序模型 Attempt 3: a ranking model with a t-statistic of 4.5

我的第一个机器学习结果看起来非常出色,我差点就此打住。模型采用 LightGBM,训练目标是预测每只股票在同一天同类股票中的 20 日收益率排名。它使用了 28 个特征:动量、波动率、距高点的距离、流动性、贝塔、偏度,以及五个基于 SEC 文件构建的特征。My first machine-learning result looked excellent, and I nearly stopped there. The model was LightGBM, trained to predict each stock's 20-day return rank among its peers on the same date. It used 28 features: momentum, volatility, distance from highs, liquidity, beta, skew and five built from SEC filings.

我采用滚动前向方式进行了评估。每年一月,模型只使用过去的数据重新训练,并设置了 20 天的清除期;随后在 2015 年至 2026 年间,以互不重叠的 20 天持有期进行评分。I evaluated it walk-forward. Every January it retrained on past data only, with a 20-day purge, and then it was scored on non-overlapping 20-day holds from 2015 to 2026.

首次运行在两个半样本中的秩相关分别为 +0.053 和 +0.041。表现最好的五分位组每 20 天分别比最差的五分位组高出 1.45% 和 1.21%,合并后的 t 统计量为 4.5。它的表现优于由九种教科书式信号构成的固定复合指标,也优于简单动量策略。t 值达到 4.5,就该让你心生怀疑;它确实本该如此。The first run gave a rank correlation of +0.053 and +0.041 in the two halves. The best fifth beat the worst fifth by 1.45% and 1.21% per 20 days, with a pooled t-statistic of 4.5. It beat a fixed composite of nine textbook signals and plain momentum. A t of 4.5 is the kind of number that should make you suspicious, and it should have.

让模型看起来比实际表现更好的三个漏洞 Three bugs that made the model look better than it was

审计数据构建器后,模型的汇总 t 统计量从 4.5 降到了约 2.5。每个错误都不易察觉,而且每一个都会让结果显得更好。Auditing the data builder cut the model's pooled t-statistic from 4.5 to about 2.5. Each bug was quiet, and each one flattered the result.

  1. 退市损失被重复计入。股票消失时,表格会在最后一行记入 -30% 的损失。构建程序却在每个结束系列的最后两行都记入了这笔损失,其中也包括溢价收购的情况。破绽是,194 只股票代码中出现了 383 条这样的记录,恰好每只两条。由于模型学会了避开即将消失的股票,仅修正这一点就消除了约三分之一测得的优势。
    Delisting losses booked twice. When a stock vanishes, the table books a -30% loss on its last row. The builder booked it on the last two rows of every series that ended, including acquisitions at a premium. The giveaway was 383 such rows across 194 tickers, exactly two per ticker. Because the model learned to avoid stocks about to disappear, fixing this alone removed roughly a third of the measured edge.
  2. 一个看不见泄漏的泄漏测试。我的原始自测检查的是:截止日期之后的价格是否会改变更早的特征。我在基本面数据中植入一个前视偏差后,旧测试竟然通过了。我编写了一个新测试,移除截止日期之后提交的所有文件,结果它在我的真实数据上失败了。一个过去四季度的滚动总和,被当作在其中最新季度的日期就已公开,即使总和中的某个较早季度其实是后来才首次公布的。现在,只有四个季度的数据都已公开,滚动总和才会变为可用。
    A leak test that could not see the leak. My original self-test checked that prices after a cut date could not change earlier features. When I planted a look-ahead in the fundamentals, the old test passed it. I wrote a new test that removes every filing made after the cut date, and it failed on my real data. A trailing four-quarter sum was treated as public on the date of its newest quarter, even if an older quarter in the sum had been first reported later. Now a trailing sum becomes available only when all four quarters are public.
  3. 预测死亡的数据缺失。退市公司大多无法在美国证券交易委员会(SEC)的当前股票代码文件中找到匹配项,因此其基本面数据为空。后来退出股票池的股票中,62.3% 的数据行缺少基本面数据,而留在股票池中的股票这一比例为 9.1%。模型可能会把“空白”解读为“将会消失”。现在,缺失值会用随机排名填充,因此不携带任何信息。
    Missing data that predicted death. Delisted companies mostly had no match in the SEC's current ticker file, so their fundamentals were blank. Among stocks that later left the universe, 62.3% of rows had no fundamentals, against 9.1% for stocks that stayed. A model can read "blank" as "will disappear". Missing values are now filled with random ranks, so they carry no information.

反正该模型几乎不需要这些基础特征。移除全部五个基于 SEC 的特征后,其优势依然不变,因此最终的重点规格仅使用价格特征。The model barely needed the fundamentals anyway. Removing all five SEC-based features left its edge intact, so the final headline specification uses price features only.

真实数据:回报更高,单位风险回报不变 The honest numbers: more return, the same return per unit of risk

优化后的模型收益高于 SPY,但几乎所有超额收益都来自承担更高风险。表格展示了 2015—2026 年的情况:投资组合包含从流动性最高的 100 只股票中选出的 20 只股票;按照我的波动率规则,闲置资金投资于 SPY。交易成本为每边 5 个基点。The cleaned-up model earned more than SPY, but almost all of the extra came from taking more risk. The table shows 2015-2026 for a 20-stock portfolio drawn from the 100 most liquid names, with idle money held in SPY under my volatility rule. Costs are 5 basis points per side.

Portfolio Return a year Sharpe Worst drawdown Beta to SPY
SPY buy and hold 13.8% 0.83 -33.7% 1.00
SPY under the volatility rule 11.1% 0.88 -18.4% 0.69
Model portfolio 24.8% 0.92 -52.4% 1.37
Model, sold to SPY after two high-volatility days 21.5% 0.97 -35.9% 0.92

该模型的年化 alpha 为 +6.5%,t 统计量为 1.5,未达到显著水平。它相对于波动率规则的夏普比率优势的置信区间为 -0.33 至 +0.39,因此零值远在区间之内。The model's alpha was +6.5% a year with a t-statistic of 1.5, which is not significant. The confidence interval for its Sharpe advantage over the volatility rule ran from -0.33 to +0.39, so zero is well inside it.

优势也有所减弱。2015—2020 年,阿尔法为每年 +12.5%(t 值为 2.7)。从 2021 年起,阿尔法降至 -1.0%(t 值为 -0.1),该时期最大回撤为 52.4%,而 SPY 为 24.5%。The edge also faded. In 2015-2020 the alpha was +12.5% a year (t of 2.7). From 2021 onward it was -1.0% (t of -0.1), and the worst drawdown in that period was 52.4%, against 24.5% for SPY.

仅选择符合伊斯兰教法的股票让回测结果看起来更好,我对此并不信任。 Restricting to Shariah-compliant stocks made the backtest look better, and I do not trust it

我希望该系统只交易符合伊斯兰教法筛选标准、可以持有的股票,因此将范围限定为 SPUS(标普500伊斯兰教法ETF)的持仓。回测结果大幅改善:年化收益率为 28.6%,而 SPY 为 13.8%;阿尔法为 +10.2%(t 值为 2.7)。I wanted the system to trade only stocks I could hold under Shariah screening, so I limited it to the holdings of SPUS, the S&P 500 Shariah ETF. The backtest improved sharply: 28.6% a year against SPY's 13.8%, with an alpha of +10.2% (t of 2.7).

然后我看了基准表现。同一批股票构成的等权组合,完全没有使用任何模型,年化收益也比 SPY 高出 3.6 个百分点(17.4%,而 SPY 为 13.8%)。除非这份名单是事后挑选的,否则一个分散投资组合不应该有这样的表现。Then I looked at the baseline. An equal-weight basket of the same stocks, with no model at all, also beat SPY by 3.6 points a year (17.4% against 13.8%). A diversified basket should not do that unless the list is built from hindsight.

这份名单是今天的持仓,也就是说,其中包含的是那些一直保持规模较大、仍在上市且符合规定的公司。模型在这份名单中的排序能力与之前大致相同(秩相关系数为 +0.025 和 +0.028),因此,额外收益主要来自这份名单,而不是模型。The list is today's holdings, which means it contains the companies that stayed large, listed and compliant. The model's ranking skill inside the list was about what it was before (rank correlation of +0.025 and +0.028), so the extra return is mostly the list, not the model.

这堂课的结论很直接:基于今天的持仓清单进行回测,无法告诉你本来能赚多少。只有前瞻数据才能做到。我也无法确认任何股票是否符合要求。测试会对我提供的任何清单进行交易,因此我依赖基金公布的持仓,并在指数发生变化时更新清单。The lesson is blunt: no backtest on today's holdings list can tell you what you would have earned. Only forward data can. I also cannot certify that any stock is compliant. The test trades whatever list I give it, so I rely on the fund's published holdings and refresh them when the index changes.

我尝试用来改进它的所有办法,都没能跨过同一道门槛。 Everything I tried to improve it failed the same bar

六种门控和退出方案都未达到我预先注册的标准。从2021年起,按照我的波动率规则,没有一种方案的夏普比率超过 SPY,也没有一种方案的最大回撤不高于 SPY。夏普比率差值的所有自助法置信区间都包含零。Six variants of gates and exits all failed my pre-registered bar. From 2021 onward none beat SPY under my volatility rule on Sharpe ratio, and none kept its worst drawdown within SPY's. Every bootstrap interval for the Sharpe difference included zero.

Variant Return a year Sharpe Worst drawdown
V0 control (the frozen test) 24.8% 0.92 -52.4%
V1 buy only when the volatility gate is on 22.2% 0.89 -48.9%
V2 V1, plus sell everything after two gate-off days 21.5% 0.97 -35.9%
V3 CAN SLIM style: top 5, 8% stop, 50-day exit 27.6% 1.01 -41.9%
V4 V1, only stocks with beta below 1.2 15.8% 0.97 -32.0%
V6 two-stage gate 20.8% 0.87 -47.7%

有三点尤为突出:Three things stood out:

  • 波动率过滤器仅在 17% 的交易日关闭,因此限制新增买入几乎没有影响。转而持有 SPY(V2)降低了回撤,但也牺牲了收益。
    The volatility gate was off on only 17% of days, so blocking new buys changed little. Retreating to SPY (V2) cut the drawdown but cost return.
  • V3 是我更偏好的设计,整体 alpha 表现最好(年化 +14.7%,t 值为 2.2)。这些收益全部来自 2015—2020 年(+26.5%,t 值为 3.2)。2021 年以来,收益为 +1.6%,t 值为 0.2。
    V3, the design I preferred, had the best alpha overall (+14.7% a year, t of 2.2). All of it came from 2015-2020 (+26.5%, t of 3.2). From 2021 it was +1.6% with a t of 0.2.
  • 仅调整模型本身也无济于事。使用经过 beta 调整的收益进行训练,使排名最高的五分位组的 beta 降低了 0.25 至 0.40,但合并后的 t 值从 2.5 降至 0.6。使用原始波动率和 beta 水平训练,效果更差(降至 0.1);对五个随机种子的结果取平均则没有任何改变(仍为 2.5)。模型在 2021 年之后获得的超额优势中,约有三分之一来自额外的 beta 暴露。
    Changing the model itself also failed. Training on beta-adjusted returns cut the top-ranked fifth's beta by 0.25 to 0.40, but the pooled t fell from 2.5 to 0.6. Raw volatility and beta levels made it worse (0.1), and averaging five random seeds changed nothing (2.5). About a third of the model's post-2021 edge was extra beta.

前向测试:冻结模型,让时间来检验 The forward test: freeze the model and let time decide

由于历史上的每一年都已被反复检视,剩下唯一干净的证据,就是尚不存在的数据。于是我冻结了一个版本,称之为 V0,并开始用它进行模拟交易。Because every year of history has been inspected many times, the only clean evidence left is data that does not exist yet. So I froze one version, called V0, and started trading it on paper.

规则是固定的。一个仅使用价格特征的 LightGBM 模型只对 SPUS 成分股进行评分。每五个交易日,如果某只持仓的排名跌至 60 名之后或亏损达到 25%,就卖出该持仓;随后买入排名最高且尚未持有的股票,最多持有 20 只。订单在下一个收盘价成交,买卖双方各收取 5 个基点的费用,基准为 SPUS。The rules are fixed. A LightGBM model with price-only features scores only SPUS names. Every five trading days it sells a holding if its rank falls below 60 or it is down 25%, then buys the best-ranked names it does not hold, up to 20. Orders fill at the next close with 5 basis points per side, and the benchmark is SPUS.

我在进行第一笔交易前就确定了成功标准。经过至少 50 个决策日(约一年)后,模型对股票未来 5 天实际表现的排名相关性平均必须达到 +0.02 或更高,t 统计量至少为 2。我选择这一指标,是因为每个决策日都会为约 100 只股票评分,相比投资组合收益,它能提供多得多的观测值。I fixed the success bar before the first trade. After at least 50 decision days, about a year, the model's rank correlation with what stocks actually did over the next 5 days must average +0.02 or more, with a t-statistic of at least 2. I chose that measure because each decision day scores about 100 names, which gives far more observations than a portfolio return does.

自动化任务每周都会运行同一套流程:Each week the automated job runs the same cycle:

  1. 在周五美国市场收盘后刷新价格并计算实时特征。
    Refresh prices and compute the live features after Friday's US close.
  2. 在双方共有的日期上,将实时特征与训练表进行比较。
    Compare the live features with the training table on the dates they share.
  3. 为符合伊斯兰教法的列表打分,并应用买入和卖出规则。
    Score the Shariah-compliant list and apply the buy and sell rules.
  4. 将决策追加到只允许追加的账本中。
    Append the decisions to an append-only ledger.
  5. 根据账本和最新价格重建模拟投资组合,并与 SPUS 进行比较。
    Rebuild the paper portfolio from the ledger and fresh prices, and compare it with SPUS.
  6. 备份账本并记录其 SHA-256 哈希值,以便发现之后的任何修改。
    Back up the ledger and record its SHA-256 hash, so any later edit shows up.

该任务在一台小型服务器上运行,使用锁机制防止两次运行重叠;当价格数据过时时便停止,并在发生任何故障时发送 Telegram 警报。The job runs on a small server, uses a lock so two runs cannot overlap, stops when the price data is stale, and sends a Telegram alert on any failure.

这道防护措施在第一天就证明了它的价值。服务器拒绝记录它的第一个决策,因为有一个实时特征与表中的值相差 0.862(取值范围为 -1 到 +1)。更完整的报告显示,448 行中只有 4 行存在实质性差异,占单元格总数的 0.04%,而且模型对两组特征的评分几乎完全相同(秩相关系数为 0.991)。这只是一个无害的数据小异常,但单靠一个数字无法告诉我这一点,因此现在这项检查会判断模型的答案是否发生变化。The safeguard earned its keep on day one. The server refused to record its first decision because one live feature differed from the table by 0.862 on a scale of -1 to +1. A fuller report showed that only 4 of 448 rows differed materially, 0.04% of cells, and that the model scored both feature sets almost identically (rank correlation 0.991). It was a harmless data quirk, but a single number could not have told me that, so the check now judges whether the model's answer changes.

关于首项决策所体现的信息,需要注意一点:20 只股票中有 12 只是半导体、存储器、存储设备或光学硬件股,因此这几乎相当于押注同一个主题。早期的模拟收益更多反映的是这一主题的表现,而非模型本身。One caution about what the first decision shows. Twelve of its 20 stocks were semiconductor, memory, storage or optical-hardware names, which makes it close to one bet on that theme. Early paper returns will say more about that theme than about the model.

蜡烛图形态:两次测试,相同答案 Candlestick patterns: two tests, the same answer

无论是将蜡烛图形态输入模型,还是将其作为简单规则进行交易,它们都没有带来任何增益。前向测试开始后,我又分别进行了两项测试,每项测试都先写下了对应的K线。Candlestick patterns added nothing, whether I fed them to the model or traded them as plain rules. I ran two separate tests after the forward test started, each with its bar written first.

第一项测试是一次机器学习消融实验。我给冻结模型的纯价格特征集添加了 17 个蜡烛图形态特征:实体大小、上影线和下影线、收盘价在当日区间中的位置、开盘跳空,以及前两个交易日的对应数值。判定标准是:两个时期的秩相关增益均高于零,且配对 t 值至少为 2。The first test was a machine-learning ablation. I gave the frozen model's price-only feature set 17 candle-shape features: body size, upper and lower wicks, where the close sits in the day's range, the opening gap, and the same values for the two previous days. The bar was a gain in rank correlation above zero in both periods, with a paired t of at least 2.

秩相关性略有下降,从 +0.034 降至 +0.030(配对 t 值为 -1.2)。在 5 天的预测周期内,蜡烛图特征反而造成了明显损害(t 值为 -2.4)。单独来看,蜡烛图特征在 2015—2020 年间具有微弱的正向信号,但从 2021 年起转为负向。信号方向会反转的效应是无法用于交易的。The rank correlation fell slightly, from +0.034 to +0.030 (paired t of -1.2). At a 5-day horizon the candles actively hurt (t of -2.4). On their own, the candle features had a small positive signal in 2015-2020 that turned negative from 2021. An effect that flips sign is not one you can trade.

第二项测试完全没有使用机器学习。我把四种经典看涨形态编成固定规则:锤头线、看涨吞没形态、刺透形态和启明星形态。它们只有在上升趋势中、刚创下新的 10 日低点后才计入。每种形态都与没有出现形态时的相同情形进行比较,因此蜡烛图形态必须胜过回调本身。The second test used no machine learning at all. I coded four classic bullish patterns as fixed rules: hammer, bullish engulfing, piercing line and morning star. They only counted in an uptrend, right after a fresh 10-day low. Each was compared with the same situation without a pattern, so the candle had to beat the pullback itself.

股票池约包含 2,000 只美国股票(包括已退市的股票),每天按流动性排名选出最具流动性的 450 只,时间范围为 2010 至 2026 年。结果指标为入场后 10 天的收益率减去 SPY 的收益率。由于测试了四种形态,显著性门槛设为 t 值 2.5;统计时按 10 天区间而非单个事件计数,因为这些形态会在抛售日集中出现。The universe was about 2,000 US stocks including delisted ones, ranked to the 450 most liquid each day, from 2010 to 2026. The outcome was the 10-day return after entry minus SPY's. Because four patterns were tested, the bar was a t of 2.5, counted over 10-day blocks rather than individual events, since patterns cluster on sell-off days.

Pattern Events Excess over the no-pattern baseline (10 days) t Verdict
Hammer 6,630 +0.15% 1.2 Fail
Bullish engulfing 6,727 -0.13% -1.0 Fail
Piercing line 2,465 +0.01% 0.0 Fail
Morning star 5,377 0.00% 0.0 Fail

这种设定本身也没有优势。在上涨趋势中创出新10日低点后的任何一天,接下来10天的表现都仅比 SPY 高出 0.07%。The setting itself had no edge either. Any day in an uptrend after a fresh 10-day low beat SPY by just +0.07% over the next 10 days.

这项测试的首次运行还产生了整个项目中最明显的错误数值:无模式基线在 10 天内平均跑赢 SPY 88.6%。问题是由几段异常的价格序列造成的,其中一段显示两周内涨了 29,000 倍。经过数据检查,我们剔除了 861 个发生近乎不可能的单日价格跳涨的事件,以及 34 个认股权证和单位;基线随后降至上面所说的 +0.07%。其余真实的离群值,例如 2021 年 1 月的 GameStop,则予以保留。The first run of this test also produced the most obviously wrong number of the whole project. The no-pattern baseline beat SPY by an average of 88.6% in 10 days. A few broken price series caused it, one of them showing a 29,000-fold gain in two weeks. A data check removed 861 events near impossible one-day price jumps, plus 34 warrants and units, and the baseline fell to the +0.07% above. The real outliers that remained, such as GameStop in January 2021, stayed in.

我学到了什么,以及我不会声称什么 What I learned, and what I will not claim

最重要的教训是:在经受住攻击之前,都要把任何看起来不错的结果视为一个 bug。以下这些习惯带来了回报:The main lesson is to treat any good-looking result as a bug until it survives an attack. These are the habits that paid off:

  • 在测试之前先写下通过/失败的标准。这样答案不尽如人意时,你就不会临时改变评判标准。
    Write the pass/fail bar before the test. It stops you from moving the goalposts when the answer disappoints.
  • 测试你的测试。故意植入一个已知的泄漏,看看检测器能否发现它。我的检测器直到重写后才做到了。
    Test your tests. Plant a known leak and check that your detector catches it. Mine did not, until I rewrote it.
  • 寻找蛛丝马迹。“每个股票代码恰好对应两行”这一线索,揭示了退市数据被重复计数的问题。某个基准策略在 10 天内跑赢 SPY 88%,但胜率并不高于那些模式,这直接指向了几条有问题的价格序列。
    Look for fingerprints. "Exactly two rows per ticker" was the clue to the double-counted delistings. A baseline that beat SPY by 88% in 10 days, with a win rate no higher than the patterns', pointed straight at a few broken price series.
  • 评估风险调整后的回报,而非原始回报。贝塔系数为 1.4 时获得更高回报,并不代表能力。
    Judge risk-adjusted return, not raw return. A higher return from a beta of 1.4 is not skill.
  • 不要相信今天编制的任何名单。当前的指数成分股和 ETF 持仓都带有事后偏见。
    Distrust any list built today. Current index members and ETF holdings carry hindsight.
  • 构建能够自我解释的告警。一个吓人的数字让我白白耗了一天。一份能显示差异出在哪里的报告挽救了这一天。
    Build alarms that explain themselves. A single scary number cost me a day. A report that shows where the difference is saved it.

我不会声称这个模型能跑赢市场、它过去的优势会再次出现,或任何人都应该据此交易。修正后,它只是一个较弱的排名信号,而且在 2020 年后逐渐失效;我没有证据证明它具有优势。What I will not claim: that this model beats the market, that its past edge will repeat, or that anyone should trade it. After the fixes it is a weak ranking signal that faded after 2020, and I have no proof of an edge.

前瞻测试仍在继续,等大约积累了 50 个决策日后,我会写下第一份坦诚的评估。如果秩相关系数接近于零,实际的答案就会是那个不起眼的选择:持有一个普通指数,也许再加上一条波动率规则来限制回撤。The forward test continues, and I will write up the first honest read after about 50 decision days. If the rank correlation comes in near zero, the practical answer is the unglamorous one: hold a plain index, perhaps with a volatility rule to cap drawdowns.

所有数据均来自我自己的回测,使用调整后价格,并按每边 5 个基点计入成本,且未考虑税费和市场冲击。SPY 用于代表 SPUS 在 2019 年推出之前的表现。这是个人研究记录,不构成财务建议。All figures come from my own backtests with adjusted prices and 5 basis points of cost per side, and ignore taxes and market impact. SPY stands in for SPUS before its 2019 launch. This is a personal research log, not financial advice.

值得记下

主要教训是:把任何看起来不错的结果都当作 bug,直到它经受住攻击。
t 值 4.5 是那种应该让你起疑的数字,而它确实应该。
模型的 alpha 为每年 +6.5%,t 统计量为 1.5,并不显著。
基于当前持仓名单的任何回测都无法告诉你你本可以赚到多少。只有前瞻数据可以。
真正的惊喜是那个门控。仅在确认上升趋势中持有 SPY,在 2020-2026 年间回报为 +24.6%,而买入并持有为 +158%。