DashboardThree-Axis ProbesCrypto Shared Data PoolRotating-slice orthogonality probe

probe · findings/evolution/shared_data/rotating_slice_orthogonality_probe.py

The auto-rotation coverage engine: testing features in every market condition

One engine that decides whether a new market feature is genuinely useful — by re-testing it in every kind of market (calm vs. wild, Asian hours vs. US/Europe hours, rising vs. falling) until no condition has been left under-tested. It keeps attacking whichever condition is least covered, so nothing slips through.

In plain English. Imagine a quality inspector with a checklist of every situation a new part must survive. Instead of testing whatever happens to be in front of them, they always pick up the item that has been checked the fewest times and check it next — over and over — until every item on the list has been inspected the same number of times. This probe is that inspector for trading "features": it keeps a written ledger of how many times each market condition has been tested, and always works on the most-neglected condition next, so coverage is something you can prove from the ledger, not just hope for.

The problem it solves

A "feature" here is a number computed from market data — a candidate signal we might add to the production system. Before we trust it, we need to know two things: is it orthogonal (genuinely new information, not just a copy of something we already have) and non-redundant (not a near-duplicate of a sibling candidate)? The honest way to answer is to test the feature across every market condition it would have to survive in — not just one slice of the calendar. The two obvious approaches both fail:

The rotation mechanism replaces both with a coverage-ledger-driven, deficit-first loop: it lists out the full grid of conditions up front, then keeps attacking whichever condition is least-tested until every reachable one has been tested the target number of times.

81
base cells (symbol × threshold)
≤18
regime strata per base cell
20
target paths per stratum (target_N)
47
shared candidate kernels
200
bar causal window (WIN)

The stratum grid

A stratum is one tiny, precisely-defined market situation — one cell of a big grid. The grid is the cross-product of these dimensions:

asset_class × symbol × threshold × vol(low/mid/high) × session(Asian/US-EU) × trend(up/flat/down)

Each dimension in plain terms:

The base cells — every symbol × threshold combination — number 19 forex + 62 crypto = 81. Each base cell is then split into up to 3 vol × 2 session × 3 trend = 18 regime strata. (A regime is just one of those vol×session×trend combinations — a recognizable "mood" of the market.) So the engine is juggling on the order of hundreds of individual market situations.

Why it stratifies on value-rows, not bars

This is the clever bit that resolves an awkward tension. The candidate kernels (the formulas being tested) each need a contiguous causal window of 200 bars to compute their value — "causal" meaning the window only ever looks backward in time, never at future data, and respects gaps in the data. (A kernel is simply one candidate calculation in the registry.)

Here's the problem: if you first chopped the input bars down to a single regime (say, only the high-volatility Asian-session bars), you would shatter that 200-bar window into disconnected fragments — there'd be no clean trailing window to compute from. The fix is to stratify on the output values, not the input bars:

  1. Fetch the full, continuous bar series for a base cell once.
  2. Compute the candidate matrix — run every kernel at every STRIDE-th bar (stride = 20, so values overlap 10× across the 200-bar window). Each output value still sees a proper, unbroken trailing 200-bar window.
  3. Attach the regime label (vol × session × trend) to each output value-row — a value-row being one computed result together with its market-condition tag.
  4. A stratum's data is then just the subset of value-rows whose label matches that stratum.

The result: the kernels always see clean, look-ahead-free windows, while the sorting into market conditions happens afterward, on the outputs. The session-vs-window tension disappears.

The rotation loop

The engine works in batches, and it is fully resumable — it reads a saved ledger each time, so you can stop and restart. Each batch:

It loops batch after batch until every feasible stratum has reached target_N = 20 tested paths.

The fused pass — two questions in one sweep

For each path, the engine answers both core questions at once:

ORTHOGONALITY candidate vs. production
Compares the candidate against the existing production columns using two measures: Spearman |ρ| — rank correlation, how strongly the two move together in the same order (close to 1 = redundant, near 0 = independent) — and Chatterjee ξ — a newer dependence score that also catches non-straight-line relationships a plain correlation would miss. Low on both = genuinely new information.
REDUNDANCY candidate vs. siblings
Compares the candidate against the sibling candidates (the other features being considered) using Leave-out R² — how well the other candidates can predict this one (high = this candidate adds nothing) — plus the max |Spearman| against any single sibling. High on either = this feature is a near-duplicate of something already in the running.

One nuance: at the crypto 750 threshold (the spearman_only tier), each slice holds too few bars for the Chatterjee ξ to be reliable, so ξ is skipped there; the Spearman measure still runs.

