# Continuous-Reasoning Trader — Architecture Design

**Date:** 2026-05-20
**Branch context:** `ml-alpha-phase-a` (S2 closed at `8828e8ab1`; Phase A attempt fe2498769 RED on Gate 1)
**Status:** Design v3 — phase split collapsed after empirical Gate 1 failure + architectural clarification
**Supersedes scope of:** `2026-05-19-isv-driven-stop-controller-design.md`

---

## v3 ARCHITECTURE RESET (the section that supersedes everything below)

**Date:** 2026-05-20 (same day as v2; cluster smoke evidence forced the reset)

**Empirical trigger.** Phase A as designed in v2 (continuous controller with scalar conviction-EMA on `max(|p−0.5|)`, rescale on target magnitude) failed Gate 1 catastrophically. Smokes `vjmwc` (commit `3d8f12deb`) and `lkrdf` (commit `fe2498769` — with the amplification-clamp fix) both produced:
- 155k+ trades (62× baseline 2,511)
- max-drawdown 9,100% (175× baseline 53.5%)
- Sharpe-ann −15.7 (vs baseline −6.6)
- ~83% of realized loss = round-trip cost (155k × $12.50)

The clamp fix `min(scale, 1.0)` prevented the most pathological amplification but had effectively zero impact on the catastrophic outcome — confirming the bug is structural, not in the scalar EMA formula.

**Architectural finding.** The phase split v2 made (A: continuous control / B: signal-driven position management) is artificial. The two are the same mechanism. A scalar EMA on `max(|p−0.5|)` cannot smooth a controller that's churning on direction flips between horizons; only the multi-horizon ISV-weighted conviction (was §4.4, deferred to Phase B in v2) is structurally coherent.

**The user-validated mental model.** The agent should reason like a discretionary trader: the multi-horizon signal vector at any moment IS a forward prediction of the trade trajectory; the optimal position is the inventory implied by current beliefs; trade actions are sparse because most events produce only fractional adjustments to a stable optimum. Continuous evaluation; discrete action.

### v3 phase structure (REPLACES v2's A/B/C/D)

```
CRT.1  Unified Inventory Controller    (was Phase A + Phase B merged)
       - forward_step every event (already shipped: A0.5 / commit a0e81fbdf, 92f8b10ed)
       - decision_stride deleted (already shipped: A1 / commit 045850e8f)
       - Multi-horizon ISV-weighted conviction (v2 §4.4 formula — promoted from Phase B)
       - Wiener-α EMA on the multi-horizon conviction (v2 §4.2 — promoted)
       - target_lots = round(direction × |conviction_ema| × envelope_max)
       - No-trade band in seed_inflight: skip if |delta| < δ
       - open_trade_state 24→64 byte expansion (v2 §7 — promoted from Phase B)
       - Conviction-degradation exit (v2 §4.3): EMERGENT from target→0 when conviction collapses,
         plus a composite-signal safety layer matching v2 §4.3

CRT.2  Adaptive Risk Envelope          (was Layer C)
       - ISV-derived max_lots, threshold, target_vol  (unchanged from v2 §5)

CRT.3  Online Weight Adaptation        (was Layer D)
       - LoRA adapter + EWC++ + shadow eval  (unchanged from v2 §6)
```

### Why the v2 phase split was wrong

1. **Direction jitter, not magnitude noise, is the dominant source of hyperactivity.** Scalar EMA on `max(|p − 0.5|)` smooths magnitude only; doesn't smooth direction. The multi-horizon ISV-weighted conviction formula `weight_h × magnitude_h × direction_h` summed across horizons — particularly with ISV weights that down-weight low-SNR horizons — naturally smooths direction because disagreeing horizons partially cancel.

2. **A target-position controller without a no-trade band cannot work at event rate.** Even with perfect smoothing, target moves fractionally every event; with target-delta seed_inflight semantics, every fractional move generates an order. The no-trade band (skip seeding if |Δ| < δ) is structurally required; deferring it to "Phase B" left Phase A unrunnable.

3. **Conviction-degradation exit is not a separate exit policy — it emerges from `target_lots → 0` when conviction collapses.** Splitting "continuous control" from "signal-driven exit" doubly-implements what's actually one mechanism.

### Inheritance from v2

- §4.4 conviction formula (with ε-floor net_edge; not the v1 backwards-fallback): **promoted, unchanged.**
- §4.2 Wiener-α adaptive EMA: **promoted, applied to the multi-horizon conviction scalar instead of `max(|p−0.5|)`.**
- §4.3 composite exit signal: **emergent + safety.** The unified target-→-zero path handles the common case; the v2 §4.3 composite formula remains as a safety layer for cases where target-derivation hits an edge (e.g., conviction stuck above zero but trade is deep in loss).
- §5 (CRT.2) and §6 (CRT.3): **unchanged.**
- §7 (open_trade_state 64-byte layout): **promoted to CRT.1.**
- §8 hardcoded-vs-adaptive boundary: **unchanged.**

### Gate restructure (v3)

| v2 Gate | v3 status |
|---|---|
| Gate 1 (Layer A continuous controller) | REMOVED — A and B are co-shipped as CRT.1 |
| Gate 2 (Layer B multi-horizon + exit + state expansion) | **REPROMOTED as Gate CRT.1**, with the v2 §9 Gate 2 tiered MUST/WIN criteria intact |
| Gate 3 (Layer C envelope) | unchanged → Gate CRT.2 |
| Gate 4 (Layer D online) | unchanged → Gate CRT.3 |

### What survives from v1/v2 commits

The following commits are load-bearing and STAY on the branch:
- `a0e81fbdf` — forward_step incremental SSM (A0.5)
- `92f8b10ed` — forward_step_into eliminates GPU↔CPU round-trip (A0.5 corrective)
- `045850e8f` — delete decision_stride field (A1; greenfields atomic)
- `3d8f12deb` — buffer-level seed bit-identity test (A0.5 polish)

What gets REPLACED in CRT.1 implementation:
- `1d889d2de` (A2 scalar conviction-EMA with aggregate rescale) — **superseded** by the multi-horizon §4.4 formula
- `fe2498769` (A2.1 clamp on the broken rescale) — **superseded** for the same reason

The amplification clamp in `fe2498769` and the scalar EMA infrastructure in `1d889d2de` are not load-bearing for CRT.1; the multi-horizon formula replaces the scalar approach end-to-end. Code-wise: the three conviction-EMA device slots (`conviction_ema_d`, `conviction_diff_var_ema_d`, `conviction_sample_var_ema_d`) STAY in `LobSimCuda` — the EMA mechanism is reused on the multi-horizon conviction scalar. What gets deleted: the `final_size *= scale` rescale step in both decision kernels.

