beatnothing 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. beatnothing-0.2.0/LICENSE +21 -0
  2. beatnothing-0.2.0/PKG-INFO +496 -0
  3. beatnothing-0.2.0/README.md +456 -0
  4. beatnothing-0.2.0/beatnothing/__init__.py +18 -0
  5. beatnothing-0.2.0/beatnothing/canary.py +57 -0
  6. beatnothing-0.2.0/beatnothing/cli.py +137 -0
  7. beatnothing-0.2.0/beatnothing/data/SOURCE.md +14 -0
  8. beatnothing-0.2.0/beatnothing/data/sp500_membership_by_date.csv.gz +0 -0
  9. beatnothing-0.2.0/beatnothing/data/sp500_ticker_start_end.csv.gz +0 -0
  10. beatnothing-0.2.0/beatnothing/data/still_listed_leavers.json +49 -0
  11. beatnothing-0.2.0/beatnothing/data/ticker_aliases.json +125 -0
  12. beatnothing-0.2.0/beatnothing/engine.py +132 -0
  13. beatnothing-0.2.0/beatnothing/features.py +140 -0
  14. beatnothing-0.2.0/beatnothing/leaderboard.py +300 -0
  15. beatnothing-0.2.0/beatnothing/models.py +102 -0
  16. beatnothing-0.2.0/beatnothing/score.py +117 -0
  17. beatnothing-0.2.0/beatnothing/stats.py +401 -0
  18. beatnothing-0.2.0/beatnothing/universe.py +92 -0
  19. beatnothing-0.2.0/beatnothing/validate.py +220 -0
  20. beatnothing-0.2.0/beatnothing.egg-info/PKG-INFO +496 -0
  21. beatnothing-0.2.0/beatnothing.egg-info/SOURCES.txt +32 -0
  22. beatnothing-0.2.0/beatnothing.egg-info/dependency_links.txt +1 -0
  23. beatnothing-0.2.0/beatnothing.egg-info/entry_points.txt +2 -0
  24. beatnothing-0.2.0/beatnothing.egg-info/requires.txt +16 -0
  25. beatnothing-0.2.0/beatnothing.egg-info/top_level.txt +1 -0
  26. beatnothing-0.2.0/pyproject.toml +51 -0
  27. beatnothing-0.2.0/setup.cfg +4 -0
  28. beatnothing-0.2.0/tests/test_cli_and_universe.py +54 -0
  29. beatnothing-0.2.0/tests/test_engine_and_score.py +118 -0
  30. beatnothing-0.2.0/tests/test_investable_bar.py +42 -0
  31. beatnothing-0.2.0/tests/test_long_short.py +52 -0
  32. beatnothing-0.2.0/tests/test_membership_mask.py +33 -0
  33. beatnothing-0.2.0/tests/test_stats.py +161 -0
  34. beatnothing-0.2.0/tests/test_validate.py +105 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kaustubh Patil
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,496 @@
1
+ Metadata-Version: 2.4
2
+ Name: beatnothing
3
+ Version: 0.2.0
4
+ Summary: Can your model beat doing nothing, after costs, without knowing the future? A benchmark harness for daily equity signals.
5
+ Author: Kaustubh Patil
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/kaustubhspatil/beatnothing
8
+ Project-URL: Repository, https://github.com/kaustubhspatil/beatnothing
9
+ Project-URL: Leaderboard, https://github.com/kaustubhspatil/beatnothing/blob/master/leaderboard/LEADERBOARD.md
10
+ Project-URL: Issues, https://github.com/kaustubhspatil/beatnothing/issues
11
+ Keywords: quant,backtest,benchmark,transaction costs,sharpe,bootstrap,survivorship bias,point in time,leakage,equity signals
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Financial and Insurance Industry
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Office/Business :: Financial :: Investment
22
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: numpy>=1.24
27
+ Requires-Dist: pandas>=2.0
28
+ Requires-Dist: pyarrow>=14
29
+ Requires-Dist: scipy>=1.10
30
+ Provides-Extra: data
31
+ Requires-Dist: yfinance>=0.2; extra == "data"
32
+ Requires-Dist: requests>=2.31; extra == "data"
33
+ Provides-Extra: figures
34
+ Requires-Dist: matplotlib>=3.7; extra == "figures"
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=7; extra == "dev"
37
+ Requires-Dist: build>=1.0; extra == "dev"
38
+ Requires-Dist: twine>=5.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # beatnothing
42
+
43
+ [![tests](https://github.com/kaustubhspatil/beatnothing/actions/workflows/tests.yml/badge.svg)](https://github.com/kaustubhspatil/beatnothing/actions/workflows/tests.yml)
44
+ [![PyPI](https://img.shields.io/pypi/v/beatnothing)](https://pypi.org/project/beatnothing/)
45
+ [![license](https://img.shields.io/github/license/kaustubhspatil/beatnothing)](LICENSE)
46
+
47
+ **Can your model beat doing nothing, after costs, without knowing the future?**
48
+
49
+ Every quant paper has a chart that goes up and to the right. This benchmark asks that
50
+ chart one question. It hands the same prices to the dumbest strategy imaginable, hold
51
+ everything at equal weight and never think again, charges both of them the same fee on
52
+ every trade, refuses to let either one see tomorrow, and measures the gap with an error
53
+ bar. That gap is the **Net Edge**. Twenty five contestants have tried so far: four neural
54
+ architectures, gradient boosted trees, the classic cross sectional factors, a cross
55
+ sectional ranking network, and a 2026 financial foundation model used with no training at
56
+ all. **None of them clears the bar.**
57
+
58
+ <p align="center">
59
+ <img src="leaderboard/figures/net_edge.png" width="900" alt="Net Edge with 95% intervals for every contestant, on the sealed 2022 to 2025 window and on 2026 to date">
60
+ </p>
61
+
62
+ ```bash
63
+ pip install beatnothing
64
+ ```
65
+
66
+ ## The rules of the game
67
+
68
+ <table>
69
+ <tr><th>Rule</th><th>What it means in practice</th></tr>
70
+ <tr><td><strong>One engine</strong></td><td>Predictions become positions by one of three fixed rules a contestant declares: long every name with a positive value (the default), long the top decile, or long the top and short the bottom decile, dollar neutral, with a 50 bps a year borrow charge. Weights are used as given, gross exposure at most one, no leverage. Nobody gets a custom backtester.</td></tr>
71
+ <tr><td><strong>One bar, and a second one that could not choose</strong></td><td>The universe bar: always long, equal weight, same universe, same days, same costs. The thing you would earn with zero skill on the names you picked. Beside it, the investable bar: RSP, the equal weight S&amp;P 500 ETF, which holds every index member by construction, dead ones included, and cannot have picked its universe with hindsight. The gap between the two bars is what picking the universe was worth.</td></tr>
72
+ <tr><td><strong>Costs on every trade</strong></td><td>10 bps per unit of turnover, day one included. Gross and 20 bps numbers sit beside the net number so you can see who only wins for free.</td></tr>
73
+ <tr><td><strong>One score</strong></td><td>Net Edge = your net Sharpe minus the bar's net Sharpe on the same days, with a 95% paired stationary block bootstrap interval. You clear the bar only when the whole interval is above zero.</td></tr>
74
+ <tr><td><strong>Frozen means frozen</strong></td><td>Every submission records the sha256 of its model files and a registration date. A monthly job pulls new prices and rescores everything on the days that arrived after registration. Signals never change; only the calendar does.</td></tr>
75
+ </table>
76
+
77
+ ## Scoreboard, first season
78
+
79
+ Sealed window 2022 to 2025, 48 large cap US stocks, net of 10 bps. Full detail with
80
+ drawdowns, exposure and dollars in [`leaderboard/LEADERBOARD.md`](leaderboard/LEADERBOARD.md).
81
+
82
+ <table>
83
+ <tr><th>Contestant</th><th>Net Edge</th><th>95% interval</th><th>Edge vs RSP</th><th>Net Sharpe</th><th>Gross Sharpe</th><th>Turnover a year</th></tr>
84
+ <tr><td>Always long, the universe bar</td><td>0.00</td><td></td><td>+0.44 [+0.19, +0.76]</td><td>+0.88</td><td>+0.88</td><td>0.3×</td></tr>
85
+ <tr><td>Feedforward network</td><td>−0.03</td><td>[−0.06, −0.01]</td><td>+0.41 [+0.17, +0.72]</td><td>+0.85</td><td>+0.88</td><td>4.3×</td></tr>
86
+ <tr><td>Cost aware network, 10 bps term</td><td>−0.04</td><td>[−0.10, +0.01]</td><td>+0.40 [+0.15, +0.72]</td><td>+0.83</td><td>+0.87</td><td>2.9×</td></tr>
87
+ <tr><td>LightGBM, MSE objective</td><td>−0.14</td><td>[−0.45, +0.09]</td><td>+0.30 [−0.13, +0.72]</td><td>+0.74</td><td>+0.79</td><td>7.2×</td></tr>
88
+ <tr><td>LSTM, 60 day windows</td><td>−0.53</td><td>[−1.00, −0.12]</td><td>−0.08 [−0.61, +0.44]</td><td>+0.35</td><td>+0.62</td><td>43×</td></tr>
89
+ <tr><td>Cost aware network, no cost term</td><td>−0.70</td><td>[−1.27, −0.09]</td><td>−0.26 [−0.89, +0.35]</td><td>+0.18</td><td>+0.53</td><td>11.5×</td></tr>
90
+ <tr><td>1D CNN, 60 day windows</td><td>−0.93</td><td>[−1.55, −0.29]</td><td>−0.49 [−1.17, +0.16]</td><td>−0.05</td><td>+1.08</td><td>265×</td></tr>
91
+ <tr><td>Linear regression, the incumbent</td><td>−1.17</td><td>[−1.82, −0.62]</td><td>−0.73 [−1.42, −0.10]</td><td>−0.30</td><td>+0.51</td><td>159×</td></tr>
92
+ <tr><td>Kronos small, zero shot</td><td>−1.67</td><td>[−2.09, −1.26]</td><td>−1.23 [−1.71, −0.79]</td><td>−0.79</td><td>+0.52</td><td>241×</td></tr>
93
+ </table>
94
+
95
+ Read the two edge columns together. Three contestants clear RSP on the sealed window, and
96
+ so does the universe bar itself, by the same margin. They are not beating the index; the
97
+ universe is. A contestant that clears the investable bar while failing the universe bar
98
+ has demonstrated one thing only: that its universe was chosen with hindsight.
99
+
100
+ On the 176 trading days of 2026 that none of the frozen models had ever seen, the order
101
+ reproduces, every interval widens to include zero, and the two bars converge (RSP 1.30,
102
+ universe bar 1.44, gap inside the noise). Eight months cannot separate a network from a
103
+ bar. The leaderboard says so instead of ranking noise.
104
+
105
+ ## What the first season taught us
106
+
107
+ <p align="center">
108
+ <img src="leaderboard/figures/cost_inversion.png" width="900" alt="Gross versus net Sharpe for every contestant on the sealed window">
109
+ </p>
110
+
111
+ * **Costs reorder the field.** The 1D CNN has the highest gross Sharpe of anything, 1.08, and a negative net Sharpe, because it turns the book over 265 times a year. Gross rank is not net rank. Papers that report gross numbers are reporting a different sport.
112
+ * **The MSE optimum is nearly always long.** LightGBM, early stopped honestly on validation loss, stops after three trees and holds 47 of 48 names. Squared error on daily returns is minimised by predicting the drift, and the drift is the bar wearing a different hat.
113
+ * **A foundation model obeys the same arithmetic as a linear regression.** Kronos small, pretrained on 12 billion bars across 45 exchanges and used zero shot, has a gross Sharpe of 0.52 and a net Sharpe of minus 0.79, with a 53% drawdown, because it flips positions 241 times a year. A hundred times the parameters of the feedforward network; the same cost inversion as the incumbent.
114
+ * **A cost aware objective repairs turnover, not alpha.** A network trained end to end on net Sharpe with a 10 bps turnover term trades three times a year instead of twelve and lands within 0.05 of the bar across three seeds, holding half the book in cash. Remove the cost term and the identical architecture loses 0.7 of Sharpe to fees. Given the true objective, the optimiser finds the bar.
115
+
116
+ ## How we decide something is real
117
+
118
+ A leaderboard is a multiple test, and a Sharpe ratio is a badly behaved statistic. Three
119
+ things stand between a number in the table above and a claim worth acting on.
120
+
121
+ **The interval.** Net Edge carries a studentized circular block bootstrap interval
122
+ (Ledoit and Wolf, 2008): every resample recomputes not only the difference but a
123
+ heteroskedasticity and autocorrelation robust standard error for it, using the delta
124
+ method over the four moments that define two Sharpe ratios. The block length is chosen by
125
+ the Politis and White rule applied to the statistic's influence function, not to the
126
+ return series, because the difference of two return series has almost no autocorrelation
127
+ in its level while its squares are strongly persistent.
128
+
129
+ **The correction.** With fifteen contestants tested separately at five percent, a false
130
+ winner appears more than half the time. The same joint bootstrap resamples every
131
+ contestant on the same dates and feeds a Romano and Wolf stepdown, which controls the
132
+ chance of even one false claim across the whole board while keeping far more power than
133
+ Bonferroni. The column that decides a verdict is **p adj**, not p.
134
+
135
+ **The search.** A submitter who tried forty variants and shows you the best one has told
136
+ you nothing. `beatnothing.stats` ships the combinatorially symmetric cross validation
137
+ probability of backtest overfitting and the deflated Sharpe ratio, so a contestant with
138
+ many variants can be scored on what the search itself would have produced.
139
+
140
+ <p align="center">
141
+ <img src="leaderboard/figures/stats_validation.png" width="900" alt="Measured size, power, familywise error and overfitting probability of the benchmark's own statistics">
142
+ </p>
143
+
144
+ None of that is asserted. `scripts/validate_stats.py` simulates markets with fat tails and
145
+ clustered volatility, where the contestant is highly correlated with its bar because real
146
+ contestants are, and measures what the machinery actually does. On 250 simulations of four
147
+ years of daily data:
148
+
149
+ <table>
150
+ <tr><th>Experiment</th><th>Result</th></tr>
151
+ <tr><td>No real edge exists. How often is one claimed?</td><td>studentized test <strong>4.8%</strong> against a promise of 5%; percentile interval 2.4%</td></tr>
152
+ <tr><td>A real edge of a third of a Sharpe exists. How often is it found?</td><td>studentized test <strong>42.4%</strong>; percentile interval 38.8%</td></tr>
153
+ <tr><td>Fifteen worthless contestants on one board. How often does one of them win?</td><td>tested separately <strong>61%</strong>; after the stepdown <strong>1%</strong></td></tr>
154
+ <tr><td>Twelve variants of noise. What is the overfitting probability of the best?</td><td><strong>0.66</strong>, where one half is what pure noise deserves; with one genuinely good variant among the twelve, <strong>0.00</strong></td></tr>
155
+ </table>
156
+
157
+ The studentized test keeps its word. The older percentile interval fires at about half its
158
+ nominal rate, and being conservative is not free: it misses real edges the studentized
159
+ test finds. Size stays near five percent as the record lengthens from two years to sixteen,
160
+ so what distortion remains is a finite sample effect and not a bug.
161
+
162
+ The third row is the one that matters most for a leaderboard, and it is why the verdict
163
+ column reports an adjusted p value rather than an interval. Six boards in ten would have
164
+ crowned somebody. On the real boards here the lowest adjusted p value is 0.97, so nothing
165
+ comes close.
166
+
167
+ Getting that experiment right was harder than it looks, and the first attempt was silently
168
+ wrong: adding zero mean noise to the bar leaves the mean alone but raises the variance, so
169
+ every simulated contestant was genuinely worse than its bar and the board could not have
170
+ produced a false winner at all. It reported zero percent both ways, which looked like a
171
+ result. Full numbers in [`leaderboard/stats_validation.json`](leaderboard/stats_validation.json).
172
+
173
+ ## Verify your verifier
174
+
175
+ Most leakage is not in the model. It is in the evaluation. So the harness ships
176
+ contestants that cheat on purpose, and you run them through *your* pipeline first:
177
+
178
+ ```python
179
+ from beatnothing import Backtest, peek, off_by_one, hindsight_universe
180
+ Backtest(actual, predictions=peek(actual)).stats()["net_sharpe"] # tomorrow's return as today's signal: absurd, or your pipeline is broken
181
+ Backtest(actual, predictions=hindsight_universe(actual, 10)).stats() # the ten best names of the whole window, chosen at the start
182
+ ```
183
+
184
+ If a canary does not score absurdly well, your pipeline is not measuring what you think.
185
+ The `truncation_test` in the same module proves a feature builder is trailing only by
186
+ deleting the future, recomputing, and demanding byte identical rows. Run on the point in
187
+ time panel it compares 46,605 rows across 60 names either side of a June 2024 cutoff and
188
+ finds a maximum difference of exactly zero on every one of the seventeen features
189
+ ([`data/pit/truncation_test.json`](data/pit/truncation_test.json)).
190
+
191
+ ## The universe knew the future
192
+
193
+ <p align="center">
194
+ <img src="leaderboard/figures/survivorship_gap.png" width="900" alt="Members on 31 December 2021, how many left the index, and how many no longer have prices">
195
+ </p>
196
+
197
+ "The current S&amp;P 500 constituents" is a list of companies that survived. The
198
+ package ships point in time membership from 1996 to 2026 (`beatnothing members
199
+ 2008-09-15` prints who was actually in the index on the Lehman weekend) and a coverage
200
+ report that states, in numbers, what a free price source can no longer supply. For the
201
+ sealed window: 94 of the 505 members on the last day of 2021 left the index, and 47 of
202
+ those have no prices on Yahoo Finance any more, both 2023 bank failures among them.
203
+ That is the residual survivorship gap in this first season, and it is stated rather
204
+ than hidden.
205
+
206
+ <p align="center">
207
+ <img src="leaderboard/figures/two_bars.png" width="900" alt="Growth of one dollar: the 48 survivor universe bar against RSP and SPY, 2022 to 2026">
208
+ </p>
209
+
210
+ It is also priced. RSP, the equal weight S&amp;P 500 ETF, is the same idea as the
211
+ universe bar applied to the whole index, and it could not pick its members with
212
+ hindsight. On the sealed window the 48 name bar earned a Sharpe of 0.88 against 0.44 for
213
+ RSP, an edge of +0.44 with an interval of [+0.19, +0.76]. Part of that is a size effect,
214
+ since the largest names ran hardest in 2023 to 2025, so read it as an upper bound on
215
+ survivorship and a fair measure of hindsight in universe selection. In 2026, where no
216
+ hindsight was possible, the two bars sit 0.14 apart with an interval that spans a full
217
+ Sharpe point in each direction. Every contestant now carries an edge against both bars.
218
+
219
+ A probe of a paid archive found 45 of the 47 vanished names in its delisted index and the
220
+ other two renamed and still trading, so season two can run on the universe that did not
221
+ know the future.
222
+
223
+ ## Season two: the universe that did not know the future
224
+
225
+ <p align="center">
226
+ <img src="leaderboard/pit/figures/two_bars.png" width="900" alt="The point in time universe bar against RSP and SPY, 2022 to 2026">
227
+ </p>
228
+
229
+ Every S&amp;P 500 member on every day since 2022, 615 names in all, dead ones included,
230
+ built entirely from free data: Yahoo for the 564 that still trade, Yahoo under a new
231
+ ticker for 16 renames (the alias table ships with the package), and Tiingo's free tier
232
+ for 39 names that were acquired or failed, each fetched to its last trading day. The
233
+ membership table decides on which days a name exists. Coverage is 99.5% of the member
234
+ days the index defines, about 501 names a day; the gap is one real hole (Equity
235
+ Residential, which no free source serves after its 2026 merger) and four 2026 spin
236
+ offs too young to have features.
237
+
238
+ The bar now agrees with the ETF that holds the same names: a Sharpe of 0.47 for the
239
+ point in time universe against 0.44 for RSP on the sealed window, an edge of +0.03 with
240
+ an interval of [−0.01, +0.06]. On the 48 name survivor universe that same gap was
241
+ +0.44. The hindsight premium is gone, and with it the illusion that three contestants
242
+ "beat the index".
243
+
244
+ The frozen contestants, trained on 48 survivors and now asked about 500 names including
245
+ the ones that later died:
246
+
247
+ <table>
248
+ <tr><th>Contestant, point in time track</th><th>Net Edge</th><th>95% interval</th><th>Edge vs RSP</th><th>Net Sharpe</th><th>Gross Sharpe</th><th>Max drawdown</th><th>Turnover a year</th></tr>
249
+ <tr><td>Momentum 12 1, long short vs cash</td><td>+0.21</td><td>[−0.79, +1.20]</td><td></td><td>+0.21</td><td>+0.27</td><td>−17%</td><td>7.0×</td></tr>
250
+ <tr><td>Momentum 12 1, long top decile</td><td>+0.16</td><td>[−0.48, +0.80]</td><td></td><td>+0.63</td><td>+0.66</td><td>−24%</td><td>7.2×</td></tr>
251
+ <tr><td>Low volatility, long top decile</td><td>+0.02</td><td>[−0.74, +0.76]</td><td></td><td>+0.49</td><td>+0.55</td><td>−14%</td><td>7.7×</td></tr>
252
+ <tr><td>Cost aware network, 10 bps term</td><td>+0.00</td><td>[−0.06, +0.07]</td><td>+0.03 [−0.04, +0.11]</td><td>+0.47</td><td>+0.50</td><td>−12%</td><td>3.2×</td></tr>
253
+ <tr><td>Always long, the universe bar</td><td>0.00</td><td></td><td>+0.03 [−0.01, +0.06]</td><td>+0.47</td><td>+0.47</td><td>−21%</td><td>0.4×</td></tr>
254
+ <tr><td>Feedforward network</td><td>−0.02</td><td>[−0.03, −0.01]</td><td>+0.01 [−0.03, +0.05]</td><td>+0.45</td><td>+0.47</td><td>−21%</td><td>4.3×</td></tr>
255
+ <tr><td>LightGBM, MSE objective</td><td>−0.11</td><td>[−0.45, +0.12]</td><td>−0.08 [−0.37, +0.13]</td><td>+0.36</td><td>+0.40</td><td>−22%</td><td>7.4×</td></tr>
256
+ <tr><td>LSTM, 60 day windows</td><td>−0.25</td><td>[−0.53, +0.00]</td><td>−0.22 [−0.46, +0.00]</td><td>+0.21</td><td>+0.52</td><td>−22%</td><td>51×</td></tr>
257
+ <tr><td>Low volatility, long short vs cash</td><td>−0.27</td><td>[−1.25, +0.69]</td><td></td><td>−0.27</td><td>−0.22</td><td>−28%</td><td>6.7×</td></tr>
258
+ <tr><td>Cost aware network, no cost term</td><td>−0.33</td><td>[−0.89, +0.24]</td><td>−0.30 [−0.84, +0.24]</td><td>+0.14</td><td>+0.49</td><td>−6%</td><td>12×</td></tr>
259
+ <tr><td>Kronos small, zero shot, weekly</td><td>−0.35</td><td>[−0.55, −0.16]</td><td></td><td>+0.12</td><td>+0.38</td><td>−25%</td><td>50×</td></tr>
260
+ <tr><td>Reversal 1m, long top decile</td><td>−0.38</td><td>[−0.81, +0.03]</td><td></td><td>+0.09</td><td>+0.17</td><td>−27%</td><td>21×</td></tr>
261
+ <tr><td>Reversal 1m, long short vs cash</td><td>−0.47</td><td>[−1.38, +0.49]</td><td></td><td>−0.47</td><td>−0.25</td><td>−21%</td><td>21×</td></tr>
262
+ <tr><td>Linear regression, the incumbent</td><td>−0.80</td><td>[−1.09, −0.52]</td><td>−0.77 [−1.10, −0.48]</td><td>−0.34</td><td>+0.47</td><td>−38%</td><td>151×</td></tr>
263
+ <tr><td>1D CNN, 60 day windows</td><td>−1.03</td><td>[−1.38, −0.70]</td><td>−1.00 [−1.42, −0.67]</td><td>−0.56</td><td>+0.53</td><td>−52%</td><td>235×</td></tr>
264
+ <tr><td>Kronos small, zero shot, daily</td><td>−1.31</td><td>[−1.56, −1.06]</td><td></td><td>−0.84</td><td>+0.35</td><td>−55%</td><td>225×</td></tr>
265
+ </table>
266
+
267
+ ### The foundation model's information does not live in the daily rebalance
268
+
269
+ Kronos runs twice here, forecasting every trading day and every fifth, the second holding
270
+ its signal between. The pair separates what the model knows from what the trading costs:
271
+
272
+ <table>
273
+ <tr><th>Kronos small, zero shot, sealed window</th><th>Gross Sharpe</th><th>Net Sharpe</th><th>Cost of trading</th><th>Turnover</th><th>Max drawdown</th></tr>
274
+ <tr><td>Forecasting daily</td><td>+0.35</td><td>−0.84</td><td>1.19 of Sharpe</td><td>225×</td><td>−55%</td></tr>
275
+ <tr><td>Forecasting weekly</td><td>+0.38</td><td>+0.12</td><td>0.26 of Sharpe</td><td>50×</td><td>−25%</td></tr>
276
+ </table>
277
+
278
+ The gross numbers are the same within noise, so the model knows no more at a daily
279
+ horizon than at a weekly one. Everything that separates a Sharpe of minus 0.84 from plus
280
+ 0.12 is the cost of acting on it five times as often, and that is a property of the
281
+ evaluation nobody sees without charging for turnover. It is also not a rescue: at plus
282
+ 0.12 against the bar's plus 0.47, the weekly version is still significantly worse than
283
+ holding everything, with an interval of [−0.55, −0.16] that sits entirely below zero.
284
+
285
+ Nothing clears either bar. Three contestants have a positive point estimate and every one
286
+ of their intervals contains zero; after the stepdown across all fourteen, the lowest
287
+ adjusted p value on the board is 0.97. The order is the same as on the survivor universe,
288
+ the absolute numbers are roughly half, and the learned models neither collapse nor shine
289
+ on ten times the names including the failures: a strategy that hugs the bar hugs whatever
290
+ bar it is given. On 2026 to date every interval includes zero on both tracks.
291
+
292
+ Kronos is now on this track twice, daily and weekly. Five hundred names times twelve
293
+ hundred days of autoregressive generation is 590,089 forecasts and just under five hours
294
+ on a laptop card.
295
+
296
+ ### The classic factors, on the honest universe, after costs
297
+
298
+ A benchmark that only my own models have failed proves little. So the field now holds
299
+ the strategies every quant knows, built from prices alone and traded the way the
300
+ literature trades them, monthly rebalanced, decile portfolios: twelve month momentum
301
+ skipping the last month, one month reversal, and low volatility. Each runs two ways.
302
+ Long only the top decile, judged against the universe bar. And long the top decile,
303
+ short the bottom decile, dollar neutral, paying 50 bps a year to borrow, judged against
304
+ cash because a dollar neutral book competes with cash, not with an index.
305
+
306
+ Their rows are in the table above, mixed in with everything else, which is the point: a
307
+ factor and a neural network are contestants under the same rules.
308
+
309
+ Momentum's positive edge, the only one on the board, rests entirely on cheap borrow. The
310
+ bottom decile of a momentum screen is where hard to borrow names live, so the flat fifty
311
+ basis points a year charged by default is the optimistic case. Raising it prices the
312
+ optimism away:
313
+
314
+ <table>
315
+ <tr><th>Dollar neutral contestant, net Sharpe</th><th>50 bps borrow</th><th>200 bps</th><th>500 bps</th><th>1000 bps</th></tr>
316
+ <tr><td>Momentum 12 1</td><td>+0.21</td><td>+0.14</td><td>+0.01</td><td>−0.21</td></tr>
317
+ <tr><td>Low volatility 63d</td><td>−0.27</td><td>−0.33</td><td>−0.44</td><td>−0.62</td></tr>
318
+ <tr><td>Reversal 1m</td><td>−0.47</td><td>−0.55</td><td>−0.70</td><td>−0.96</td></tr>
319
+ </table>
320
+
321
+ The leaderboard therefore carries a Sharpe at 500 basis points column for every contestant
322
+ that shorts, beside the one at 20 basis points of turnover cost, so neither assumption can
323
+ carry a result on its own.
324
+
325
+ This is what the literature would predict for four recent years. Momentum is the only
326
+ factor with a positive net edge on both rules, and even so its interval spans zero:
327
+ four years of a fifty name decile book is not enough to separate a Sharpe of 0.2 from
328
+ luck, which is exactly why the intervals are printed. Short term reversal, a strong
329
+ anomaly in the 1990s, is dead after costs at 21 turns a year. Low volatility is flat.
330
+ Nothing clears either bar, and the models above now sit in a field that includes the
331
+ strategies real money trades. The calibration also cuts the other way: a harness that
332
+ had shown momentum at a Sharpe of 2 would have been reporting a bug.
333
+
334
+ Two notes on the data. The dead names' histories come from Tiingo's free tier, whose
335
+ terms cover personal use, so this repository carries their realised returns for the
336
+ scored window rather than their raw prices; `scripts/build_pit_universe.py` rebuilds
337
+ the full panel from your own free token in an afternoon. And the contestants here were
338
+ trained on survivors, which is the last hindsight left in the benchmark; retraining
339
+ them on the point in time universe is season three.
340
+
341
+ ## Season three: trained on the honest universe, and the one contestant with real information
342
+
343
+ The last hindsight left was in the training, not the evaluation: every learned contestant
344
+ so far was fitted on forty eight survivors and then asked about five hundred. Season three
345
+ removes it. The universe now reaches back to 2013, 797 names, 97.4% of the member days the
346
+ index defines, fitted to the end of 2018, chosen on 2019 to 2021, and scored on the same
347
+ sealed window as everyone else. It stops at 2013 rather than 2005 on purpose: the free
348
+ tier cannot supply Lehman Brothers, Bear Stearns, Washington Mutual, Countrywide, Fannie
349
+ Mae or Freddie Mac, so reaching back to 2008 would quietly put survivorship bias into the
350
+ one window where it would matter most.
351
+
352
+ Alongside the usual architectures it adds the contestant the earlier results kept pointing
353
+ at. A model trained to predict tomorrow's return under squared error is trained to predict
354
+ the drift, and the drift is the bar; so instead, rank the names against each other within
355
+ each day, which cancels the market move by construction, and trade the ranking long and
356
+ short. Such a model cannot inherit the bar's return, so any edge would be its own.
357
+
358
+ It has the most information of anything on the board, and it still loses:
359
+
360
+ <table>
361
+ <tr><th>Ranking network, rebalanced</th><th>Information coefficient</th><th>Gross Sharpe</th><th>Net Sharpe</th><th>Turnover</th><th>Paid to trade</th></tr>
362
+ <tr><td>daily</td><td>+0.0154</td><td>+0.72</td><td>−1.50</td><td>222×</td><td>2.22 of Sharpe</td></tr>
363
+ <tr><td>weekly</td><td>+0.0007</td><td>+0.32</td><td>−0.37</td><td>67×</td><td>0.69</td></tr>
364
+ <tr><td>monthly</td><td>−0.0039</td><td>−0.10</td><td>−0.30</td><td>20×</td><td>0.20</td></tr>
365
+ </table>
366
+
367
+ Read the first column down. The information is real at a one day horizon, the highest
368
+ coefficient of any contestant here, and it is gone within a week. Read the last column up.
369
+ Harvesting it daily costs 2.22 of Sharpe ratio, which is three times the gross it produces.
370
+ **The only contestant with genuine cross sectional information has information that decays
371
+ faster than it can be traded profitably.** Slow down to keep the costs and the signal is no
372
+ longer there; trade fast enough to catch it and the costs take three times what it is worth.
373
+ That is a more interesting way to fail than any of the sixteen contestants that simply
374
+ tracked the bar, and it is the kind of statement this benchmark exists to make.
375
+
376
+ Twenty five contestants now. None clears the bar on either window.
377
+
378
+ ## Enter a contestant
379
+
380
+ A submission is a folder with a signal file and a metadata file; the engine does the
381
+ rest. Read [`contestants/README.md`](contestants/README.md), then open a pull request
382
+ with `submissions_pit/<name>/`. Two reference scripts show the full path from raw prices
383
+ to a submission: a pretrained foundation model used without training, and a network
384
+ trained end to end on net Sharpe with three seeds.
385
+
386
+ Every entry is checked by a machine before a human looks at it, on every pull request:
387
+ structure, the engine's exposure rules, complete metadata, and then a leakage smell test.
388
+ A cross sectional signal on daily equity returns has an information coefficient of roughly
389
+ 0.02 to 0.05; a submission an order of magnitude above that has not found something the
390
+ field missed. The leakage canaries are used as tests of the checker itself, so the
391
+ contestant that peeks at tomorrow's return cannot reach the board. Its first run refused
392
+ four of my own factor submissions, which is exactly what it is for.
393
+
394
+ Frozen means frozen, and it is verifiable rather than promised: each entry pins the
395
+ sha256 of its own signal file, and because entries arrive by pull request, the public git
396
+ history dates the registration. Neither the content nor the date can move afterwards
397
+ without leaving a trace.
398
+
399
+ ```bash
400
+ python scripts/validate_submissions.py --folder submissions_pit/my_model
401
+ ```
402
+
403
+ ## Use it as a library, or from the shell
404
+
405
+ ```python
406
+ from beatnothing import Backtest, net_edge
407
+ from beatnothing.leaderboard import load_actual, bar_returns
408
+ actual = load_actual("data/actual_returns.parquet") # dates x tickers, next day returns
409
+ mine = Backtest(actual, predictions=my_predictions) # or weights=my_weights
410
+ print(net_edge(mine.daily_returns, bar_returns(actual))) # net edge, interval, clears_bar
411
+ ```
412
+
413
+ ```bash
414
+ beatnothing score my_signal.parquet --actual data/actual_returns.parquet
415
+ beatnothing members 2020-03-16
416
+ beatnothing leaderboard --root .
417
+ ```
418
+
419
+ Every number in the tables above rebuilds from a clean clone in about two minutes, with
420
+ no data download and no API key, because the realised returns and every contestant's
421
+ frozen signal are in the repository. This was checked from a fresh clone into an empty
422
+ environment, not assumed:
423
+
424
+ ```bash
425
+ git clone https://github.com/kaustubhspatil/beatnothing && cd beatnothing
426
+ pip install -e ".[dev]"
427
+ pytest -q # 50 tests: engine, canaries, statistics, submission rules
428
+ beatnothing leaderboard --track pit # rebuilds the board and its verdicts
429
+ ```
430
+
431
+ To go further back, to the raw prices and the universe itself:
432
+
433
+ ```bash
434
+ pip install -e ".[data,figures]"
435
+ python scripts/download_data.py # prices, features, realised returns, a hashed manifest
436
+ python scripts/build_pit_universe.py # the point in time universe, needs a free Tiingo token
437
+ python scripts/validate_stats.py # remeasure the statistics themselves
438
+ python scripts/make_figures.py pit
439
+ ```
440
+
441
+ ## Roadmap
442
+
443
+ 1. **Reach 2008.** The universe stops at 2013 because the free tier cannot supply the companies that died in the financial crisis. A paid archive would extend it, and a crisis is the one regime this benchmark has never tested anything in.
444
+ 2. **An LLM agent contestant**, in the spirit of StockBench, under the same costs and the same bar.
445
+ 3. **A technical report with a DOI**, so the method can be cited rather than linked. Anyone whose contestant is merged and survives a year of forward track is a named author on it.
446
+ 4. **A year of forward track.** The workflow is armed; the calendar does the rest.
447
+
448
+ ## What a skeptic should attack, and what happens when they do
449
+
450
+ Two things a quant would go for first, both checked rather than argued.
451
+
452
+ **"Your dead companies quietly disappear before they lose the money."** They do not. The
453
+ bar holds Silicon Valley Bank through its 60.4% day on 8 March 2023 and keeps it until the
454
+ index dropped it on the 15th, a cumulative 84.6% loss over its scored life. It holds First
455
+ Republic to a 90.5% single day and a 99.8% loss before removal on 4 May. Those are the two
456
+ largest single day losses in the whole panel, and they are in the bar's return, not
457
+ excluded from it. Exactly one name in the entire window stops trading more than a week
458
+ before its removal date, and that is Juniper being acquired.
459
+
460
+ **"Your membership dates leak."** Index changes are announced several days before they take
461
+ effect, so a table built from announcements would drop a failing name early and quietly
462
+ avoid part of its loss. The table here uses effective dates: Silicon Valley Bank leaves on
463
+ 15 March 2023 and First Republic on 4 May 2023, both the effective dates, not the earlier
464
+ announcements. Additions are treated the same way, so the benchmark also forgoes the pop
465
+ that a stock gets on the announcement of its inclusion.
466
+
467
+ ## Honest limits
468
+
469
+ * The season one universe is a survivor universe: 48 names chosen in August 2026, ten of which joined the index after 2005. It flatters every contestant and the bar equally, so Net Edge survives it. Absolute numbers do not.
470
+ * The contestants were trained on that survivor universe and evaluated on the point in time one, which removes the hindsight from the evaluation but not from the training. `scripts/train_on_pit.py` is the fix and season three is the run.
471
+ * Costs are a flat 10 bps of turnover with no market impact and no size dependence, which is the friction a small book pays, plus a borrow charge that is a constant rather than a per name rate. The sensitivity columns exist because neither number should be trusted alone.
472
+ * Five hundred large capitalization names. Most published cross sectional edge lives in smaller companies, which this universe does not contain.
473
+ * Net Edge compares Sharpe ratios. A contestant whose value is a lower drawdown, or a low correlation with everything else, is not measured by it.
474
+ * The leakage check is a smell test, not a proof. A determined submitter could add noise until the information coefficient drops under the threshold. What it catches is accidents, which is most of them.
475
+ * Eight months of forward track is not evidence. Check back in a year.
476
+
477
+ ## Cite
478
+
479
+ ```
480
+ Patil, K. (2026). beatnothing: a net of cost benchmark for daily equity signals. https://github.com/kaustubhspatil/beatnothing
481
+ ```
482
+
483
+ A `CITATION.cff` is included. The first five contestants come frozen from the
484
+ [deep learning equity signal capstone](https://github.com/kaustubhspatil/sp500-quantitative-deep-learning),
485
+ where their training is documented notebook by notebook.
486
+
487
+ ## Credits
488
+
489
+ Point in time membership: [fja05680/sp500](https://github.com/fja05680/sp500), MIT.
490
+ Kronos: Shi et al., *Kronos: A Foundation Model for the Language of Financial Markets*,
491
+ AAAI 2026, [the Kronos repository](https://github.com/shiyu-coder/Kronos), MIT. Stationary
492
+ bootstrap: Politis and Romano (1994). Probabilistic Sharpe ratio: Bailey and Lopez de
493
+ Prado (2012). Sharpe standard error: Lo (2002). Prices from Yahoo Finance through
494
+ yfinance; the snapshot hash is in `data/MANIFEST.json`.
495
+
496
+ MIT licensed. Built by Kaustubh Patil.