The flowchart, verbatim from the source

  ┌──────────────────────────────────────────────────────────────────────┐
  │ BUILD STRATUM GRID  (once, on real bars)                              │
  │   asset × symbol × threshold × vol(L/M/H) × session(Asian/US-EU)       │
  │                                              × trend(up/flat/down)      │
  │   base cells: 19 forex + 62 crypto = 81  →  × up to 18 regime strata   │
  │   each tagged FEASIBLE (≥40 value-rows) or INFEASIBLE(reason) in ledger│
  └───────────────────────────────┬──────────────────────────────────────┘
                                  │
  ╔═══════════════════════════════▼══════════════════════════════════════╗
  ║ PER BATCH   (resumable · reads the persisted ledger)                   ║
  ║  1. deficit[s] = target_N − paths_done[s]   for every FEASIBLE s       ║
  ║  2. pick the K strata with the LARGEST deficit   (least-covered first) ║
  ║  3. group picks by cell; fetch + compute candidate matrix ONCE per cell║
  ║  4. for each picked stratum:                                          ║
  ║       a) take its REAL value-rows                                     ║
  ║       b) stationary block-bootstrap resample (Politis–Romano)         ║
  ║       c) FUSED PASS:  ρ + ξ vs production   +   LOO-R² + ρ vs siblings ║
  ║       d) paths_done[s] += 1 ; append result                           ║
  ║  5. persist ledger                                                    ║
  ╚═══════════════════════════════╤══════════════════════════════════════╝
                                  │
            ┌──────────────────────▼──────────────────────┐
            │  every FEASIBLE stratum reached target_N ?   │
            └───────┬───────────────────────────┬──────────┘
                NO  │                            │  YES
        ┌───────────▼──────────┐      ┌──────────▼─────────────────────────┐
        │ next batch loops back │      │ COVERAGE-COMPLETE                   │
        │ → auto-fills thinnest │      │  • verdict = worst-case over strata │
        │   (least-covered) cell│      │    + cross-asset replication        │
        └───────────────────────┘      │  • emit coverage map (N per cell;   │
                                       │    infeasible cells listed w/ reason)│
                                       └─────────────────────────────────────┘

Engine constants (from the source): WIN=200 (kernel window), STRIDE=20, FLOOR_BARS=1000 (min bars for reliable Chatterjee ξ), MIN_STRATUM_ROWS=40 (a stratum needs ≥40 value-rows to be testable), TARGET_N=20, VOL_SMOOTH=500, FETCH_BARS_DEFAULT=80000 recent bars pulled per cell.

What guarantees coverage

The guarantee comes from the ledger, not from randomness. Because every batch attacks the largest-deficit stratum, coverage fills in evenly and provably: the loop literally cannot terminate while any feasible stratum is still below target_N.

PITSLARP. The source names this design principle: a coverage ledger that always drives the least-covered stratum first is a no-silent-drift control — coverage is provable from the ledger, infeasible cells carry an explicit reason (no unexplained divergence), and kernels only ever see causal trailing windows (no future data leaks in).

Symbol & threshold plan

The exact instruments and thresholds the engine sweeps. This is the single source of truth (configurable in the ASSETS block at the top of the engine).

Forex — 4 symbols, 19 cells

symbolproduction thresholds (dbps)
EURUSD1, 2, 3, 5, 10, 25, 50
GBPUSD5, 10, 25, 50
XAGUSD5, 10, 25, 50
XAUUSD5, 10, 25, 50

Crypto — 16 symbols, 62 cells (thresholds 100/250/500/750)

All live in opendeviationbar_cache.open_deviation_bars (read from ClickHouse, the production columnar database, read-only). The 750-dbps threshold is the spearman-only tier (ξ skipped — too few bars per slice).

dispositionsymbolsthresholds
INCLUDE — deep historyADAUSDT, AVAXUSDT, AAVEUSDT, BCHUSDT, BNBUSDT, BTCUSDT, DOGEUSDT, FILUSDT, LINKUSDT, LTCUSDT, NEARUSDT, SOLUSDT, UNIUSDT, XRPUSDT100 / 250 / 500 / 750
INCLUDE — flagETHUSDT100 / 250 only (no 500/750 exist)
INCLUDE — flagSUIUSDT (youngest, ~3 yr)100 / 250 / 500 / 750 (750 thinnest ≈ 80k bars)

No symbol is dropped without a reason. The legacy 6-symbol set was a tractability choice under 2026-05-30 data limits (BTC/ETH only back to 2021, LTC@100 broken) that have since been repaired/backfilled — all 16 are now viable, so the engine uses all 16. The same 47 candidate kernels run on both asset classes (resolved from the shared mql5 campaign probe), which is exactly what makes the cross-asset comparison meaningful.

Column surface covered

Per slice, each candidate is compared against the usable production columns for that cell — the columns that pass a quality filter (≥20% non-null AND ≥5 distinct values):

Validation evidence

Two real-bars runs on bigblack confirm the engine works as intended — including a deliberate "we already know the answer" sanity check.

assetcellstratafeasiblepathsprod colssample (edge_spread_bps)
cryptoAAVEUSDT@1001817324ρ=0.37, ξ=0.40, sib-ρ=0.48
forexEURUSD@118133131ρ≈1.0, ξ=1.0, LOO-R²=1.0
The redundancy sanity check. The forex row is the key proof. edge_spread_bps is one of the 9 already-shipped features — so it's now a production column, meaning it should be flagged as completely redundant with itself. The engine reports ρ≈ξ≈LOO-R²≈1.0 — a perfect match — which is exactly correct: the redundancy detector works. By contrast, on crypto AAVEUSDT@100 the same candidate scores low-to-moderate (ρ=0.37, ξ=0.40), behaving like genuinely partial overlap.
Bottom line. This engine turns "did we test the feature everywhere it matters?" from a hope into a provable fact: a written ledger always steers testing toward the least-covered market condition, and a feature is only promoted if it survives the worst condition and replicates across both forex and crypto.