diff --git a/crates/ml-alpha/src/data/loader.rs b/crates/ml-alpha/src/data/loader.rs index f99d36f08..3a3daac54 100644 --- a/crates/ml-alpha/src/data/loader.rs +++ b/crates/ml-alpha/src/data/loader.rs @@ -507,26 +507,11 @@ impl MultiHorizonLoader { } } -/// Mid price at level 0; returns NaN for one-sided / empty books per -/// the F2 finding (2026-05-22). MBP-10 snapshots at session boundaries, -/// halts, or stale level-0 vacancies have `bid_px = 0` or `ask_px = 0`; -/// the previous blind average produced `mid = real_price / 2 ≈ 2750` -/// (single side) or `mid = 0` (both sides empty). Downstream label -/// generation in `multi_horizon_labels::generate_outcome_labels_ab` -/// only guarded `is_finite()` (not `> 0`), so transitions from mid=0 -/// to mid≈5500 produced `ΔP ≈ 5500`, spuriously satisfying the cost -/// threshold and inflating `pos_fraction` from the expected ~5% to -/// the observed ~40%. NaN return cascades through label generation -/// and the BCE NaN-mask handles it cleanly. fn mid_price_f32(s: &Mbp10Snapshot) -> f32 { let l = s.levels.first().copied().unwrap_or_else(BidAskPair::empty); let bid = BidAskPair::price_to_f64(l.bid_px); let ask = BidAskPair::price_to_f64(l.ask_px); - if bid > 0.0 && ask > 0.0 { - (0.5 * (bid + ask)) as f32 - } else { - f32::NAN - } + (0.5 * (bid + ask)) as f32 } fn convert( diff --git a/crates/ml-alpha/src/multi_horizon_labels.rs b/crates/ml-alpha/src/multi_horizon_labels.rs index 43340af01..3600208a8 100644 --- a/crates/ml-alpha/src/multi_horizon_labels.rs +++ b/crates/ml-alpha/src/multi_horizon_labels.rs @@ -215,13 +215,7 @@ pub fn generate_outcome_labels_ab( for t in 0..n - k { let p_t = prices[t]; let p_kt = prices[t + k]; - // F2 fix (2026-05-22): also reject p <= 0 to match `generate_labels:75`. - // mid_price_f32 in loader.rs now returns NaN for one-sided books, so - // the is_finite guard alone would suffice — this is belt+suspenders - // for callers that bypass the loader (e.g. direct unit tests passing - // raw price arrays). Without the `> 0` guard a transition from - // mid=0 to mid≈5500 produces ΔP=5500, spuriously triggering y_prof=1. - if !p_t.is_finite() || !p_kt.is_finite() || p_t <= 0.0 || p_kt <= 0.0 { + if !p_t.is_finite() || !p_kt.is_finite() { continue; } let delta = p_kt - p_t;