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 data.
Data and universe
Two panels, both at 15-minute and one-hour bars from January 2021 to July 2026. Crypto is Binance USDT-margined perpetual OHLCV with the measured quoted spread per symbol-month and every funding event at its settlement timestamp. Equities are split-adjusted consolidated bars restricted to NBBO-eligible trades in the regular session, with odd lots and off-exchange trade reports excluded, keyed by a permanent security identifier rather than the ticker and carrying a measured time-weighted NBBO spread per bar.
The universe was fixed on 31 December 2022 from October to December 2022 liquidity alone, taking the ten most liquid names in each market. MATIC stops trading in September 2024 and is left absent afterwards rather than replaced, since a replacement chosen later would use information about which contracts survived.
| Market | Instruments |
|---|---|
| Crypto | BTCUSDT, ETHUSDT, DOGEUSDT, XRPUSDT, BNBUSDT, SOLUSDT, MATICUSDT, LTCUSDT, CHZUSDT, ETCUSDT |
| Equities | TSLA, AAPL, NVDA, AMZN, MSFT, AMD, META, GOOGL, NFLX, XOM |
Rule library
Fourteen families, thirty parameterisations each, for 420 rules. For every family the generator enumerates the grid below, drops invalid and duplicate settings, then takes 30 space-filling configurations with seed 20260804. The library is serialised canonically and hashed, and every runner refuses to start if the hash differs.
| Family | Signal at the close of bar t | Grid |
|---|---|---|
| Trend following | Side of a moving trend, if its strength clears a threshold | lookback 12, 24, 48, 96; strength 0, 0.5, 1 vol units; hold 1, 2, 4, 8, 16 |
| Mean reversion | Against a standardised deviation from a rolling mean | lookback 12, 24, 48, 96; entry z 0.5, 1, 1.5, 2; hold 1, 2, 4, 8 |
| Breakout | Close beyond the prior rolling close extreme | lookback 6 to 96; buffer 0, 5, 10 bp; hold 1, 2, 4, 8 |
| Momentum | Sign of a lagged cumulative return above a strength | lookback 3 to 96; strength 0, 0.25, 0.5, 1 vol units; hold 1, 2, 4, 8 |
| Volatility expansion | Short-horizon direction when short vol exceeds long vol | short 6, 12; long 24, 48, 96; ratio 1.25, 1.5, 2; direction 1, 3; hold 1, 2, 4 |
| Volatility contraction | Short-horizon direction during compression | short 6, 12; long 24, 48, 96; ratio 0.4, 0.6, 0.8; direction 1, 3; hold 1, 2, 4 |
| Moving-average crossover | Completed-bar fast/slow EMA cross | fast 3 to 24; slow 12 to 96, fast below slow; hold 1 to 16 |
| Channel breakout | Close beyond a mean plus or minus an ATR channel | lookback 12 to 96; width 0.5, 1, 1.5, 2 ATR; hold 1, 2, 4, 8 |
| RSI reversal | Buy oversold, sell overbought | RSI 6, 14, 28; bounds 20/80, 30/70, 40/60; hold 1, 2, 4, 8 |
| Bollinger reversal | Against a close outside the band | lookback 12, 20, 48, 96; width 1 to 2.5 sd; hold 1, 2, 4, 8 |
| Donchian breakout | Close beyond the prior high/low channel | lookback 12 to 96; hold 1 to 16; optional one-bar confirmation |
| Volume confirmation | Momentum only when normalised volume clears a ratio | momentum 3 to 24; volume lookback 12 to 96; ratio 1, 1.5, 2; hold 1, 2, 4, 8 |
| Opening-range breakout | Break of the first bars of the market day | range 1, 2, 4 bars; buffer 0, 5, 10 bp; latest entry 0.5, 0.75 of the day; hold 1, 2, 4 |
| Time based | Fixed session bucket and direction | session fraction 0, 0.25, 0.5, 0.75; long or short; weekday mask; hold 1, 2, 4, 8 |
A signal is an entry transition: it fires when a rule first enters a long or short condition, while repeated bars in the same condition are not new signals. Inside each 24-month training window a rule is dropped only if it has fewer than 120 completed trades, trades fewer than 5 instruments or produces exactly the same signal set as a lower-numbered rule, and no profitability statistic is ever used to drop one.
Seven volatility-expansion settings never trade, because their required ratio of short to long volatility is out of reach for those window lengths, and the duplicate check compares signals without the holding period, so rules that differ only in how long they hold collapse to the lowest-numbered one. On average 392 to 396 of the 420 rules are eligible in a given window.
Execution
The decision is made after bar t completes. The position enters at the open of bar t+1 and exits at the close of the h-th held bar, where h is the rule's holding period in observed bars. There are no stops or targets, so the order of a bar's high and low never decides an exit.
Each rule-instrument pair holds at most one candidate position, and a signal that arrives while one is open is ignored; a model that skips a trade never creates a replacement.
entry = signal_idx + 1 exit = signal_idx + hold # close of the hold-th held bar gross_bps = direction * log(close[exit] / open[entry]) * 1e4 net_bps = gross_bps - spread_bps - fee_bps - slippage_bps + funding_bps
Features
Sixty-two price and volume features computed at bar t within each instrument's own history: lagged returns and momentum at 1 to 24 bars, high-low range, candle body and wick fractions, close-to-open gap, volatility, moving-average distance, position in range and distance from highs and lows at 6 to 96 bars, plus normalised volume. Seven more are added: trend strength (absolute 24-bar moving-average distance over 24-bar volatility), session position and day of week as sine and cosine pairs, bars left in the market day and the log close of bar t.
The rule contributes its family as a one-hot vector, its parameters scaled to their grid position with missing-parameter flags, the signal's direction and strength, the intended holding period and the bars since its previous signal. Recent performance is the mean, sum, wins and losses of the rule's last 20 completed trades in that instrument, alongside the mean, wins and losses of its last 100 completed trades across instruments, counting only trades that exited at or before the decision bar.
# strategy-level trailing 100: only trades that have already exited count
for p in trades_sorted_by_entry:
while exits[cursor].exit_ts <= p.signal_ts:
ring.push(exits[cursor].net_bps); cursor += 1
p.recent_100_mean = ring.mean()Execution data (the next bar's open, every cost column and funding) is stored on each row and removed from the model matrix. Session position and bars remaining use the day's bar count, which on a day with missing bars reflects the gap; those days carry between 0.002% and 0.33% of trades depending on the cell, and dropping them moves the best book from +0.748% to +0.761%.
A trade that entered inside the training window but exits after it is excluded from both training and the out-of-sample set, so it also drops out of the 20-trade history, which leaves that history slightly stale but never ahead of the decision.
Models and search
Three families, each fitted separately for the net-return target and the profit target, each with 30 fixed configurations. The regression target is clipped at its 0.5% and 99.5% training quantiles and standardised. Failed, constant and non-converging configurations count toward the 30 and cannot be chosen.
| Family | Implementation | Grid range over the 30 configurations |
|---|---|---|
| Linear | Ridge for pure L2, elastic net otherwise; logistic elastic net for profit | alpha 0.0001 to 0.03; L1 ratio 0 to 1 |
| Boosted trees | LightGBM, 150 trees, no row or column subsampling | depth 2 to 6; leaves 4 to 31; learning rate 0.0054 to 0.091; min child 104 to 4,645; L2 0.0014 to 68 |
| Neural net | GELU MLP, 6 epochs, batch 8,192 | depth 1 to 3; width 16 to 128; learning rate 0.0001 to 0.0028; weight decay 1.5e-7 to 0.0094 |
Inside each window the training rows split chronologically 80/20 into inner training and validation, with 16 bars of clock time removed before the split. The net-return models are selected on validation Spearman rank IC and the profit models on validation ROC AUC, ties going to the lower configuration number. The winner is refitted on the full 24 months with its preprocessing fitted on those rows only.
Walk-forward
Forty-three monthly out-of-sample windows, January 2023 to July 2026, in each of four market-timeframe cells, for 172 windows. Each window trains on exactly the preceding 24 calendar months, using only trades whose entry and exit both fall inside them, then predicts every eligible trade entered in the next calendar month once. One model per market, timeframe, family and target pools all rules and instruments.
for cell in ["equity_1h", "equity_15m", "crypto_1h", "crypto_15m"]:
for month in months("2023-01", "2026-07"):
is_rows = trades(entry >= month - 24M, exit < month, eligible_rules(month))
oos_rows = trades(month <= entry < month + 1M, eligible_rules(month))
for family in ("linear", "gbt", "mlp"):
for target in ("net_return", "profit"):
best = select(family, target, is_rows, n_configs=30) # 80/20 inner split
model = refit(best, is_rows)
oos_rows[f"pred_{family}_{target}"] = model.predict(oos_rows)That gives 1,032 selected models from 30,960 attempted configurations, 30,394 of which fitted successfully, and 239,452,104 out-of-sample predictions over 39,908,684 candidate trades.
Trading policies
Every rule-instrument pair gets the same base weight, w0 = 1 / (S × 10) with S the number of eligible rules in the window, so taking every signal can never exceed 100% gross exposure.
baseline w = w0
simple filter w = w0 if at least 2 of { vol_24, trend_strength_24, vol_norm_24 }
exceed the instrument's median over the 24 training months
ML filter w = w0 if prediction > 0 bp (net return) or > 0.5 (profit)
ML sizing q = rank of prediction in the window's sorted validation predictions
w = w0 * (0.25 + 1.5 * q)
ML ranking at each signal timestamp keep ceil(20%) of the new signals, at least one,
by prediction; w = w0 * min(5, n_candidates / n_kept)Simple-filter medians come from unique market bars in the training window, not from trade rows, so a bar with many signals is not counted many times. A filter that takes no trade in a month leaves that month in cash.
Costs
| Market | Spread | Fee per fill | Slippage per fill | Funding |
|---|---|---|---|---|
| Crypto | Half the measured spread at entry plus half at exit | 5 bp | 2 bp | Every actual event from the entry bar to the exit bar, signed |
| Equities | Half the measured per-bar spread at entry plus half at exit | 0.5 bp | 0 | None |
Positive funding debits longs and credits shorts, and funding events are assigned to the bar that contains them, so a trade entering at the open of a settlement bar pays that settlement. Funding stays small next to fees: taking every crypto one-hour signal nets -0.28% of capital in funding over the run against 174.59% paid in fees. Costs are identical for every policy, which is why the per-trade cost bar in the note barely moves while the gross bar does.
Inference and claim bars
Economics come from the concatenated out-of-sample ledger and a non-overlapping daily return series, with P&L booked at each trade's exit. The note reports summed daily returns, since compounding pins every crypto book near -100%; the registered net-return bar uses the compounded series.
Each contrast is a policy minus its comparator on the daily series. Uncertainty is a paired bootstrap resampling whole weeks, 10,000 replicates with seed 20260804, and Benjamini-Hochberg runs across all 144 contrasts: 4 cells, 18 model policies and 2 comparators, failures included.
weeks = daily_delta.groupby(week) # week-clustered resampling
boot = [weeks.sample(n_weeks, replace=True).sum() for _ in range(10_000)]
p = two_sided(boot)
q = benjamini_hochberg(p_all_144)
# rotation null: same timestamps, same instrument, same count, same exposure budget
for r in range(1_000):
shifted = circular_shift(scores, offset=nonzero, within=(cell, month, instrument))
null[r] = net_of_policy(select(shifted))
passes_rotation = policy_net > quantile(null, 0.95)A contrast is credited when all seven bars hold: net return above the comparator, net return per unit of average gross exposure above the comparator, q below 0.05, more than half of the 43 months won, monthly reward-to-risk above 1, a positive lead after deleting the best month and a result above the 95th percentile of the rotation null. When a policy wins every month the reward-to-risk ratio is undefined and the bar fails; twelve contrasts are in that position, six in equity 15-minute bars and six in crypto one-hour bars.
The general claim needs a credited result in both markets, both timeframes and at least two of the three model families. The absolute weekly figures quoted in the note for single books come from a separate week-clustered bootstrap of each book's own weekly P&L.
Checks
Before any out-of-sample number existed, the panel-level suite confirmed that truncating the future leaves every earlier feature, signal and trade unchanged (maximum difference 0.0), that a planted target leak lifts validation IC from 0.017 to 0.9999, that no cost or funding column reaches the model matrix, that funding is event-timed with long positions debited when the rate is positive, that known equity splits are adjusted and that next-bar targets align to within 1.7e-6 bp. A Python reference matched the compiled signal engine on 57,648 trades from one configuration in every family on 50,000 real BTCUSDT bars.
After the run, every one of the 39,908,684 out-of-sample candidate rows was checked against the source panels: entry one bar after the signal, exit after exactly the holding period, entry price equal to the panel open and exit price to the panel close, gross return, spread, fee, slippage and funding recomputed from raw inputs, the net identity, entry inside the scoring month and the price feature equal to the log close of the signal bar. Every filter, sizing, ranking and simple-filter weight was rebuilt from the stored predictions and training medians, and the pooled totals of all 92 policy books rebuild to within 5.3e-15.
The equity spreads are the per-bar values frozen with the study. Re-pricing every equity trade with a later re-pull of the same spreads moves no policy by more than 0.0024 of capital and changes no sign. The bootstrap p-values, BH q-values and rotation-null exceedances also reproduce exactly from the daily series.
Hashes
| Artifact | SHA-256 |
|---|---|
| Rule library | 501f04df5537f881709027e8755eade85d7ca667acb0051d1fcd4b9dc18bb14a |
| Model grids | 6c6d89b6cfd435ce875876820443405f5d383956a5673ea38f5d2d410b490e47 |
| Policies | df469ad083dde73c7be52ed5d33ad4d434faa684a74e7777c200b6d5b988bf1d |
| Features | eedebd594bfa8aa9c56fb282e7d065eef06c99b63a0d07f974668a526862cc02 |
| Combined configuration | 6fc808d41d71d9930b17eb81a0b6b0d2189ba9b7e084896e70c61a3020c94f0f |
| Analysis configuration | 1e3af923775cf8fca30f2c28da0ecab7803320454cfafd15e9101a33d53b5735 |
| Analysis implementation | 86f433e42fe14560425f6da690754e5b603509b4b9581bba56d6bd1bd447fb3b |
Every number the note's charts draw is in one JSON file. The full pipeline, from signal generation through the walk-forward fits to the audit scripts, is available on request at daniel@daru.finance.

