AEM stays shadow-only this sprint (V7 drives live exits); changes affect
what AEM *would have done*, logged to CH for future V7→AEM demotion analysis.
adaptive_exit_engine.py:
- Replace single MAE_MULT_TIER1=3.5 with MAE_MULT_BY_BUCKET dict
(B3→None disables MAE stop, B4→2.0 strict, B6→6.0 wide band)
- evaluate() return dict extended: mae_mult_applied, mae_threshold,
atr, p_threshold, giveback_k (all params that drove the decision)
- adaptive_exit_shadow schema extended (new Nullable columns added via
ALTER TABLE IF NOT EXISTS — backward-compat with pre-sprint rows)
- log_shadow() signature extended: v7_action, v7_exit_reason,
naive_would_have (TP/STOP/MAX_HOLD counterfactual at same instant)
dolphin_actor.py:
- AEM shadow call now passes V7 head-to-head decision and naive
counterfactual so future retrospective requires no offline replay
- EsoF listener registered on DOLPHIN_FEATURES map (esof_advisor_latest
key); label fed into engine._current_esof_label before each step_bar
- S6/bucket loaders (_load_s6_size_table, _load_asset_bucket_data) and
constructor wiring for the new GREEN engine kwargs
Plan refs: Tasks 5, 7, 10 — V7 path untouched, AEM return value is
never gated, CH shadow is best-effort (daemon thread).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All sizing multipliers now applied at one location (esf_alpha_orchestrator.py
line 565 region) so there are no hidden side-effects and BLUE parity is
trivially preserved by leaving the new kwargs at None/False.
- S6 per-bucket notional multiplier via s6_size_table kwarg
- EsoF regime gate at _try_entry top: reads _current_esof_label, skips
entry if esof_sizing_table maps to 0.0 (UNFAV/MILD_NEG regime)
- Integer-leverage gate: use_int_leverage=True → leverage_int=1 (FIXED,
pending prod/scripts/analyze_leverage_winrate.py analysis); float
leverage_raw preserved in NDPosition + return dict for CH logging
- notional <= 0 → return None guard (prevents 0-notional positions)
- NDPosition extended: asset_bucket_id, s6_mult, esof_mult, esof_label,
leverage_raw fields (BLUE leaves these at defaults)
Plan ref: Task 2 — sizer stays pure (its returned notional is discarded
by line 565 anyway; adding mults inside would be dead code with hidden
side-effects).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add `asset_bucket_ban_set` and `asset_bucket_assignments` kwargs to
AlphaAssetSelector.__init__ (both default None → BLUE unchanged).
When active, assets whose KMeans bucket is in the ban set are skipped
inside rank_assets() so the next-ranked asset takes the slot — capital
is preserved rather than wasted by a 0× sizer multiplier.
Plan ref: Task 9 — ban ≠ 0× is the critical caveat from the sprint plan.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>