Terrain — Backend Notes for UI Wiring
Ground truth for the Strategy Lab / Terrain Workbench wiring. Authoritative for the backend (this repo: ClickHouse + the Python API). Frontend routing and theme are covered by the app-repo wiring gap analysis and are out of scope here. Last verified against code 2026-06-24.0. Naming
Terrain is Sequency’s regime classification & detection system. It replaces the internal working terms “vNext” / “regime_v2” in all product/UI naming.- Terrain v1 = the 2×3 grid.
- Terrain v2 = the multi-axis read.
regime_v2, RegimeSnapshotV2, regime_vnext/,
regime_archive) keep their names for now; “Terrain” is the product-facing name.
Product name → code symbol map:
Current canon — cite these, not the
docs/superpowers/specs/_deprecated/ set:
- Program charter —
docs/superpowers/specs/2026-06-11-regime-engine-program-charter.md - Terrain v2 design —
docs/superpowers/specs/2026-06-21-regime-vnext-design.md(currently local-only — not yet committed tomain) - Locked prereg + evidence —
docs/superpowers/specs/2026-06-22-regime-vnext-prereg-reset.md - Production-policy design —
docs/superpowers/specs/2026-06-22-regime-vnext-production-policy-readiness-design.md - SP1/SP2/SP3 designs;
docs/api/screener-regime-intraday.md;docs/runbooks/regime-vnext-rd.md
1. The taxonomies + EXACT enums (stop guessing these)
Terrain v1 — the 2×3 grid
Direction × Volatility. Persisted daily indaily_regime_labels.regime_grid.
- Direction:
BULL|SIDEWAYS|BEAR(note:SIDEWAYS, not “NEUTRAL”, in the label string) - Volatility:
LOW_VOL|HIGH_VOL - Cell string: e.g.
BULL_LOW_VOL,BEAR_HIGH_VOL(6 cells)
Terrain v2 — the 3-axis read (backtester-derivable subset)
risk_state:stress|elevated|calm|normalvolatility_change:expanding|compressing|stable← NOTrisingdirectional_pressure:upside_extended|balanced|downside_extended← nopersistent_trend- Cell string:
"{risk_state}|{volatility_change}|{directional_pressure}"— e.g.calm|stable|upside_extended,normal|stable|balanced
Important: theRegimeSnapshotV2model defines 6 axes (the above 3 plusbreadth_state,correlation_state,intraday_structure). Those three have no point-in-time source inregime_archive, so the backtester / historical path produces only the 3 axes above. Do not render the other three for any historical/backtest view — they don’t exist there.
2. Data provenance + pins (corrections to the handoff)
- Terrain v2 axes are NOT persisted daily. Only the 2×3 grid is stored daily
(
daily_regime_labels). Terrain v2 is derived on-the-fly fromregime_archiveviaservices/regime_vnext/comparison.py/backtester/regime_v2.py(derive_regime_v2_axes_from_snapshot). This is the biggest hidden cost of the Workbench, and it is the recommended slice-1 path. - Pins:
logic_version = 'sp1-v1'(the invented'rl-2026.06.2'does not exist).variant:'emitted'— point-in-time, look-ahead-free. PIT already exists; the limit is data volume (forward accrual), not schema.'corrected'— descriptive / full-history backfill. Use this for descriptive displays and backtest attribution.
- A persisted daily Terrain-v2 table (full backfill) is NOT required for slice 1, and is NOT blocked by the regime-program governance charter — that charter governs alpha certification (promoting regime-conditioning to a live-money gate), not a descriptive display table. Treat backfill as a future performance optimization, not a prerequisite.
3. Terrain v2 ↔ v1 grid projection (for the UI)
Both derive from the sameregime_archive substrate (slope_pctile, vol_pctile,
shock), so v2 maps onto the familiar grid:
v1 and v2 are computed independently (v1 = persisted grid; v2 = derived), so they
agree but are not a strict per-row mechanical projection. The UI should read v1 for
the grid headline and v2 for the axis drill-down — both are stored (§4.2), so no
client-side projection is needed.
4. Two distinct backend surfaces — do NOT conflate them
4.1 Terrain analysis (market-level) — NET-NEW, still to build
Purpose: current/historical market regime + per-regime market attribution (the Terrain Workbench).- ~70% of the math exists offline in
services/regime_vnext/comparison.py(readsregime_archive+ daily rollups, derives the v2 axes, computes per-regime attribution). It has no HTTP route yet — that route is the critical path. - Route naming: use
/api/graph/v1/terrain/.... The singular/api/graph/v1/regimeprefix is taken (regime.py+regime_v2.py); a plural/regimeswould work but/terrainboth avoids the collision and adopts the product name.regime_vnext.pyexists but is currently unmounted. - Slice-1 sourcing: derive on-the-fly (§2).
variant='corrected'for full history,variant='emitted'for honest PIT.
4.2 Strategy effectiveness across Terrain — ALREADY SHIPPED (PR #1165 + #1166)
Purpose: how a strategy performed across regimes (the heatmap). You do not need to build this aggregation — it already exists and accrues automatically.-
Table:
backtest_regime_effectiveness(ClickHouse, DBsequency). Written on every backtest completion (best-effort; never blocks a run), for both the rule-driven and cross-sectional paths. -
Stores both taxonomies per (strategy, cell): rows with
regime_taxonomy='terrain_v1'(grid cells) andregime_taxonomy='terrain_v2'(3-axis cells). -
Schema:
Engine:
ReplacingMergeTree(computed_at),ORDER BY (strategy_id, regime_taxonomy, regime_cell, run_id). -
UI usage: read
terrain_v1rows for the 6-cell grid heatmap headline; readterrain_v2rows for the axis drill-down. Query “strategy X across all runs in cell Y” directly off this table. -
Migration:
clickhouse/058_backtest_regime_effectiveness.sql. Accumulator:api/app/services/backtester/regime_effectiveness.py.
5. Quick reference for the wiring agent
- It’s Terrain (v1 grid / v2 3-axis), not “vNext”. Name the design-system module
lib/terrain.tsand the tokens accordingly. - Use the exact enums in §1 —
compressing|stable|expanding, norising, nopersistent_trend. - Pins:
logic_version='sp1-v1';variantemitted(PIT) /corrected(descriptive). - New work = the market-level Terrain analysis route (§4.1). Strategy-across-Terrain data is done (§4.2) — don’t rebuild it.
- Theme + frontend routing (
/api/v4,/market/pulse, providers.tsx default): see the app-repo gap analysis; those are not backend concerns.