Commit Graph

7 Commits

Author SHA1 Message Date
hjnormey
aac4484c0f test(green): add green-only feature tests
New test_green_only_features.py covers the sprint additions; the existing
test_green_blue_parity.py (104 tests) is left untouched as the authoritative
doctrinal baseline.

Tests:
- Toggles-OFF identity: with all GREEN kwargs at BLUE defaults (None/False)
  NDAlphaEngine + NDPosition produce the pre-sprint forms
- S6 selector ban: banned bucket assets skipped; slot rerouted (not wasted)
- AEM MAE table: B3→None disables stop, B4→2.0 strict, B6→6.0 wide,
  unknown bucket falls back to MAE_MULT_TIER1
- EsoF rename: advisor emits UNKNOWN not NEUTRAL; both label keys present
  in gate tables with identical values (historical replay safe)
- Int-leverage gate: source still has leverage_int=1 with both option
  comments present (guards against premature flip before winrate analysis)

Plan ref: Task 11.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 06:08:20 +02:00
hjnormey
36d263eb91 feat(config/scripts): GREEN config wiring + S6 recompute tooling
prod/configs/green.yml:
- asset_bucket_ban_set: [4] (B4 banned at selector level)
- s6_size_table: inline bootstrap multipliers (B0→0.4, B1→0.3, B3→2.0,
  B5→0.5, B6→1.5) matching CRITICAL_ASSET_PICKING S6 scenario
- esof_sizing_table: FAV→1.2, MILD_POS→0.6, UNKNOWN→0.25,
  MILD_NEG→0.0, UNFAV→0.0
- use_int_leverage: true (1x fixed pending winrate analysis)
- s6_table_path: pointer to generated YAML (recompute updates this)
BLUE (blue.yml) carries none of these keys → BLUE math unchanged.

prod/configs/green_s6_table.yml: bootstrap stub with frontmatter
(generated_at, source_branch, n_trades). Regenerated by recompute script.

prod/scripts/recompute_s6_coefficients.py:
  Queries trade_events, maps assets to KMeans buckets, derives per-bucket
  sizing mults. Variance guard: >20% net-PnL move flags bucket in
  dolphin.s6_recompute_log for manual review before promote.

prod/s6_recompute_flow.py:
  Prefect flow wrapping the recompute script. Cadence via
  S6_RECOMPUTE_INTERVAL_DAYS env (default 30). Kill-switch:
  S6_RECOMPUTE_DISABLED=1.

prod/scripts/analyze_leverage_winrate.py:
  Read-only walk of CH trade_events; bins trades by leverage_raw,
  emits per-bin WR/net-PnL/avg-MAE. Output informs the int-leverage
  rounding rule choice (Option 1 round-half-up vs Option 2 banker's round
  vs stay-at-1x). Does not auto-apply a rule change.

Plan refs: Tasks 3, 8, 10.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 06:08:08 +02:00
hjnormey
48dcf3fe13 feat(aem): per-bucket MAE_MULT table + shadow logging completeness
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>
2026-04-22 06:07:46 +02:00
hjnormey
af5156f52d feat(esof): rename NEUTRAL→UNKNOWN + backward-compat alias
The mid-band advisory label (constituent signals in conflict) was called
NEUTRAL, implying "benign middle" — but retrospective data (637 trades)
shows it is empirically the worst-ROI regime. Renaming to UNKNOWN makes
the semantics explicit for regime-gate consumers.

- esof_advisor.py: emits UNKNOWN; LABEL_COLOR keeps NEUTRAL alias for
  historical CH rows / stale HZ snapshots
- esof_gate.py: S6_MULT, IRP_PARAMS, Strategy A mult_map all keyed on
  UNKNOWN with NEUTRAL alias (values identical → replays unaffected)
- prod/docs/ESOF_LABEL_MIGRATION.md: migration note, CH/HZ impact,
  rollback procedure

Plan ref: Task 4 — NEUTRAL→UNKNOWN is load-bearing for the EsoF gate
in the orchestrator (0.25× sizing vs 1.0× under old label semantics).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 06:07:30 +02:00
hjnormey
0da46d8635 feat(s6/esof): orchestrator single-site S6+EsoF+int-leverage gate
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>
2026-04-22 06:07:20 +02:00
hjnormey
ce7f3ce8ff feat(s6): AlphaAssetSelector bucket-ban kwargs (BLUE no-op)
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>
2026-04-22 06:07:07 +02:00
hjnormey
01c19662cb initial: import DOLPHIN baseline 2026-04-21 from dolphinng5_predict working tree
Includes core prod + GREEN/BLUE subsystems:
- prod/ (BLUE harness, configs, scripts, docs)
- nautilus_dolphin/ (GREEN Nautilus-native impl + dvae/ preserved)
- adaptive_exit/ (AEM engine + models/bucket_assignments.pkl)
- Observability/ (EsoF advisor, TUI, dashboards)
- external_factors/ (EsoF producer)
- mc_forewarning_qlabs_fork/ (MC regime/envelope)

Excludes runtime caches, logs, backups, and reproducible artifacts per .gitignore.
2026-04-21 16:58:38 +02:00