Files
foxhunt/crates
jgrusewski d08ab461db feat(alpha): fit_poisson via cloglog likelihood + Serde derives on FillCoeffs
Phase E Task 5 (code portion; the 5.2M-trade fit run lands separately).

Diagnosis: the plan's draft used pure Poisson NLL with Bernoulli y∈{0,1},
which converges to λ = empirical rate ȳ. But the runtime fill_prob() uses
`p = 1 − exp(−λ)`, so a trained λ=0.4 → predicted p=0.33 → systematic
~30pp under-fill bias on every passive backtest order. Training and
inference must agree on what λ means.

Fix: switch the fitter to the **cloglog (complementary log-log) binary
likelihood**. Per-sample gradient:

  ∂L/∂β_k = (p − y) · (μ/p) · x_k
  where μ = exp(β·x),  p = 1 − exp(−μ)

The μ/p factor is the link derivative; p.max(1e-7) handles the μ→0 limit
in f32 (the analytical limit μ/p → 1 is achieved automatically because
both numerator and denominator vanish proportionally).

Recovery test verifies the fix: 1000 deterministic 40% fills, all-zero
features → fitter recovers β_0 ≈ ln(0.5108) ≈ -0.672 (the value at which
1 − exp(−exp(β_0)) = 0.4), within tolerance 0.05. Slope coefficients
stay near zero (features uninformative).

Also adds Serde derives on FillCoeffs / FillFeatures / FillModel for JSON
serialization (downstream when the calibration example lands).

Deferred: the calibration example (Steps 3-7 of the plan task) is held
back until paired with the actual 5M-trade fit run — the plan's example
has a placeholder loop that violates feedback_no_stubs, and the loader
lift from precompute_features.rs deserves a dedicated commit.

4 new tests (7 total in env::fill_model now):
  - fit_recovers_baseline_when_features_uninformative
  - fit_rejects_empty_and_mismatched_inputs
  - coeffs_round_trip_through_json

`cargo test -p ml --lib env::fill_model`: 7 passed.
2026-05-15 12:32:56 +02:00
..