---

## v2 changelog (retained for audit trail; superseded by v3 above)

**v2 changelog** (vs v1 commit `0f3484325`):
1. §4.4 conviction: removed backwards cold-start fallback; replaced with ε floor on net_edge for smooth bootstrap. No regime switch when model has zero edge.
2. §7 open_trade_state: 128 → 64 bytes. Observability fields moved to a separate audit buffer (single source of truth keeps state minimal).
3. §4.3 exit logic: 5 OR conditions → 1 composite exit_signal scalar with attribution. Safety (SL/trail/circuit-breaker) orthogonal.
4. §5.1 envelope: multiplicative → additive bounded contributions. No single factor can crash envelope to zero.
5. §5.2 threshold: hand-waved "learn toward PF" → explicit 3-arm bandit with Wiener-α target EMA, bounded [0.5, 0.95].
6. §9 Gate 2: PF > 1.0 (unrealistic) → tiered MUST/WIN criteria with explicit STOP condition.
7. §9.1.1: defined alpha-realization metric explicitly (was pointed-at, now formulated).
8. §3.4 + §3.6: Layer A wall-time target ≤ 5× → ≤ 2× (controller is light, 5× would mean structural bug).
9. §4.2: explicit Wiener-α formula for conviction EMA (was hand-waved).
10. §6.2.1: shadow eval defined with explicit modes (backtest / live) and metrics.
11. §6.2: Layer D loss explicit (primary = value regression; secondary = EWC++ regularizer).
12. §10.5 added: adapter regime over-fit risk with sample-count down-weighting + cross-regime shadow eval.
13. Greenfields per user direction: removed all backwards-compat shims (decision_stride field deleted, not deprecated; no 24-byte open_trade_state compat).

---

## 1. Motivation

The S2 cluster smoke (commit `8828e8ab1`, smoke `tgg4l`) ran on clean data with the ISV stop controller working end-to-end. Result: Sharpe_ann = -6.64, PF = 0.40, win_rate = 24.2%, max_dd = 53.5%. Three independent bug layers had been masking this baseline ([sentinel injection](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_behavioral_sentinel_values_poison_downstream_consumers.md), [decision-rate vs event-rate max_hold](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_stop_checks_run_at_deadline_cadence.md), no-PnL on force-close). Now visible: the policy layer is the alpha leak.

**The signal layer operates at event-rate, stateful, multi-horizon.** Mamba2 SSM state advances every snapshot; ISV per-horizon EMAs (`pnl_ema_loss`, `pnl_ema_win`, `realised_return_var`) track per-horizon. The trunk produces alpha probabilities at h100/h500/h1500/h6000 simultaneously. [State amplifies short-horizon into long-horizon](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_state_amplifies_short_horizon_into_long_horizon.md) — AUC 0.50→0.66 at k=6000.

**The policy layer collapses that continuous state into discrete actions every 200 events** with hardcoded contracts (`max_lots=5`, `max_hold_ns=60s`, `threshold=0.0`, `decision_stride=200`). Between decisions, ~199 events of evolved state are discarded. Hardcoded YAML values substitute for what should be signal-derived envelopes.

**Thesis: the policy layer must match the signal layer's continuity, statefulness, and multi-horizon dimensionality.** Hardcoded contracts replaced by ISV-derived envelopes. Time-in-trade emergent, not bound. Position size continuously adjusted to conviction. Online weight adaptation amplifies whatever the architecture is doing right.

This spec specifies the rebuild.

---

## 2. Architectural Vision (v3 — see "v3 ARCHITECTURE RESET" section above)

**Three phases, sequenced.** The original v2 four-layer split has been merged into three because the empirical Gate-1 evidence + user clarification showed that v2 Layer A (continuous control) and Layer B (signal-driven position management) are the same mechanism.

```
┌────────────────────────────────────────────────────────────────────┐
│ CRT.1  Unified Inventory Controller                                 │
│        Continuous evaluation (every event), discrete action.        │
│                                                                     │
│        - forward_step every event (already shipped via A0.5)        │
│        - Multi-horizon ISV-weighted conviction (§4.4)               │
│        - target_lots = direction × |conviction_ema| × envelope_max  │
│        - Wiener-α EMA on the multi-horizon conviction (§4.2)        │
│        - No-trade band on |target − current| in seed_inflight       │
│        - open_trade_state 24→64 expansion for trajectory (§7)       │
│        - Conviction-degradation exit emergent (target→0) +          │
│          composite-signal safety circuit-breaker (§4.3)             │
│                                                                     │
│        Addresses: stale-state decisions, magnitude noise, direction │
│        jitter, fixed-time exits, spread-cost death-spiral.          │
├────────────────────────────────────────────────────────────────────┤
│ CRT.2  Adaptive Risk Envelope                                       │
│        ISV-derived max_lots / threshold / target_vol from rolling   │
│        Sharpe × drawdown × regime-vol.                              │
│        Addresses: fixed risk in regime-varying market.              │
├────────────────────────────────────────────────────────────────────┤
│ CRT.3  Online Weight Adaptation                                     │
│        LoRA + EWC++ + offline-batch trajectory updates + shadow     │
│        eval + circuit-breaker revert.                               │
│        Addresses: static model in non-stationary market.            │
└────────────────────────────────────────────────────────────────────┘
```

**Sequencing rationale (v3):** CRT.1 unifies the signal-to-action mechanism — without it, neither CRT.2 nor CRT.3 has a coherent base to amplify. The v2 phased validation gates collapse one tier: CRT.1's gate is what was Gate 2 (Layer B) in v2; Gate 1 (Layer A) is gone because A standalone is incoherent. The "every gate green before the next" discipline survives.

**The mental model.** Every event: the signal vector is a forward prediction; ISV state tells us how much to trust each horizon; the unified conviction is the weighted aggregate; the target is the optimal inventory given current beliefs. Most events: target moves fractionally from prior — the no-trade band absorbs the noise, no order seeded. When the signal genuinely shifts (or the trade thesis breaks): target moves enough to cross the band, an order seeds, the position adjusts. Continuous evaluation, discrete action. No fixed cadence — the signal IS the cadence.

The detailed sections below were authored in v2 and remain accurate for the mechanisms they describe; the only structural change is that v2 §3 (Layer A) is absorbed into CRT.1, v2 §4 (Layer B) is renamed CRT.1, v2 §5 → CRT.2, v2 §6 → CRT.3.

