spec: continuous-reasoning trader architecture (4-layer rebuild)

Integrated spec for the continuous-reasoning trader rework. Replaces
the rule-based discrete policy with a 4-layer signal-driven architecture:

  Layer A: continuous control loop (every event, not every stride)
  Layer B: signal-driven position management (multi-horizon fusion,
           continuous sizing, conviction-degradation exit, open_trade_state
           expanded 24→128 bytes)
  Layer C: adaptive risk envelope (ISV-derived max_lots, threshold,
           target_annual_vol)
  Layer D: online weight adaptation (LoRA + EWC++, offline-batch
           per-session, shadow-eval gated)

Phased gates: A unlocks B; B is where alpha lives; C amplifies B;
D amplifies whatever's working. Each gate has explicit pass criteria.

Conviction definition: ISV-weighted multi-horizon agreement.
weight_h = max(pnl_ema_win - pnl_ema_loss, 0) / (var + cost^2).
Net edge x SNR per horizon, scale-normalised, cold-start uniform fallback.

Pearl conformance: 13 existing pearls referenced and respected
(ISV-driven anchors, first-observation bootstrap, Wiener-alpha floor,
blend-with-floor, z-score normalisation, one-unbounded multiplicand,
trade-level vol bootstrap, deadline cadence, single-source-of-truth,
atomic refactor, adaptive-not-tuned).

Hardcoded boundary preserved for hardware/exchange realities (latency,
cost, annualisation, instrument bounds). Everything else adaptive.

Status: design — awaiting user review before implementation plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-05-20 17:11:46 +02:00
parent 8828e8ab13
commit 0f34843253

View File

