Every number in the note, and where it came from
Enough method to check the arithmetic by hand, or to rebuild an equivalent study from the same public and licensed sources, including what the addendum changes and the hash each run is frozen under.
Data and universe
Crypto. Ten Binance USDT-margined perpetuals, selected on 31 December 2022 by median daily notional volume over October to December 2022: BTC, ETH, DOGE, XRP, BNB, SOL, MATIC, LTC, CHZ and ETC. Klines and the full funding-rate history come from the Binance public archives.
Equities. Ten large-cap US single stocks selected the same way: TSLA, AAPL, NVDA, AMZN, MSFT, AMD, META, GOOGL, NFLX and XOM. The source is 1-minute NBBO-eligible trade and quote data, keyed by a stable security identifier with adjustment factors chained across identifier changes, so splits and ticker reuse cannot corrupt returns.
Scope. Two bar frequencies, 15-minute and 1-hour, giving four cells: crypto:15m, crypto:1h, equity:15m and equity:1h. Input history starts 1 January 2021 and out-of-sample scoring runs from 1 January 2023 through 31 July 2026.
Every input file is hashed in a data manifest that the run verifies before touching a single bar. Funding, spreads, fees, slippage, high and low timestamps, path-order fields, forward returns and trade outcomes are execution metadata, and they are prohibited from base signal definitions and from equity-state inputs.
The strategy library, and how it was calibrated
Eighty mechanical strategies across eight families, every one sign-symmetric. The library was calibrated once, on January 2021 to December 2022 only, so that holding periods resemble those of a discretionary intraday and swing trader: most positions closing within hours, with a tail reaching about two weeks. The calibration reads candidate counts, holding periods and instrument coverage, and reads no return, Sharpe, win rate or drawdown of any kind. It converged in three rounds with five replacements, all in the RSI reversal family, every one for thin coverage.
A candidate fires only when a strategy's directional view actually inverts, long to short or short to long. A neutral bar is not a signal and does not reset the view:
# The candidate grid: genuine inversions only, never a re-entry from neutral. view = raw_view.replace(0, method="ffill") # neutral holds the previous view signal = view != view.shift(1) # a transition, not a level
This matters more than it looks. Treating every re-entry from neutral as a fresh signal makes a rule whose condition flickers around a band emit a candidate each time, and counting only genuine inversions cut candidate counts roughly threefold.
Those signal transitions are also the common candidate grid on which every policy is compared. At every transition each policy closes whatever it holds and opens a position in the newly signalled direction, so trade boundaries and trade counts are identical across policies by construction, and the only thing that differs is how each trade ended and at what price.
Barriers, and the baseline the treatments must beat
Barrier width and reward-to-risk are strategy parameters, not universal constants, so they are tuned in sample per strategy-window on the registered objective and counted in the same search budget as the overlays.
stop in {3, 6} ATR x reward-to-risk in {1.5, 3} = 4 base configurationsThat yields target and stop pairs of (4.5, 3), (9, 3), (9, 6) and (18, 6) ATR. ATR is Wilder ATR(14) computed through the completed signal bar and frozen for the life of the trade, and one unit of risk is one entry-time ATR move at baseline size. Tuning the baseline this way raises the bar the treatments have to clear: the honest comparison is against tuned static management, not against a universal two-to-one strawman that is easy to improve on.
Equity-state variables
Eight oriented variables, each defined so that a larger value always means better recent performance: cumulative net result over the previous 10 trades; the same over 50; equity minus its 20-trade moving average divided by the standard deviation of the previous 20 increments; maximum drawdown over the previous 50 completed trades, stored as a non-positive value; signed streak length; win rate over 20 trades; win rate over 50; and mean net result over 20 trades.
state = {
"cum_r_10": net_r.rolling(10).sum(),
"cum_r_50": net_r.rolling(50).sum(),
"equity_vs_ma": (equity - equity.rolling(20).mean()) / net_r.rolling(20).std(),
"max_dd_50": (equity - equity.rolling(50).max()).rolling(50).min(), # <= 0
"streak": signed_streak(net_r > 0),
"win_rate_20": (net_r > 0).rolling(20).mean(),
"win_rate_50": (net_r > 0).rolling(50).mean(),
"mean_r_20": net_r.rolling(20).mean(),
}A ninth candidate, current drawdown against an all-time running peak, was removed on structural grounds before the run. For a strategy with negative expectancy it is unbounded and drifts monotonically downward, so thresholds fitted in sample are permanently exceeded out of sample: in a measured example the entire out-of-sample range fell below the lowest in-sample cutpoint and the filter executed zero trades. That is a stationarity failure, observable without reference to profitability, which is why the windowed 50-trade drawdown is used instead.
Until a variable's lookback is complete, management stays at the selected baseline and the decision trace records insufficient_history. One equity curve exists per base strategy, market, timeframe and policy, and a cell's ten instruments share it.
The management library and the search budget
Overlay actions are multipliers on the selected base, so a family keeps its meaning while the base floats. The mean-reversion response reverses the state-to-action mapping.
| State | TP only | SL only | TP + SL | Position size | Trade filter |
|---|---|---|---|---|---|
| 1 | 0.5x TP | 0.5x SL | 0.5x both | 0.5R | skip |
| 2 | 0.75x TP | 0.75x SL | 0.75x both | 1R | skip |
| 3 | 1x | 1x | 1x | 1R | take |
| 4 | 1.5x TP | 1.5x SL | 1.5x both | 1.5R | take |
| 5 | 2x TP | 2x SL | 2x both | 2R | take |
Eight state variables times two directions gives 16 overlays per family, and 16 overlays times 4 base configurations gives 64 trials per family per window. Every family gets exactly the same budget, which is what equal search means here: equal attempted configurations, not an equal number of knobs.
State sources are chosen so no family can manufacture its own state. The barrier families read their own realized net result; position sizing reads its own realized result at unit size, so leverage cannot inflate the variable that drives it; trade filtering reads a continuously updated baseline shadow book, so the off state can never be absorbing.
Walk-forward
Fourteen full quarterly out-of-sample windows from 2023Q1 through 2026Q2, plus one registered partial window covering July 2026 alone, which is scored, flagged and disclosed wherever it is included. In-sample is the preceding 24 calendar months in every case, and calendar boundaries are identical across markets.
for window in quarterly_windows(oos_start="2023-01-01", oos_end="2026-08-01"):
grid = candidate_grid(strategy, window) # no equity state, no OOS values
is_candidates = [c for c in grid if c.entry_ts < window.oos_start
and c.exit_ts < window.oos_start]
thresholds = state_thresholds(baseline_replay(is_candidates))
attempts = [simulate(cfg, is_candidates, thresholds) for cfg in family_grid] # all 64
persist(attempts) # including degenerate and inactive
winner = max(attempts, key=lambda a: (a.net_r_per_common_candidate,
a.executed_trades, -a.canonical_id))
winner.freeze() # config and thresholds fixed
score(winner, window.oos_slice) # OOS starts flat, state from IS replaySelection is chronological, every attempt is persisted including the ones that did nothing, and the selected overlay's out-of-sample state is initialized from its completed in-sample replay.
Intrabar execution
The prepared panels carry the timestamp of each parent bar's ultimate high and low, so the intrabar path is reconstructed as open, first recorded extreme, second recorded extreme, close, with barrier crossings evaluated along those monotone segments.
A gap through a stop exits at the first available open, while a gap through a target fills at the target, which is what a resting limit order does. If only one barrier is reachable it executes; if both are, the recorded extreme order decides; and if that order is unknown the stop executes first, with the trades that depended on that convention counted and their economic contribution disclosed.
Cost model
| Fill | Fee | Slippage | Spread |
|---|---|---|---|
| Entry, any reason | taker 5 bps | 2 bps | half measured |
| Exit: take profit, or gap through the target | maker 2 bps | none | none |
| Exit: stop loss, or gap through the stop | taker 5 bps | 2 bps | half measured |
| Exit: reverse-signal flip | taker 5 bps | 2 bps | half measured |
Equities pay a flat 0.5 bps commission and fee allowance per fill with no maker or taker distinction. Crypto funding is charged at every settlement an open position crosses, at its observed sign. Over 2021 to 2026 funding was positive 74.5% of the time and averaged +0.87 bp per eight hours, roughly 9.5% a year against a permanently long book, which is why it is reported decomposed by trade direction rather than folded into a single net number.
Every ledger keeps gross price result, entry and exit spread, entry and exit fee, entry and exit slippage, signed funding and net result as separate columns, and the identity has to reconcile below 1e-9 risk units:
net_r = (gross_price_r
- entry_spread_r - exit_spread_r
- entry_fee_r - exit_fee_r
- entry_slip_r - exit_slip_r
+ funding_r) # signed: debits longs when funding is positive
assert abs(net_r - ledger.net_r).max() < 1e-9Inference
The paired bootstrap resamples whole Monday-to-Sunday calendar weeks, taking every instrument and strategy inside a week together, at 10,000 resamples with a fixed seed. Row-wise resampling, averaging window Sharpes and multiplying the window count into the strategy count are all prohibited.
weeks = pd.Grouper(key="ts", freq="W-MON") blocks = [df for _, df in paired_deltas.groupby(weeks)] # whole weeks move together draws = [np.concatenate(rng.choice(blocks, len(blocks))).mean() for _ in range(10_000)] p = 2 * min((np.array(draws) <= 0).mean(), (np.array(draws) >= 0).mean()) q = benjamini_hochberg(p_values, alpha=0.05) # 20 contrasts in the declared family
Multiplicity is corrected within the declared family of 2 markets by 2 timeframes by 5 management families, being 20 contrasts, failures included.
The action-rotation placebo takes the same policy and rotates its actions across the same candidates at 1,000 seeded draws, with an exposure match as a blocking check: the rotated null has to reproduce the real overlay's turnover, executed count and nominal action exposure exactly, so the only thing destroyed is the alignment between the equity state and the trade.
The perturbation suite runs 20 candidate-dropout draws at 1% and 20 fill shocks with costs scaled between 0.75x and 1.25x, and requires that at least 80% of draws retain the sign of the effect, that median decision agreement stays above 80%, and that median equity-curve correlation stays above 0.9.
The eight claim bars
- pooled net result per common candidate exceeds the baseline
- executed-trade expectancy improves, where an undefined expectancy counts as a failure
- net result per unit of average nominal gross risk does not deteriorate
- the paired week-clustered difference has a BH-adjusted q below 0.05
- complete-window win rate above 50% and reward-to-risk above 1
- the difference stays positive after deleting the best out-of-sample window
- the result beats the 95th percentile of the matched action-rotation placebo
- the path-dependence stability gate passes
Bar 4 failed in all 20 comparisons of the study and all 24 of the addendum. Bar 2 is written so that a zero-turnover policy fails it, because such a policy can beat a losing baseline on bar 1 by doing nothing at all, and the analysis suite tests exactly that case.
What the addendum changes
Universe, data, timeframes, walk-forward schedule, the in-sample-tunable barrier grid, the eight equity-state variables, the 64-trial budget, the selection rule, eligibility, the bootstrap, the placebo, the perturbation suite and the eight claim bars are all inherited unchanged. Four things differ.
Exits
A position closes only when its take-profit or its stop-loss executes. If neither has executed by the end of the instrument's history the trade is truncated at the last observed bar and flagged, and the truncation rate is a required reported statistic, since a large one would mean the barriers do not resolve inside the sample and the protocol has not delivered the fair test it promises.
Barrier widths
Removing the reversal exit lengthens holds, so the stop grid was recalibrated per cell before the run, reading holding period and resolution only:
| Cell | Stops (ATR) | Median hold across the four bases | Holds of two weeks or more |
|---|---|---|---|
| crypto:15m | 3.0, 6.0 | 6.2h to 38.5h | 0.0% to 4.4% |
| equity:15m | 3.0, 6.0 | 6.0h to 28.9h | 0.6% to 24.3% |
| crypto:1h | 1.5, 3.0 | 6.5h to 38.0h | 0.0% to 3.8% |
| equity:1h | 1.5, 2.25 | 7.0h to 21.0h | 0.3% to 13.7% |
Fifteen of the sixteen cell-and-base combinations sit inside the agreed band. The exception is equity:15m at its widest base, reported in the note's limitations rather than removed.
Occupancy
Each branch resolves a signal arriving while a position is open, and both are scored on the same denominator, being every signal transition in the window, which is fixed by the rule and the price series:
if branch == "single" and book.holds(instrument):
decision = "declined_as_occupied" # earns nothing, still counts in the denominator
else:
book.open(instrument, side, size, tp, sl)A policy that is busy and skips a signal simply earns nothing from it. Trade counts then differ between policies, which is an outcome to report rather than a broken comparison, and turnover is reported alongside.
Multiplicity
The addendum is corrected inside its own declared family of 3 families by 4 cells by 2 branches, being 24 contrasts. The two branches are corrected together because both were run and both are reported, so scoring them separately would understate how much was searched. The addendum's contrasts are never pooled with the study's, and neither re-scores the other.
Verification, and the frozen hashes
The Rust execution engine was checked line by line against an independently written Python reference implementation. On the addendum's engine they agree exactly, to a 1e-9 tolerance, on every exit bar, exit price and net result across 362,100 rows. Parity is the suite that matters most there, because removing the reversal exit changed the execution core itself, and the freeze refuses a verdict recorded before the code it certifies, so no verdict is inherited from the study's engine.
| Suite | Study | Addendum |
|---|---|---|
| Execution parity, Rust engine against the Python reference | pass | pass |
| Pre-freeze calibration, holding period only | pass | pass |
| Inference primitives | pass | pass |
| Analysis checks | pass | pass |
| State, selection, robustness and artifact parity | not re-run | not re-run |
| Planted-defect detection, causality and leak gates | not re-run | not re-run |
Both were specified in writing and frozen before any out-of-sample number was produced or opened, and every reported number is generated from stored artifacts rather than typed by hand. The addendum carries its own overlay and implementation hashes, which is what makes it an addition rather than a re-scoring:
| Artifact | SHA-256 |
|---|---|
| strategy_configs.json, shared by both | d5c1fabbd22fcf20907e24025c6868f739afb11a2a952fc71241630f318cf68e |
| overlay_configs.json, study | c0eaaaedd74800e2e973dda23a23053ee7966a07762803e7b2592cabfed5320f |
| analysis_config.json, study | 1afcb713478d37d234268038280d9a83f38dfc535c8b1e3fbb9b6acc649e4fef |
| data_manifest.json, study | 93aefeaa5ad78d920fdaf48d36cc61794921eaaaa92cbf52f3b7f5119c855a59 |
| strategy_selection.json, study | a37718869d539141b64c492f1d221a7a9ba8395f7e9ca323d2c536028a21df4c |
| Analysis implementation, study | e46041ab30b8f44ad1029a798e1d0bb58aa97959ba88bfb0558bf4468393600f |
| overlay_configs.json, addendum | 7d575275961bee72f5f6557ae5c253545f69cc352d5b9e35a216643714b7331e |
| Analysis implementation, addendum | 667f6ab6e65c2421c670179c6a5de8405209e2d8db35347e40fd71acb5fb4105 |
Requesting the full pipeline
This page is enough to check every number in the note by hand, or to rebuild an equivalent study from the same public and licensed data sources. The production scripts, meaning the data pull, panel construction, the Rust engine, the experiment runner and the analysis, are not published here. Email daniel@daru.finance and I will send them over.

