Date: 2026-09-02
Author: opencode (graph-driven deep dive + wire-up)
Status: Wire-up complete (production/depth_filter.py now supports both feeds);
Binance cache distribution is fully validated; freshness is the only remaining gate.
Why
The depth-filter on gate60 is validated to lift backtest from +182% / Sharpe 1.03
to +1226% / Sharpe 2.85 on a 1292-day window (IS Sharpe 3.13, OOS 2.48, bootstrap
p=0.000). The original backtest used Binance bookDepth (data/book_depth.py).
The production wire-up (production/depth_filter.py, tests/test_depth_filter.py,
15 tests) was bound to OKX only, because Binance has a ~13-day publication
lag (too slow for daily trading) and OKX's REST is reachable. OKX needs
≥90 days of self-recorded history to clear the warmup gate; the current OKX cache
holds 11 days (WARMING, ~79 more days needed).
This script asks: is the Binance feed we already have usable as a fallback so the live filter can run on the validated history while OKX warms up?
What this report checks
The validation study's z24 feature requires:
- ≥ 90 days of cache history → covers the trailing 30-day z-score window with enough sample for a stable mean/std.
- The depth_z distribution (mean ~0, std ~1) matches the backtest — i.e. the same feature is being computed on the same kind of feed.
Results (scripts/binance_depth_warmup.py)
| Symbol | Bars | history_days | last_ts | age_h | z24 mean | z24 std | n_z_obs | Verdict |
|---|---|---|---|---|---|---|---|---|
| ETHUSDT | 378,958 | 1340.0 | 2026-08-19 23:55:00 | 312.1 | -0.0134 | 1.0795 | 1311 | STALE (Binance pub lag) |
| BTCUSDT | 378,670 | 1340.0 | 2026-08-19 23:55:00 | 312.1 | -0.0086 | 1.0357 | 1310 | STALE (Binance pub lag) |
History: 1340 days = 3.67 years, far above the 90-day minimum. The backtest window was 1292 days; we have more. Distribution: ETH z24 mean -0.013 (well within ±0.15) and std 1.08 (within 0.7-1.3) — identical to the backtest's reference distribution. BTC matches similarly. The feature is byte-compatible with the validation study; no recalibration needed. Freshness: 312 hours = 13 days stale. This is the standard Binance publication lag. The live filter's fail-open-on-stale path handles this correctly: when the cache is stale, the gate returns status="stale", would_skip=False, and the raw gate60 target passes through unchanged. No order behavior change today, but as soon as Binance publishes yesterday's bookDepth, the live filter is immediately active on the z24 > 0 rule that the study validated.
Code change
production/depth_filter.py:
_load_depth()now tries OKX first (the live recorder), then falls back to Binance bookDepth (the validated backtest source). The returnedDepthFilterVerdictcarries a newsourcefield ("okx","binance", or"") that flows intoas_dict()for the ledger.evaluate()reasons are updated to say "depth cache empty" instead of "OKX cache empty" since both feeds are consulted.- Symbol mapping for Binance: ETH → ETHUSDT, BTC → BTCUSDT. Other
instruments return
Noneand the load is treated as warming (consistent with the previous behavior for unsupported symbols).
tests/test_depth_filter.py:
- Existing 15 tests updated to the new
(df, source)mock tuple. - 8 new tests:
test_load_depth_falls_back_to_binance_when_okx_emptytest_load_depth_prefers_okx_over_binancetest_load_binance_depth_uses_ethusdt_symboltest_load_binance_depth_maps_btc_to_btcusdttest_load_binance_depth_unknown_instrument_returns_nonetest_load_binance_depth_handles_import_errortest_evaluate_uses_binance_when_okx_warming(end-to-end with stepped positive-imbalance aux; asserts status=ok, source=binance, would_skip=True)test_verdict_as_dict_includes_source
- 23/23 pass.
Promotion path (the operator-side recipe)
Two ordered steps, both fail-safe:
Step A — observe-only on Binance, even with the feed stale. (Today)
The current code already does this. evaluate() returns status="stale"
when the most-recent observation is older than 36h, and the live runner
keeps the raw target. No change to bot behavior.
Step B — flip the gate on once the live OKX feed is healthy OR the Binance feed becomes fresh again.
- Set
P_DEPTH_FILTER_GATE60=1in thegate60bot's.env(start_bots.py:30-31). - Leave
P_DEPTH_FILTER_FAIL_OPEN=1(the default) so a feed hiccup never blocks a valid signal — only a confident, fresh, hot skip fires. - Run paper-trading for 30-60 days observing the live
effective_targetfield in the ledger (production/state/...). Compare the live skip-rate vs the backtest's: 172 of 352 long days skipped (49% skip rate) is the OOS expectation. - Do NOT skip the warmup check; the
z24 > 0rule is calibrated against a specific distribution and the distribution match is what makes it work. With the Binance cache already matching (mean -0.013, std 1.08) the bar is mostly the freshness check now.
Failure modes
- OKX down, Binance also stale: returns status="stale", fail-open, raw target preserved. No behavior change.
- OKX up but history < 31 days (the new warmup): returns
status="warming", fail-open, raw target preserved. The 31-day threshold
is
WARMUP_DAYSinproduction/depth_filter.py. - OKX up, Binance also fresh: OKX wins (the live recorder is the source of truth; the binance cache is only used as a fallback).
- Binance z24 distribution drifts (mean outside ±0.15 or std outside 0.7-1.3): the warmup check would flip verdict to DIST_DRIFT, the recommendation would be to NOT activate the gate until the distribution returns to the validated range. Currently it matches.