---

## 3. Layer A — Continuous Control Loop (v2; ABSORBED INTO CRT.1 IN v3)

**v3 status:** the components of this layer are part of CRT.1. A0.5 (forward_step every event, commits `a0e81fbdf` + `92f8b10ed`) and A1 (decision_stride deletion, commit `045850e8f`) are load-bearing for CRT.1 and stay. The original "Gate 1" for Layer A alone is dropped; validation moves to Gate CRT.1.

### 3.1 Current state

`crates/ml-backtesting/src/sim/mod.rs` decision flow runs in the harness loop at `decision_stride=200` events. Between decisions: book updates, ATR EMA, ISV updates, fill processing, pnl_track close detection — all continuous. Controller (`decision_policy_default`/`decision_policy_program` + `seed_inflight_limits_batched`) — strictly periodic.

### 3.2 Target state

Controller runs every event. Trunk forward pass (already every event) feeds the latest alpha probabilities directly into the controller every event. `seed_inflight`'s target-delta semantics absorb continuous target changes by seeding incremental orders.

### 3.3 Files touched (greenfields — no backwards-compat)

- `crates/ml-backtesting/src/harness.rs`: remove `if event_idx % decision_stride == 0` gate around `step_decision`. Controller fires every event unconditionally.
- `crates/ml-backtesting/src/sim/mod.rs`: `step_decision` invoked every event from harness; internal kernel batching unchanged.
- `bin/fxt-backtest/src/main.rs`: REMOVE `decision_stride` field from `SweepBase` and `ResolvedSimVariant`. No deprecation shim, no compat-warning, no field-kept-for-backwards-compat. Per [feedback_no_legacy_aliases](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_legacy_aliases.md).
- `config/ml/*.yaml`: REMOVE `decision_stride` line from every YAML in one sweep. Per [feedback_no_partial_refactor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_partial_refactor.md) — every caller migrates atomically.

### 3.4 Cost target

The controller kernels (`decision_policy_*` and `seed_inflight_limits_batched`) are LIGHT: block-per-backtest, single-writer thread. 200× more invocations of light kernels should produce ≤ 2× total wall-time slowdown. If the smoke takes > 2× current (~95 s → 190 s+), something is structurally wrong with the controller cost model — pause and profile, do not paper over.

**Target:** sim throughput ≥ 10000 events/sec (vs current ~21000). Wall-time ≤ 2× current.

### 3.5 Failure mode addressed

Decisions land on stale signal: at decision_stride=200 events on ES Q1 data (~3.9s sim-time per event), entries happen up to ~13 minutes after the alpha signaled. Exits trigger up to ~13 minutes after conviction collapsed.

### 3.6 Layer A acceptance criteria (Gate 1)

- Smoke wall-time ≤ 2× stride=200 baseline (~95 s → ≤ 190 s).
- Trade-level entry timestamps cluster tighter to signal-spike timestamps than the stride=200 baseline (measurable via signal-vs-entry-ts cross-correlation; ≥ 30% reduction in mean signal-to-entry lag).
- `total_pnl_usd` and `sharpe_ann` within ±15% of stride=200 baseline (Layer A alone is not the alpha — its job is to unlock Layer B). Substantial regression (worse than −15%) blocks Gate 1.
- No new NaN/sentinel counters fire.

### 3.7 Layer A risk: high-frequency target oscillation

At event-rate, consecutive decisions see nearly-identical state. If the state→target map is sensitive, you get hyperactive oscillation (target +1, 0, +1, 0, ...) each costing the spread.

**Mitigation:** target sizing in §4.2 uses a **Wiener-α adaptive EMA with floor** — fast in regime changes, slow in steady state. The Wiener formula `α = diff_var / (diff_var + sample_var + ε)` is MSE-optimal for stationary signals; floor at 0.4 in this non-stationary control loop per [pearl_wiener_alpha_floor_for_nonstationary](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_alpha_floor_for_nonstationary.md). Floor prevents adaptive α from collapsing during quiet periods, which would slow genuine reversals.

---

## 4. CRT.1 — Unified Inventory Controller (was "Layer B" in v2)

This is the unified architecture in v3. Continuous evaluation, discrete action.

### 4.0 Required additions in v3 vs v2 §4

- **No-trade band**: `seed_inflight_limits_batched` must skip seeding when `|target_lots − effective_position| < delta_floor`. `delta_floor` is small (1 lot default for the bootstrap; CRT.2 may make it ISV-derived later). Without this, every fractional target change generates a fill — the v2 Gate-1 empirical failure mode.
- **Multi-horizon conviction promoted from §4.4**: this is the ONLY conviction formula in v3. The scalar `max(|p−0.5|)` approach used in v2's A2 commits is dead — it cannot smooth direction jitter and is structurally insufficient.

Everything else in v2 §4.1–§4.5 below is retained.

### 4.1 Multi-horizon action fusion

**Current:** `decision_policy_default` consumes `alpha_probs[N_HORIZONS]` and reduces them to a single decision via simple aggregation. `stop_check_isv` uses `open_horizon_masks` for stop evaluation only.

**Target:** different horizons drive different actions.

| Horizon | Role | Action it influences |
|---|---|---|
| h100 | Fast / microstructure | Entry timing (wait for short-horizon agreement) |
| h500 | Short-term | Mid-trade scale-up trigger |
| h1500 | Medium | Mid-trade adjustment (scale up/down) |
| h6000 | Slow / regime | Hold conviction; entry direction lock |

Concretely the policy logic becomes:

```
1. Direction: sign of h6000 alpha (the slow thesis).
2. Entry gate: h6000 |alpha − 0.5| > threshold AND h100 agrees in sign AND h500 agrees in sign.
   (Short-horizon agreement with long-horizon thesis = entry timing.)
3. Sizing: target_lots = clamp(conviction × envelope_max, 0, envelope_max).
4. Hold gate: h6000 conviction not yet degraded (see 4.3).
5. Scale-up: h500 + h1500 confirm h6000 direction AND conviction trajectory rising.
6. Scale-down: h500 OR h1500 diverge from h6000 (early-warning).
7. Exit: conviction-degradation policy (see 4.3).
```

### 4.2 Continuous sizing

`seed_inflight_limits_batched` already uses target-delta semantics (`crates/ml-backtesting/cuda/resting_orders.cu` line 652-672). Target changes any event → delta computed → incremental order seeded.

Sizing formula (with explicit Wiener-α adaptive EMA):

