← The note
Reproducibility

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 configurations

That 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.

StateTP onlySL onlyTP + SLPosition sizeTrade filter
10.5x TP0.5x SL0.5x both0.5Rskip
20.75x TP0.75x SL0.75x both1Rskip
31x1x1x1Rtake
41.5x TP1.5x SL1.5x both1.5Rtake
52x TP2x SL2x both2Rtake

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 replay

Selection 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

FillFeeSlippageSpread
Entry, any reasontaker 5 bps2 bpshalf measured
Exit: take profit, or gap through the targetmaker 2 bpsnonenone
Exit: stop loss, or gap through the stoptaker 5 bps2 bpshalf measured
Exit: reverse-signal fliptaker 5 bps2 bpshalf 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-9

Inference

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

  1. pooled net result per common candidate exceeds the baseline
  2. executed-trade expectancy improves, where an undefined expectancy counts as a failure
  3. net result per unit of average nominal gross risk does not deteriorate
  4. the paired week-clustered difference has a BH-adjusted q below 0.05
  5. complete-window win rate above 50% and reward-to-risk above 1
  6. the difference stays positive after deleting the best out-of-sample window
  7. the result beats the 95th percentile of the matched action-rotation placebo
  8. 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:

CellStops (ATR)Median hold across the four basesHolds of two weeks or more
crypto:15m3.0, 6.06.2h to 38.5h0.0% to 4.4%
equity:15m3.0, 6.06.0h to 28.9h0.6% to 24.3%
crypto:1h1.5, 3.06.5h to 38.0h0.0% to 3.8%
equity:1h1.5, 2.257.0h to 21.0h0.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.

SuiteStudyAddendum
Execution parity, Rust engine against the Python referencepasspass
Pre-freeze calibration, holding period onlypasspass
Inference primitivespasspass
Analysis checkspasspass
State, selection, robustness and artifact paritynot re-runnot re-run
Planted-defect detection, causality and leak gatesnot re-runnot 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:

ArtifactSHA-256
strategy_configs.json, shared by bothd5c1fabbd22fcf20907e24025c6868f739afb11a2a952fc71241630f318cf68e
overlay_configs.json, studyc0eaaaedd74800e2e973dda23a23053ee7966a07762803e7b2592cabfed5320f
analysis_config.json, study1afcb713478d37d234268038280d9a83f38dfc535c8b1e3fbb9b6acc649e4fef
data_manifest.json, study93aefeaa5ad78d920fdaf48d36cc61794921eaaaa92cbf52f3b7f5119c855a59
strategy_selection.json, studya37718869d539141b64c492f1d221a7a9ba8395f7e9ca323d2c536028a21df4c
Analysis implementation, studye46041ab30b8f44ad1029a798e1d0bb58aa97959ba88bfb0558bf4468393600f
overlay_configs.json, addendum7d575275961bee72f5f6557ae5c253545f69cc352d5b9e35a216643714b7331e
Analysis implementation, addendum667f6ab6e65c2421c670179c6a5de8405209e2d8db35347e40fd71acb5fb4105

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.