Files
foxhunt/config/ml/alpha_dqn_h6000_smoke.json
jgrusewski 5c0bcb1fdb fix(alpha): MBP-10 parser full-levels copy + fit_poisson L2 regularization
Two carried-over limitations from Phase E.0 / E.1 fixed and verified.

1. MBP-10 parser bug fix (`parse_mbp10_streaming` + `parse_mbp10_file`)

   The DBN crate's `Mbp10Msg` carries the FULL post-update top-10 book
   in `levels: [BidAskPair; 10]` per message — not just the single
   update event's price/size. Previously the parser only called
   `update_level(0, ...)` with the update event's fields, leaving
   `current_snapshot.levels[1..10]` at default-empty. Downstream:
     - OFI calculator reading L2-L5 got zeros → produced wrong OFI
       features (the canonical Phase 1c/1d 81-dim feature stack has
       multi-level OFI as features 0..5; with the bug these were
       constant zero).
     - microprice (`snapshot.levels[1]`) got zeros.
     - FillModel L2/L3 fit observations got zeros, so L2/L3
       coefficients were undefined (we worked around by replicating
       L1 with attenuated intercept).

   Fix: after `update_level(0, ...)`, copy fields from
   `mbp10.levels[lvl]` into `current_snapshot.levels[lvl]` for `lvl
   in 1..max_lvl`. Field-by-field copy preserves the existing scale
   convention (raw 1e9 fixed-point i64). Applied to both streaming
   and async file-parse code paths.

   Comment "For simplicity, store all updates in level 0 / A full
   implementation would maintain proper level ordering" removed.

2. fit_poisson L2 regularization

   New `fit_poisson_l2(features, observed, max_iters, lr, l2_lambda)`
   API (the old `fit_poisson` delegates with l2_lambda=0). L2 penalty
   applies to slope coefficients β[1..5] but NOT to intercept β[0]
   (penalizing the intercept biases toward p≈0.5 for all-zero-feature
   samples, breaking the recovery test). Per-iteration update:

     β[0] -= lr · grad[0] / n               (intercept)
     β[k] -= lr · (grad[k] / n + λ · β[k])  (slope, k ∈ 1..5)

   Canonical motivation: on real 5.2M-trade ES.FUT data the
   unregularized fitter converged to β_spread ≈ -40 (Task 5c commit
   12151ccf6), producing near-zero limit fill probability at typical
   spreads despite empirical fill rate ~70%. With l2_lambda=0.01 the
   slope shrinks modestly while intercept tracks the empirical rate.
   Default in the calibration binary bumped to 0.01.

   New unit test `fit_poisson_l2_shrinks_slope_on_pathological_outlier`
   constructs 990 typical samples + 10 wide-spread outliers and
   verifies `|β_spread|` with L2 < `|β_spread|` without L2. Passes.

3. Cascade re-run verifies the fix is verdict-robust:

     New fit (with L2 + parser fix, 500K snapshots):
       BID L1: β_0=-0.24  β_spread=-1.87  β_imbal=-0.10  β_ofi=-0.006  β_logτ=-0.30
       ASK L1: β_0=+0.21  β_spread=-36.41 β_imbal=+0.19  β_ofi=+0.81   β_logτ=+0.22
       (β_spread on ask still large but β_0 sane; cloglog model
       fundamentally mis-fits the binary tight-spread / wide-spread regime.)

     New baseline (with new fill model):
       mean = -5191.53   (vs old -5185.13)
       std  =  4963.62   (vs old  4952.85)
       Negligible drift, env dynamics essentially unchanged.

     H=6000 smoke re-run (same alpha cache, new fill model + parser):
       Q_SPREAD_EMA         = 29.59   (was 35.44)
       ACTION_ENTROPY_EMA   = 2.00    (was 2.00)
       RETURN_VS_RANDOM_EMA = +1.001σ (was +1.003σ)
       EARLY_Q_MOVEMENT_EMA = 0.130   (was 0.130)
       Overall: PASS (was PASS)

   Verdict is ROBUST to the fixes — the fxcache-based smoke is
   insulated from the MBP-10 parser bug (uses synthesized bid/ask
   from mid), and the FillModel quality improvement is minor enough
   that the policy's behaviour is essentially unchanged. The fixes
   matter MORE for production training paths that read MBP-10
   directly (those see the full L2-L10 book now).

Files touched:
  crates/data/src/providers/databento/dbn_parser.rs (parser fix in
    both parse_mbp10_streaming and parse_mbp10_file)
  crates/ml/src/env/fill_model.rs (new fit_poisson_l2 + test)
  crates/ml/examples/alpha_fit_fill_model.rs (--l2-lambda flag)
  crates/ml/examples/alpha_dqn_h600_smoke.rs (updated hardcoded
    baseline values to match the new random baseline run)
  config/ml/alpha_fill_coeffs.json (re-fitted with both fixes)
  config/ml/alpha_random_baseline.json (re-run with new fill model)
  config/ml/alpha_dqn_h6000_smoke.json (verified PASS)