```
conviction          = isv_weighted_agreement(alpha_probs, isv_state, mask)   // see §4.4

// Wiener-α adaptive EMA on conviction. diff_var tracks event-to-event
// changes in conviction; sample_var tracks variance of conviction itself.
// α automatically high when signal is genuinely changing, low when stable.
diff_var            = EMA((conviction_now − conviction_prev)², α=0.1)
sample_var          = EMA((conviction_now − conviction_ema_prev)², α=0.1)
alpha_raw           = diff_var / (diff_var + sample_var + ε)
alpha_active        = max(alpha_raw, 0.4)                                    // floor for non-stationarity

conviction_ema      = alpha_active × conviction + (1 − alpha_active) × conviction_ema_prev

// Target sizing — direction from conviction_signed, magnitude from |conviction_ema|.
target_lots_signed  = round(direction × |conviction_ema| × envelope_max)     // envelope_max from §5.1
```

EMA prevents micro-oscillation churn. α floor at 0.4 per [pearl_wiener_alpha_floor_for_nonstationary](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_alpha_floor_for_nonstationary.md) — adaptive α alone collapses in quiet periods, slowing genuine reversals; the floor keeps minimum responsiveness. Per [pearl_wiener_optimal_adaptive_alpha](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_optimal_adaptive_alpha.md).

### 4.3 Conviction-degradation exit

Replaces `max_hold_ns` as the dominant exit mechanism. `max_hold_ns` becomes a high-default circuit-breaker (8 hours) for safety only.

**Single composite exit signal.** Five OR-ed conditions are 5 fire modes, 5 attribution headaches, and 5 bug surfaces (we just spent the day finding three independent bugs in `max_hold` alone). Instead, ONE scalar that crosses ONE threshold.

```
// All three terms are non-negative scalars in [0, ∞). Exit fires when ANY
// term exceeds 1.0 — but the COMPOSITION is one max-of-three, so attribution
// is by which term dominated at exit time (recorded in trade observability).

// Term 1: conviction has degraded relative to entry. Ratio < 1 means conviction
// is weakening; degradation_factor (ISV-derived) sets how much weakening triggers exit.
degradation_term     = (1.0 − conviction_ema / max(conviction_at_entry, ε)) / degradation_factor

// Term 2: short-horizon disagreement with long-horizon thesis. Counts
// consecutive events where h100 or h500 reversed sign relative to h6000.
// disagreement_factor (ISV-derived) sets tolerance.
disagreement_term    = short_horizon_disagreement_events / disagreement_factor

// Term 3: pnl-adjusted conviction deeply negative. Signal AND market both saying
// "this trade is wrong." pnl_decay_factor (ISV-derived) sets tolerance.
pnl_decay_term       = max(−pnl_adjusted_conviction_ema, 0) / pnl_decay_factor

exit_signal          = max(degradation_term, disagreement_term, pnl_decay_term)
exit_fires           = (exit_signal ≥ 1.0)

// Attribution recorded for observability (see §7 open_trade_state).
exit_cause           = argmax({degradation_term, disagreement_term, pnl_decay_term})
```

**ISV-derived factors:** `degradation_factor`, `disagreement_factor`, `pnl_decay_factor` are anchored from ISV state per [pearl_controller_anchors_isv_driven](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_controller_anchors_isv_driven.md). Not YAML constants.

**Safety layer (orthogonal, NOT exit policy):**
- SL/trail from existing `stop_check_isv` — runs at policy rate as before, fires for outsized adverse moves the conviction signal might not catch fast enough.
- Circuit-breaker `(current_ts − entry_ts) ≥ max_hold_circuit_breaker_ns`, default 8 hours — catches pathological infinite holds.

Time-in-trade is now emergent. Winners hold as long as conviction holds. Losers cut when the composite exit signal crosses threshold.

### 4.4 Conviction definition (ISV-weighted multi-horizon agreement)

For each event:

```
For each horizon h in {h100, h500, h1500, h6000}:
    alpha_h        = alpha_probs[h]
    direction_h    = sign(alpha_h − 0.5)
    magnitude_h    = |alpha_h − 0.5| × 2     // ∈ [0, 1]

    // ISV-derived weight per horizon. Rewards horizons with positive net
    // edge (win EMA exceeds loss EMA) AND low realised-return variance
    // (high SNR). The floor ε is small-positive (NOT zero) so that:
    //   - horizons with no positive edge contribute negligibly (weight ≈ ε / var)
    //   - the formula never branches to a "fallback" mode that maintains
    //     trading authority precisely when the model has no edge.
    // cost^2 in the denominator is the bootstrap sentinel per
    // pearl_trade_level_vol_for_stop_distance.
    net_edge_h     = max(isv[h].pnl_ema_win − isv[h].pnl_ema_loss, ε)
    weight_h       = net_edge_h / (isv[h].realised_return_var + cost^2)

    weighted_mag_h = magnitude_h × weight_h × direction_h   // signed

// Normalise by total weight. total_weight is ALWAYS > 0 because every
// weight_h is ≥ ε / (var + cost²) > 0 by construction. No cold-start
// branch needed — the floor IS the bootstrap. When all horizons have
// (near-)zero net edge, conviction naturally shrinks toward zero
// (small numerator, normalized denominator), and the §5.2 percentile
// gate suppresses trading. This is the correct behavior: no edge → no trade.
total_weight       = sum(|weight_h|)                              // > 0 by construction
conviction_signed  = sum(weighted_mag_h) / total_weight           // ∈ [-1, +1]

conviction         = |conviction_signed|                          // ∈ [0, 1]
direction          = sign(conviction_signed)
```

**ε choice:** small relative to typical post-bootstrap `net_edge_h`. Suggested `ε = cost / 100` so that pre-bootstrap weights are dominated by `cost / (cost²) = 1/cost` — i.e., the cost-bootstrap variance term dominates exactly when ISV variance hasn't bootstrapped yet, and ISV variance dominates after bootstrap. The transition is smooth, not branched.