@@ -0,0 +1,492 @@
# Continuous-Reasoning Trader — Architecture Design
**Date:** 2026-05-20
**Branch context:** `ml-alpha-phase-a` (S2 closed at `8828e8ab1`)
**Status:** Design — pending user review before implementation plan
**Supersedes scope of:** `2026-05-19-isv-driven-stop-controller-design.md` (that spec ships clean stops; this spec replaces the rule-based policy that consumes those stops)
---
## 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
Four layers, sequenced. Each independently validatable. Each addresses a specific failure mode visible in the S2 baseline.
```
┌────────────────────────────────────────────────────────────────────┐
│ LAYER A: Continuous Control Loop │
│ Controller runs every event. decision_stride deprecated. │
│ Failure addressed: stale-state decisions. │
├────────────────────────────────────────────────────────────────────┤
│ LAYER B: Signal-Driven Position Management │
│ Multi-horizon action fusion (h100/h500/h1500/h6000). │
│ Continuous sizing: target_lots = conviction × envelope. │
│ Conviction-degradation exit: replaces max_hold. │
│ open_trade_state expanded 24 → 128 bytes for trajectory. │
│ Failure addressed: cap-binding holds, no mid-trade adjustment. │
├────────────────────────────────────────────────────────────────────┤
│ LAYER C: Adaptive Risk Envelope │
│ max_lots: ISV-derived from rolling Sharpe × drawdown × regime-vol.│
│ threshold: conviction-percentile gate. │
│ target_annual_vol: regime-vol-inverse. │
│ Failure addressed: fixed risk in regime-varying market. │
├────────────────────────────────────────────────────────────────────┤
│ LAYER D: Online Weight Adaptation │
│ LoRA adapter on frozen base. Trajectory-batch offline updates. │
│ EWC++ regularizer. Shadow eval + circuit-breaker revert. │
│ Failure addressed: static model in non-stationary market. │
└────────────────────────────────────────────────────────────────────┘
```
Sequencing rationale: A unlocks event-rate consumption needed by B. B is where alpha lives. C amplifies B by shaping when and how much B trades. D amplifies whatever's working in B+C. Each gate's lift is conditional on the prior gate being green — [no-deferrals-for-complementary-fixes](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_deferrals_for_complementary_fixes.md) doesn't apply because the layers are amplifications of one another, not independent fixes for orthogonal mechanisms.
---
## 3. Layer A — Continuous Control Loop
### 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
- `crates/ml-backtesting/src/harness.rs`: remove `if event_idx % decision_stride == 0` gate around `step_decision`.
- `crates/ml-backtesting/src/sim/mod.rs`: `step_decision` invoked every event from harness; internal kernel batching unchanged.
- `bin/fxt-backtest/src/main.rs` + `config/ml/*.yaml`: keep `decision_stride` field for backwards-compat but ignore it (deprecation log line on first use).
### 3.4 Cost target
Per-event controller cost ≤ 10× current per-event cost. Sim throughput target ≥ 5000 events/sec (current ~21000 events/sec at stride=200 with controller every 200th event; expect ~5000-10000 events/sec at continuous controller).
### 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 completes with continuous controller in ≤ 30s wall-time vs ~95s current (or accept ≤ 5× slowdown).
- No regression in n_trades vs Layer-A-off (continuous control should produce ≥ as many trades).
- Trade-level entry timestamps cluster tighter to signal-spike timestamps than the stride=200 baseline (measurable via signal-vs-entry-ts cross-correlation).
- `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).
---
## 4. Layer B — Signal-Driven Position Management
This is the heart of the rebuild. Three sub-components.
### 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:
```
conviction = isv_weighted_agreement(alpha_probs, isv_state, mask) // see 4.4
conviction_ema = EMA(conviction, α=isv_derived_alpha) // smoothing
target_lots = round(conviction_ema × envelope_max)
```
EMA prevents micro-oscillation churn. α derived per [pearl_wiener_optimal_adaptive_alpha](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_optimal_adaptive_alpha.md) with floor per [pearl_wiener_alpha_floor_for_nonstationary](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_alpha_floor_for_nonstationary.md).
### 4.3 Conviction-degradation exit
Replaces `max_hold_ns` as the dominant exit mechanism. `max_hold_ns` becomes a high-default circuit-breaker (e.g., 8 hours) for safety only.
**Per-trade conviction trajectory** stored in expanded `open_trade_state` (see §6). Tracks:
- conviction at entry
- conviction EMA over the trade lifetime
- multi-horizon agreement at each decision
- pnl-adjusted conviction = conviction × sign(unrealized_pnl)
**Exit fires when ANY of:**
1. **Net conviction degradation:** `conviction_ema_now < conviction_at_entry × degradation_factor` for K consecutive events. `degradation_factor` and K are ISV-derived (not hardcoded).
2. **Multi-horizon disagreement:** h100 or h500 reversed sign relative to h6000 for ≥ K_short events.
3. **pnl-adjusted conviction crosses zero:** signal says exit AND market agrees.
4. **Hard stop:** SL/trail still active per current `stop_check_isv` logic (Layer B doesn't remove them — they're orthogonal safety).
5. **Circuit-breaker:** `(current_ts entry_ts) ≥ max_hold_circuit_breaker_ns` where the circuit-breaker default is 8 hours, ~480× the current 60s cap.
This makes time-in-trade emergent. Winners hold as long as conviction holds. Losers cut when conviction degrades, not when a clock fires.
### 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). Floor at 0 prevents negative-edge horizons
# from dragging conviction; 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, 0)
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. If all horizons have zero net edge
# (cold start), fall back to uniform-weighted magnitude per
# pearl_first_observation_bootstrap.
total_weight = sum(|weight_h|)
if total_weight > 0:
conviction_signed = sum(weighted_mag_h) / total_weight // ∈ [-1, +1]
else:
conviction_signed = sum(magnitude_h × direction_h) / 4 // cold-start uniform
conviction = |conviction_signed| // ∈ [0, 1]
direction = sign(conviction_signed)
```
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 follows the asymmetric structural bound — losses are NOT symmetrically subtracted from gains since negative-edge horizons should drop out, not flip sign ([pearl_audit_unboundedness_for_implicit_asymmetry](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_audit_unboundedness_for_implicit_asymmetry.md)).
- `cost^2` bootstrap follows [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.
- Cold-start fallback (uniform weighting before ISV bootstraps) ensures the controller is functional from event 1; matches [pearl_first_observation_bootstrap](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_first_observation_bootstrap.md).
### 4.5 Layer B acceptance criteria (Gate 2)
- Hold-time distribution is no longer cap-binding: mean and max NOT clustered at the circuit-breaker.
- `profit_factor > 1.0` OR a defined alpha-realization metric (e.g., realized-vs-theoretical PnL on tagged signal opportunities) shows ≥ 30% lift over Layer A baseline.
- Win rate stable or up (24.2% baseline; target ≥ 25% with PF lift).
- Drawdown not worsened by > 10% vs Layer A baseline.
- Conviction degradation exits account for ≥ 60% of closes (rest = SL/trail/circuit-breaker). Validates the mechanism is dominant.
---
## 5. Layer C — Adaptive Risk Envelope
ISV-derived bounds replace YAML constants.
### 5.1 `max_lots` envelope
Replaces hardcoded `max_lots: 5`.
```
rolling_sharpe_short = isv_aggregated rolling sharpe over last N closed trades
drawdown_pct = current_dd / peak_equity
regime_vol_inverse = 1 / (regime_atr_ema + cost)
envelope_raw = base_lots × rolling_sharpe_short_clamped × (1 drawdown_pct/dd_cap) × regime_vol_inverse
envelope_max = clamp(envelope_raw, envelope_floor, envelope_hard_cap)
```
Floor and hard cap follow [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)`, not blend. Hard cap stays as safety (default 50 lots, configurable).
### 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 convictions (per backtest, on-device)
percentile_target = ISV-derived (start at 0.75, learn toward maximizing realized PF)
threshold = percentile(conviction_history, percentile_target)
```
Self-tuning: percentile_target drifts toward whatever value maximizes recent realized PF, bounded by a hard floor (e.g., 0.5 — never trade below median conviction).
### 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 trades):
1. Collect trajectories: (state_at_decision, action, outcome_realized_pnl) for all closed trades.
2. Compute outcome-conditional loss:
- Value regression (predicted_value vs realized)
- Direction loss (predicted_direction vs realized_pnl_sign)
- Conviction calibration (conviction vs |realized_pnl|)
3. EWC++ regularizer toward base checkpoint (prevents catastrophic forgetting).
4. Gradient step on LoRA adapter only (base frozen).
5. Shadow eval: run adapter-on vs adapter-off on a held-out validation window.
6. If shadow eval shows PF degradation > X% vs baseline: revert adapter to checkpoint.
```
### 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: **128 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)).
Proposed layout:
```
Offset Size Field
─────────────────────────────────────────────────────────────────────
0 8 entry_ts_ns (u64)
8 4 entry_px_x100 (i32)
12 4 entry_size (i32)
16 4 realized_at_open (f32)
20 1 horizon_idx_dominant_at_entry (u8)
21 3 pad
24 4 conviction_at_entry (f32)
28 4 conviction_ema (f32)
32 16 conviction_per_horizon_at_entry [4 × f32] (h100, h500, h1500, h6000)
48 16 conviction_per_horizon_ema [4 × f32]
64 4 pnl_adjusted_conviction_ema (f32)
68 4 degradation_consecutive_events (u32)
72 4 short_disagreement_consecutive_events (u32)
76 4 peak_unrealized_pnl (f32)
80 4 trough_unrealized_pnl (f32)
84 4 scale_up_events_count (u32)
88 4 scale_down_events_count (u32)
92 4 max_target_lots_reached (i32)
96 4 min_target_lots_reached (i32)
100 28 reserved for future trajectory features
```
128 bytes total. Aligns to cache line.
### 7.1 Migration
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:
- `crates/ml-backtesting/cuda/pnl_track.cu``OPEN_TRADE_STATE_BYTES` constant from 24 to 128; all `st + offset` reads/writes updated.
- `crates/ml-backtesting/cuda/decision_policy.cu``stop_check_isv` reads entry_ts via `+ 24` indexing; update.
- `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, `read_open_trade_state` helper.
- Any tests asserting layout.
---
## 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` | **DEPRECATED** | Layer A makes obsolete. Field kept for backwards-compat. |
| `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
**Pass criteria:**
- Hold-time mean NOT cap-binding (max_hold circuit-breaker rarely fires; < 5% of closes).
- `profit_factor > 1.0` OR ≥ 30% lift on a defined alpha-realization metric vs Layer A baseline.
- Win rate ≥ 24.2% (current).
- Drawdown not worsened > 10%.
- Conviction degradation accounts for ≥ 60% of closes.
- Per-trade trajectory data shows conviction tracks predicted alpha (sanity check on the formula).
**Validation:** parametric sweep over (degradation_factor, disagreement_K, conviction_EMA_α) on the smoke window. Pick the winner. Then walk-forward on 3 folds.
### 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.
---
## 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, no PnL regression, ≤ 5× wall-time
GATE 2 (Layer B): PF > 1.0 OR +30% alpha-realization, hold-time emergent, 60% degradation exits
GATE 3 (Layer C): DD reduction at equal PnL OR PnL lift at equal DD
GATE 4 (Layer D): Walk-forward 75% positive lift, no catastrophic forgetting
```
Each gate is a stop. If a gate fails, do not advance until the gate is reopened with revised criteria or revised implementation.
---
## 13. Pearl conformance summary
- [pearl_controller_anchors_isv_driven](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_controller_anchors_isv_driven.md) — Layer C envelopes are ISV-derived.
- [pearl_first_observation_bootstrap](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_first_observation_bootstrap.md) — conviction trajectory bootstrap uses sentinel=0, first observation replaces directly.
- [pearl_wiener_optimal_adaptive_alpha](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_optimal_adaptive_alpha.md) — conviction EMA α adaptive.
- [pearl_wiener_alpha_floor_for_nonstationary](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_alpha_floor_for_nonstationary.md) — α floor in conviction tracking (non-stationary by definition).
- [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 floor uses `max(real, floor)`.
- [pearl_zscore_normalization_for_magnitude_asymmetric_signals](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_zscore_normalization_for_magnitude_asymmetric_signals.md) — multi-horizon weights are scale-normalized.
- [pearl_one_unbounded_signal_per_reward](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_one_unbounded_signal_per_reward.md) — conviction formula has exactly one unbounded multiplicand.
- [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^2` bootstrap for ISV weight denominator.
- [pearl_stop_checks_run_at_deadline_cadence](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_stop_checks_run_at_deadline_cadence.md) — Layer A makes ALL checks event-rate.
- [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 single source.
- [feedback_no_partial_refactor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_partial_refactor.md) — open_trade_state migration is atomic.
- [feedback_isv_for_adaptive_bounds](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_isv_for_adaptive_bounds.md) — adaptive bounds live in ISV, not constants.
- [feedback_adaptive_not_tuned](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_adaptive_not_tuned.md) — signal-driven, not tuned.
---
**End of design. Awaiting user review before implementation plan.**