All 8 fill_model tests pass. Build clean across data, ml-alpha, ml.
2026-05-15 17:20:52 +02:00

166 lines
4.2 KiB
JSON

{
"action_entropy_ema": 1.9956868886947632,
"all_pass": true,
"alpha_m": 0.8999999761581421,
"early_q_movement_ema": 0.13011053204536438,
"eps_end": 0.05000000074505806,
"eps_start": 0.5,
"gamma": 0.9900000095367432,
"grad_clip": 1.0,
"horizon": 6000,
"kc_log": [
{
"early_mvmt": 0.014495394192636013,
"entropy": 2.02925705909729,
"episode": 50,
"q_spread": 16.203521728515625,
"rvr": 1.0110208988189697
},
{
"early_mvmt": 0.021577946841716766,
"entropy": 2.0279672145843506,
"episode": 100,
"q_spread": 19.90399932861328,
"rvr": 1.0110208988189697
},
{
"early_mvmt": 0.029696958139538765,
"entropy": 2.0261473655700684,
"episode": 150,
"q_spread": 19.78053092956543,
"rvr": 1.0108973979949951
},
{
"early_mvmt": 0.03799359127879143,
"entropy": 2.024191379547119,
"episode": 200,
"q_spread": 22.229785919189453,
"rvr": 1.0106135606765747
},
{
"early_mvmt": 0.04607415571808815,
"entropy": 2.022186040878296,
"episode": 250,
"q_spread": 22.845211029052734,
"rvr": 1.0104221105575562
},
{
"early_mvmt": 0.05395592004060745,
"entropy": 2.019918441772461,
"episode": 300,
"q_spread": 26.798715591430664,
"rvr": 1.00983726978302
},
{
"early_mvmt": 0.06150386109948158,
"entropy": 2.0177736282348633,
"episode": 350,
"q_spread": 28.840295791625977,
"rvr": 1.0095032453536987
},
{
"early_mvmt": 0.06863006949424744,
"entropy": 2.015869140625,
"episode": 400,
"q_spread": 28.40835952758789,
"rvr": 1.0089091062545776
},
{
"early_mvmt": 0.07545721530914307,
"entropy": 2.013965129852295,
"episode": 450,
"q_spread": 23.48478889465332,
"rvr": 1.0084400177001953
},
{
"early_mvmt": 0.0819401815533638,
"entropy": 2.0119924545288086,
"episode": 500,
"q_spread": 23.23484230041504,
"rvr": 1.0082913637161255
},
{
"early_mvmt": 0.0881161019206047,
"entropy": 2.010127544403076,
"episode": 550,
"q_spread": 24.8494873046875,
"rvr": 1.0076979398727417
},
{
"early_mvmt": 0.09393466264009476,
"entropy": 2.0083234310150146,
"episode": 600,
"q_spread": 22.99201202392578,
"rvr": 1.0069901943206787
},
{
"early_mvmt": 0.09942196309566498,
"entropy": 2.006577253341675,
"episode": 650,
"q_spread": 32.014129638671875,
"rvr": 1.0060724020004272
},
{
"early_mvmt": 0.10459540784358978,
"entropy": 2.004857063293457,
"episode": 700,
"q_spread": 26.52780532836914,
"rvr": 1.0056869983673096
},
{
"early_mvmt": 0.10947921127080917,
"entropy": 2.0032100677490234,
"episode": 750,
"q_spread": 98.947265625,
"rvr": 1.005436658859253
},
{
"early_mvmt": 0.11409175395965576,
"entropy": 2.0016136169433594,
"episode": 800,
"q_spread": 48.021114349365234,
"rvr": 1.0041899681091309
},
{
"early_mvmt": 0.11845553666353226,
"entropy": 2.0000827312469482,
"episode": 850,
"q_spread": 35.853179931640625,
"rvr": 1.0036035776138306
},
{
"early_mvmt": 0.1225731149315834,
"entropy": 1.998610019683838,
"episode": 900,
"q_spread": 34.26390075683594,
"rvr": 1.0024551153182983
},
{
"early_mvmt": 0.12645159661769867,
"entropy": 1.9971414804458618,
"episode": 950,
"q_spread": 37.47758102416992,
"rvr": 1.0019822120666504
},
{
"early_mvmt": 0.13011053204536438,
"entropy": 1.9956868886947632,
"episode": 1000,
"q_spread": 29.594701766967773,
"rvr": 1.001029133796692
}
],
"lr": 0.00009999999747378752,
"n_episodes": 1000,
"pass_early": true,
"pass_entropy": true,
"pass_q_spread": true,
"pass_rvr": true,
"phase": "E.1 Task 12",
"q_init_norm": 2.5735182762145996,
"q_spread_ema": 29.594701766967773,
"return_vs_random_ema": 1.001029133796692,
"reward_scale": 1000.0,
"target_update_every": 10,
"tau": 0.029999999329447746
}