Notes:
- Weights are ISV-derived ([pearl_controller_anchors_isv_driven](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_controller_anchors_isv_driven.md)). Per-horizon SNR drives how much that horizon contributes.
- Net edge with positive ε floor follows [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md): `max(real, floor)`, never branching to a different formula.
- Net edge asymmetric ([pearl_audit_unboundedness_for_implicit_asymmetry](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_audit_unboundedness_for_implicit_asymmetry.md)) — losing-edge horizons should drop out, not flip sign.
- `cost^2` bootstrap per [pearl_trade_level_vol_for_stop_distance](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_trade_level_vol_for_stop_distance.md): cost as sentinel-floor for variance until real var bootstraps.
- Normalization makes conviction scale-invariant per [pearl_zscore_normalization_for_magnitude_asymmetric_signals](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_zscore_normalization_for_magnitude_asymmetric_signals.md).
- Single unbounded multiplicand: `net_edge_h / (var + cost^2)` is the one unbounded factor; `magnitude_h × direction_h` is bounded in `[-1, +1]`. Per [pearl_one_unbounded_signal_per_reward](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_one_unbounded_signal_per_reward.md).
- Disagreeing horizons reduce conviction magnitude even if individually large — opposing signed `weighted_mag_h` terms partially cancel in the sum.
- No cold-start branch: ε floor smoothly bootstraps the formula without a regime switch (the regime switch in v1 was the bug — it MAINTAINED trading authority when net edge was zero across all horizons, which is exactly when the model is admitting it has no edge).

### 4.5 Layer B acceptance criteria (Gate 2)

See §9 Gate 2 for the canonical tiered MUST/WIN structure. Short form:
- **MUST**: hold-time emergent (circuit-breaker fires < 5%), no metric regression (Sharpe ≥ baseline − 0.5, PF ≥ baseline × 0.95, drawdown ≤ baseline × 1.10), conviction-degradation accounts for ≥ 60% of closes, exit attribution coherent, no new NaN counters.
- **WIN (one of)**: Sharpe lift ≥ +0.5; PF lift ≥ 30%; alpha-realization ratio (§9.1.1) lift ≥ +20%; drawdown reduction ≥ 20% at equal PnL.

---

## 5. Layer C — Adaptive Risk Envelope

ISV-derived bounds replace YAML constants.

### 5.1 `max_lots` envelope

Replaces hardcoded `max_lots: 5`.

**Additive bounded formula** (v1 multiplicative crashed envelope when any one factor → 0):

```
// Each contribution bounded; no factor can crash envelope to zero.
sharpe_term      = 0.5 × tanh(rolling_sharpe_short / sharpe_norm)        // ∈ [−0.5, +0.5]
dd_term          = 0.3 × min(drawdown_pct / dd_cap, 1.0)                  // ∈ [0, +0.3]
vol_term         = 0.2 × clamp(regime_vol / vol_norm, 0, 1)               // ∈ [0, +0.2]

envelope_factor  = 1 + sharpe_term − dd_term − vol_term                   // ∈ [0, 1.5]
envelope_lots    = max(envelope_floor, base_lots × envelope_factor)
envelope_max     = min(envelope_lots, envelope_hard_cap)
```

- `sharpe_norm`, `vol_norm`, `dd_cap` are ISV-derived anchors per [pearl_controller_anchors_isv_driven](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_controller_anchors_isv_driven.md). Not YAML constants.
- `envelope_floor` is a small positive integer (e.g., 1 lot) so we never reach "can't trade" state. [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md).
- `envelope_hard_cap` is a structural safety (default 50 lots), the only YAML knob in this layer.
- No single term can collapse the envelope. Even at max drawdown + max vol + min Sharpe: `1 − 0.5 − 0.3 − 0.2 = 0`; envelope hits floor, not zero.

### 5.2 `threshold` (conviction-percentile gate)

Replaces hardcoded `threshold: 0.0`. Conviction-percentile-based — trade only when current conviction exceeds the recent N-conviction percentile target.

```
conviction_history     = circular buffer of last N=256 convictions (per backtest, on-device)
percentile_target_ema  = EMA of percentile target with Wiener-α floor
threshold              = percentile(conviction_history, percentile_target_ema)
```

**Explicit self-tuning controller** (vs hand-waved "learn toward PF"):

```
// Every K_eval = 32 closed trades, compare PF at current percentile vs PF at adjacent percentiles.
// Move toward higher PF, bounded by structural limits.

every K_eval trades:
    pf_at_current   = profit_factor(last K_eval trades at current threshold)
    pf_at_higher    = profit_factor of trades that would have been kept at percentile + Δ
    pf_at_lower     = profit_factor of trades that would have been kept at percentile − Δ
    where Δ = 0.05

    best_dir        = argmax(pf_at_lower, pf_at_current, pf_at_higher)

    // Move target toward best_dir using Wiener-α adaptive EMA per pearl_wiener_optimal_adaptive_alpha
    target_proposal = percentile_target_ema + step_size × sign(best_dir)
    percentile_target_ema_new = α × target_proposal + (1 − α) × percentile_target_ema_old
    where α has floor 0.1 per pearl_wiener_alpha_floor_for_nonstationary

    // Hard structural bounds:
    percentile_target_ema = clamp(percentile_target_ema_new, 0.5, 0.95)
```

- Bounded by `[0.5, 0.95]` — never trade below median conviction, never demand top 5% only.
- `pf_at_higher` / `pf_at_lower` are counterfactual estimates from the historical conviction distribution; they don't require running parallel simulations.
- Wiener-α floor prevents thrash; the step size is one Δ per evaluation cycle.
- This is a 1-D bandit with three arms (lower, current, higher), evaluated every K_eval trades.

### 5.3 `target_annual_vol_units`

Replaces hardcoded `target_annual_vol_units: 50.0`. Regime-vol-inverse.

```
target_annual_vol      = base_vol_target × (regime_vol_baseline / current_regime_vol)
```

Defensive in volatile regimes, aggressive in calm regimes. Bounded by configured floor/cap.

### 5.4 Layer C acceptance criteria (Gate 3)

- Drawdown reduction at equal PnL (envelope-on vs envelope-off side-by-side smoke).
- OR PnL lift at equal drawdown.
- Envelope adapts smoothly — no thrash (envelope EMA on a slower timescale than trade-rate).
- Floor activates correctly when signal-derived envelope would underflow.

---

## 6. Layer D — Online Weight Adaptation

Most ambitious, deferred until A+B+C green.

### 6.1 Architecture

LoRA adapter on frozen base checkpoint. Low-rank delta applied to selected weight matrices. ~1-5% of base parameter count.

### 6.2 Update schedule

**Offline-batch, per session** — NOT per-trade in-loop.

```
For each session end (or every N=128 closed trades):
    1. Collect trajectories: (state_at_decision, action_taken, outcome_realized_pnl) tuples.

    2. Primary loss: value regression on realized PnL conditional on state.
       L_value = mean((predicted_value(state) − realized_pnl) ²)
       Single primary loss avoids multi-objective tuning ambiguity.

    3. EWC++ regularizer toward base checkpoint weights θ_base.
       L_ewc = λ_ewc × sum_i (F_i × (θ_adapter_i − θ_base_i) ²)
       F_i = Fisher information per parameter from the base training run.
       Prevents catastrophic forgetting; λ_ewc tuned at gate-4 time.

    4. Total loss: L = L_value + L_ewc

    5. Gradient step on LoRA adapter parameters only (base θ_base frozen).
       Small LR (e.g., 1e-5) — adapter changes per session bounded.

    6. Shadow eval (see §6.2.1).

    7. Promotion gate: if shadow eval passes → adapter checkpoint promoted to live.
       Otherwise → adapter reverted to last-promoted checkpoint.
```

### 6.2.1 Shadow eval — explicit definition

Shadow eval validates the new adapter on real data BEFORE promoting it to production trading. Three modes depending on operational context:

**Mode A (backtest / walk-forward):** hold out the last 15% of each fold as a shadow window. Apply candidate adapter to predictions on the shadow window, measure PF, Sharpe, drawdown. Compare adapter-on vs base-only.

**Mode B (live trading):** parallel-prediction-only execution. Candidate adapter produces shadow predictions on real-time data, but **does NOT route trades** — predictions are logged and compared against realized outcomes. After N_shadow = 64 closed trades' worth of shadow signal, compare shadow-predicted PnL vs realized PnL.

**Promotion criteria** (both modes):
- Shadow PF ≥ 0.95 × baseline PF (no significant degradation)
- Shadow Sharpe ≥ baseline Sharpe − 0.3 (allow small noise)
- No new tail-loss events > 2× baseline tail loss

**Demotion**: failed shadow eval → automatic revert to last-promoted adapter checkpoint. No human intervention.

### 6.2.2 Circuit-breaker

Independent of shadow eval. Continuously monitors rolling Sharpe over last 32 trades. If rolling_sharpe degrades by > 25% vs the rolling_sharpe at time-of-last-promotion, revert adapter regardless of shadow eval. Circuit-breaker is the safety net for real-time degradation that shadow eval missed.

### 6.3 References

- [feedback_safla_neural](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/) — SAFLA pattern for memory-persistent learning
- `sona_learning_optimizer` agent — SONA LoRA tooling already available
- EWC++ for catastrophic forgetting in continual learning

### 6.4 Layer D acceptance criteria (Gate 4)

- Walk-forward across ≥ 3 sessions shows positive PF lift in ≥ 75% of folds.
- Shadow eval correctly catches degradation in a synthetic-bad-update test.
- Circuit-breaker revert works end-to-end.
- No catastrophic forgetting: base+adapter performs ≥ base on prior windows.

---

## 7. State Representation: `open_trade_state` expansion

Current: 24 bytes per backtest (entry_ts:8, entry_px_x100:4, entry_size:4, realized_at_open:4, horizon_idx:1, pad:3).

Target: **64 bytes per backtest** (single source of truth per trade per [feedback_single_source_of_truth_no_duplicates](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_single_source_of_truth_no_duplicates.md)). State only — pure observability fields (scale-up counts, etc.) go to a separate per-trade audit buffer, not the per-event state slot.

Proposed layout:

```
Offset  Size  Field                                          Purpose
────────────────────────────────────────────────────────────────────────────────
  0      8   entry_ts_ns                                  (u64)   timing
  8      4   entry_px_x100                                (i32)   close attribution
 12      4   entry_size                                   (i32)   close attribution
 16      4   realized_at_open                             (f32)   segment-PnL delta
 20      4   conviction_at_entry                          (f32)   degradation_term denominator
 24      4   conviction_ema                               (f32)   §4.2 sizing input
 28     16   conviction_per_horizon_ema [4 × f32]         (h100, h500, h1500, h6000)
                                                                   §4.3 disagreement_term input
 44      4   pnl_adjusted_conviction_ema                  (f32)   §4.3 pnl_decay_term input
 48      4   peak_unrealized_pnl                          (f32)   trail invariant
 52      4   degradation_consecutive_events               (u32)   §4.3 attribution
 56      4   disagreement_consecutive_events              (u32)   §4.3 attribution
 60      1   horizon_idx_dominant_at_entry                (u8)    close attribution
 61      3   pad                                                  align
────────────────────────────────────────────────────────────────────────────────
        64   TOTAL — fits in one cache line
```

**Diagnostic-only fields go elsewhere.** `scale_up_events_count`, `scale_down_events_count`, `max/min_target_lots_reached` — useful for post-trade analysis but not needed for next-decision logic. Per [feedback_no_hiding](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_hiding.md), if these are wanted, route to a separate `trade_audit_log` ring buffer in observability infrastructure. Mixing state and observability bloats the hot-path read/write.

### 7.1 Migration (greenfields atomic)

Every reader and writer of `open_trade_state` migrates atomically per [feedback_no_partial_refactor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_partial_refactor.md). Sites to update in one commit:

- `crates/ml-backtesting/cuda/pnl_track.cu` — `OPEN_TRADE_STATE_BYTES` constant from 24 to 64; all `st + offset` reads/writes updated; reset-on-close zeros all 64 bytes.
- `crates/ml-backtesting/cuda/decision_policy.cu` — `stop_check_isv` reads entry_ts via `+ 24` indexing; update for new layout.
- `crates/ml-backtesting/cuda/resting_orders.cu` — S2.2 max_hold event-rate check also reads via `+ 24`; update.
- `crates/ml-backtesting/src/sim/mod.rs` — allocation site, accessors.
- Tests: any test asserting the layout updates in the same commit.

No backwards-compat shim for the old 24-byte layout. Greenfields.

---

## 8. Boundary: hardcoded vs adaptive

| Field | Status | Source / Rationale |
|---|---|---|
| `latency_ns` | **HARDCODED** (config) | Physical wire time. Real constraint. |
| `cost_per_lot_per_side` | **HARDCODED** (config) | Exchange-determined fee. |
| `annualisation_factor` | **HARDCODED** | Definitional constant. |
| `min/max_reasonable_px` | **HARDCODED** (config, per-instrument) | Instrument bounds. |
| `max_hold_ns` | **CIRCUIT-BREAKER** (default 8h) | Safety net only. Behavior emerges from §4.3. |
| `decision_stride` | **REMOVED** | Greenfields — field deleted from YAML schema and Rust structs in this rebuild. No deprecation shim. |
| `max_lots` | **ADAPTIVE** (Layer C §5.1) | Envelope from rolling Sharpe × DD × regime vol. |
| `threshold` | **ADAPTIVE** (Layer C §5.2) | Conviction-percentile gate. |
| `target_annual_vol_units` | **ADAPTIVE** (Layer C §5.3) | Regime-vol-inverse. |
| `kelly_frac_floor` | **HARDCODED** (config) | Bootstrap sentinel. [pearl_first_observation_bootstrap](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_first_observation_bootstrap.md). |
| `sharpe_weight_floor` | **HARDCODED** (config) | Bootstrap sentinel. Same rationale. |

---

## 9. Phased Implementation Gates

### Gate 1 (Layer A): Continuous controller

**Pass criteria:**
- Continuous controller compiles and runs in cluster smoke.
- Smoke wall-time ≤ 5× current.
- n_trades ≥ Layer-A-off baseline.
- Sharpe/PnL within ±15% of baseline.
- No new NaN/sentinel counter fires.

**Validation:** single-cell smoke `config/ml/sweep_smoke.yaml` with `decision_stride=200` (ignored) vs new continuous control. Side-by-side wall-time + trade metrics.

### Gate 2 (Layer B): Signal-driven position management

Tiered criteria — first MUST is required to advance; WIN is the architectural payoff.

**MUST (required to advance from Gate 2):**
- Hold-time mean NOT cap-binding (8h circuit-breaker fires in < 5% of closes).
- No regression on baseline metrics: Sharpe ≥ baseline − 0.5 units; PF ≥ baseline × 0.95; drawdown ≤ baseline × 1.10.
- Conviction-degradation exit accounts for ≥ 60% of closes — validates the mechanism is dominant.
- Trade attribution coherent: `exit_cause` field shows interpretable distribution across the three terms (not 99% one term).
- No new NaN/sentinel counters fire.

**WIN (the architectural payoff — one of):**
- Sharpe lift ≥ +0.5 units vs Layer A baseline.
- Profit factor lift ≥ 30% (e.g., 0.40 → 0.52, not 0.40 → 1.0).
- Alpha realization ratio (see §9.1.1) lift ≥ 20%.
- Drawdown reduction ≥ 20% at equal PnL.

**STOP** at gate close-out if NO WIN condition was hit despite MUST being green: re-evaluate whether to proceed to Layer C, or whether the signal itself needs improvement before any policy layer can extract value (the latter is a separate workstream).

**Validation:** parametric sweep over (degradation_factor, disagreement_factor, pnl_decay_factor, conviction_EMA_α_floor) on the smoke window. Pick the winner by composite of MUST + WIN criteria. Then walk-forward on 3 folds.

### 9.1.1 Alpha-realization ratio (defined)

Per-trade ratio of realized PnL to theoretical maximum PnL achievable within the trade's signal-validity window:

```
For each closed trade:
    signal_window_start = entry_ts
    signal_window_end   = max(exit_ts, entry_ts + h6000_horizon_ns)
    optimal_exit_ts     = argmax_t∈[start,end] (unrealized_pnl_at_t)
    optimal_pnl         = unrealized_pnl_at(optimal_exit_ts)
    realized_pnl        = trade.realized_pnl_usd

    alpha_realization_per_trade = realized_pnl / max(optimal_pnl, cost_per_lot_per_side × size)

alpha_realization_ratio = mean(alpha_realization_per_trade across all trades)
```

Bounded by trade's signal-validity window (h6000 horizon ns). Captures "how much of the available alpha the policy actually captured." Cost-bootstrapped denominator (using `cost × size` as floor) per [pearl_trade_level_vol_for_stop_distance](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_trade_level_vol_for_stop_distance.md) handles trades where `optimal_pnl` is sub-cost noise.

Computed offline from trade records + per-event unrealized PnL reconstruction (the latter requires logging unrealized PnL at decision events — minor instrumentation add). Single, unambiguous metric.

### Gate 3 (Layer C): Adaptive risk envelope

**Pass criteria:**
- Drawdown reduction at equal PnL OR PnL lift at equal drawdown (envelope-on vs envelope-off).
- Envelope stays bounded by floor and hard-cap during smoke.
- No envelope thrash (envelope EMA smoothing works).
- Self-tuning percentile drifts to a stable region.

**Validation:** side-by-side smoke. Three configs: Layer A only, Layer A+B, Layer A+B+C. Compare drawdown/PnL/turnover.

### Gate 4 (Layer D): Online weight adaptation

**Pass criteria:**
- Walk-forward across ≥ 3 sessions, ≥ 75% positive PF lift.
- Shadow eval catches synthetic-bad-update.
- Circuit-breaker revert works.
- Base+adapter ≥ base on held-out windows (no catastrophic forgetting).

**Validation:** walk-forward fold structure from training pipeline. Per-fold adapter checkpoint. Compare PF/Sharpe/DD adapter-on vs adapter-off.

---

## 10. Risks and Mitigations

### 10.1 Layer A — controller cost blowup

If event-rate controller is slow, sim throughput tanks. 200× more invocations.

**Mitigations:**
- Profile early. Cap controller cost to ≤ 10× current per-event cost.
- ISV math is light; the heavy cost is the trunk forward, which already runs every event.
- If too slow: introduce adaptive sub-stride that runs controller every event in active regimes, every K events in quiet regimes.

### 10.2 Layer B — conviction-degradation deadlock

If conviction never degrades (the signal keeps lying to itself), trade never closes. Real losers might hold high conviction.

**Mitigations:**
- pnl-adjusted conviction = conviction × sign(unrealized_pnl). When pnl negative, conviction is negated. Deep negative pnl → deeply negative pnl-adjusted conviction → trip degradation exit.
- Absolute circuit-breaker (max_hold ≈ 8h) catches pathological cases.
- Multi-horizon disagreement is independent of degradation: if h100 reverses while h6000 holds, that's an exit signal even without conviction collapse.

### 10.3 Layer C — envelope feedback loop on PnL

Drawdown shrinks envelope → smaller positions → less ability to recover → deeper drawdown.

**Mitigations:**
- Envelope adapts on slower timescale than trade timescale (EMA α floor).
- Envelope_floor is never zero (hard floor in lots).
- Drawdown weighting is logarithmic (not linear) — small DD doesn't crush envelope.

### 10.4 Layer D — online learning instability

Famous failure mode.

**Mitigations:**
- Offline-batch (not in-loop) — bounded update frequency.
- EWC++ regularizer toward base — prevents catastrophic forgetting.
- Shadow eval gates every adapter update — catches bad updates before they go live.
- Circuit-breaker revert — automatic rollback on rolling Sharpe degradation.
- Adapter is small (1-5% of base) — limited expressivity to misbehave.

### 10.5 Layer D — adapter regime over-fit

EWC++ guards against catastrophic forgetting of base capabilities but does NOT prevent the adapter from over-fitting to the **most recent regime** if recent sessions all live in one regime (e.g., a multi-week trending market followed by chop).

**Mitigations:**
- **Sample-count down-weighting:** the gradient step magnitude is scaled by `sqrt(samples_in_current_regime / samples_in_base_training_data)`. Regimes with little base coverage get small updates; regimes with rich base coverage get full updates. This is the standard distribution-shift compensation.
- **Regime detection** uses ISV-derived regime-vol classification (also used in §5.1 envelope and §5.3 vol target). When current regime ≠ base-training regime distribution, the adapter learns proportionally less.
- **Shadow eval is cross-regime** when possible: hold out a window from a DIFFERENT regime than the training window, so an adapter that over-fits to the training regime fails the cross-regime shadow.
- **Periodic adapter reset** as a hard backstop: if rolling Sharpe degrades AND shadow eval consistently fails for K consecutive cycles, drop the adapter entirely and re-bootstrap from base.

---

## 11. Open questions / future work

These are recognized scope-cuts. Document for future iteration.

1. **Discretionary trader features not in scope:** spread-clearing intent, queue-position gaming, hidden order detection, news-event awareness. The architecture supports them as future additions but does not implement them here.
2. **Multi-instrument fusion:** spec covers single-instrument (ES). Cross-instrument signal fusion (ES + NQ + cross-correlations) is a separate spec.
3. **Layer D adapter scope:** which weight matrices get LoRA adapters? Likely trunk-output + controller-input. Decided at implementation time based on profiling.
4. **Conviction-percentile bootstrap:** §5.2 has self-tuning percentile target. Bootstrap behavior in first N trades (before history builds) follows first-observation pattern, but the exact init value is implementation-time decision.

---

## 12. Acceptance criteria summary (rollup)

```
GATE 1 (Layer A):  Continuous controller live, wall-time ≤ 2× baseline, signal-to-entry
                   lag reduced ≥ 30%, no PnL regression > 15%.

GATE 2 (Layer B):  MUST  — hold time emergent (cap fires < 5%), no metric regression,
                          60% of closes via conviction-degradation, exit attribution coherent.
                   WIN   — one of: Sharpe lift ≥ +0.5, PF lift ≥ 30%, alpha-realization
                          ratio ≥ +20%, drawdown reduction ≥ 20% at equal PnL.
                   STOP  — MUST green but no WIN means re-evaluate signal-vs-policy before C.

GATE 3 (Layer C):  Drawdown reduction at equal PnL OR PnL lift at equal drawdown.
                   Envelope stays bounded by floor and hard-cap. Percentile self-tuning
                   stable (no thrash). Floor activates when signal-derived envelope underflows.

GATE 4 (Layer D):  Walk-forward across ≥ 3 folds, ≥ 75% positive PF lift.
                   Shadow eval catches synthetic-bad-update. Circuit-breaker revert works.
                   No catastrophic forgetting (base+adapter ≥ base on prior windows).
                   No regime over-fit (cross-regime shadow eval passes).
```

Each gate is a stop. If a gate fails, do not advance until the gate is reopened with revised criteria or revised implementation. Gate 2 STOP condition (MUST without WIN) is the most consequential: it forces an explicit decision between "iterate Layer B" and "this signal can't carry a controller at this cost/latency anchor."

---

## 13. Pearl conformance summary

Per-section pearl application (not just citation):

- §3 Layer A: [pearl_stop_checks_run_at_deadline_cadence](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_stop_checks_run_at_deadline_cadence.md) — checks at event rate.
- §4.2 sizing: [pearl_wiener_optimal_adaptive_alpha](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_optimal_adaptive_alpha.md) + [pearl_wiener_alpha_floor_for_nonstationary](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_alpha_floor_for_nonstationary.md) — adaptive α with floor.
- §4.3 exit composite: [pearl_controller_anchors_isv_driven](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_controller_anchors_isv_driven.md) — degradation/disagreement/pnl_decay factors are ISV-derived.
- §4.4 conviction: [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md) — ε floor on net_edge (no cold-start branch); [pearl_audit_unboundedness_for_implicit_asymmetry](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_audit_unboundedness_for_implicit_asymmetry.md) — net edge is one-sided positive; [pearl_one_unbounded_signal_per_reward](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_one_unbounded_signal_per_reward.md) — `net_edge / (var + cost²)` is the one unbounded factor; [pearl_zscore_normalization_for_magnitude_asymmetric_signals](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_zscore_normalization_for_magnitude_asymmetric_signals.md) — `/ total_weight` scale-normalizes; [pearl_trade_level_vol_for_stop_distance](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_trade_level_vol_for_stop_distance.md) — `cost²` bootstraps variance.
- §5.1 envelope: [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md) — additive bounded contributions, floor at 1 lot; [feedback_isv_for_adaptive_bounds](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_isv_for_adaptive_bounds.md) — sharpe_norm / vol_norm / dd_cap are ISV anchors.
- §5.2 threshold: explicit 3-arm bandit replaces hand-wave; bounded `[0.5, 0.95]` per [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md).
- §6.2 Layer D: single primary loss (value regression) + EWC++; shadow eval with explicit window/metric.
- §7 state: [feedback_single_source_of_truth_no_duplicates](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_single_source_of_truth_no_duplicates.md) — open_trade_state is sole per-trade scratch; [feedback_no_partial_refactor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_partial_refactor.md) — all consumers migrate atomically; [feedback_no_legacy_aliases](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_legacy_aliases.md) — no compat shim for the old 24-byte layout (greenfields).
- §8 boundary: [feedback_adaptive_not_tuned](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_adaptive_not_tuned.md) — every non-hardware constant becomes signal-derived; [feedback_no_legacy_aliases](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_legacy_aliases.md) — `decision_stride` removed, not deprecated.
- §9 gates: tiered MUST/WIN avoids the "(0, 30s)" anti-pattern from the just-closed S2 (set gates we can actually hit, gates that distinguish "broken" from "needs more work").
- §11.4 percentile bootstrap: [pearl_first_observation_bootstrap](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_first_observation_bootstrap.md) — init from first observed conviction, no special branch.
- Overall sequencing: [pearl_no_deferrals_for_complementary_fixes](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_no_deferrals_for_complementary_fixes.md) acknowledged — layers are NOT complementary fixes (which we'd combine) but sequenced amplifications (which we'd phase). The rationale is explicit in §2.

---

**End of design. Awaiting user review before implementation plan.**
