From 0797535047ff102ef5eff90a6c85b43a0c54012d Mon Sep 17 00:00:00 2001 From: I Luk Kim Date: Tue, 24 Mar 2026 04:33:08 -0700 Subject: [PATCH] =?UTF-8?q?Revert=20DB-first=20feature=20fix=20=E2=80=94?= =?UTF-8?q?=20Oracle=20real-time=20enrichment=20must=20be=20primary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DB-first approach (prefer feature_json over Oracle recalculation) caused: - LMND (+$782) and M (+$1,052) trades to disappear - TEM loss to increase from -$321 to -$535 - Overall PnL drop from +$5,948 to +$3,078 Root cause: DB features were computed at a different time with different Oracle data. When paper trader used DB values, the feature values didn't match what the backtester's Parquet snapshot had, causing different engine gate outcomes. Paper trader must use Oracle real-time enrichment as primary source (same as the original design). The volume_ratio_20d field name fix is retained as that was a genuine bug. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/paper_trader/event_detector.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/paper_trader/event_detector.py b/apps/paper_trader/event_detector.py index 6772cab..453aa65 100644 --- a/apps/paper_trader/event_detector.py +++ b/apps/paper_trader/event_detector.py @@ -135,15 +135,11 @@ class EventDetector: if not enriched.get("reaction_day_high"): enriched["reaction_day_high"] = sym_bars[rd].get("high") - # Compute market features from Oracle bars ONLY if missing in DB - # feature_json. DB values are authoritative because they were computed - # by the feature_builder at event time with the correct reaction_date - # and base price. Oracle bars can produce different values due to - # non-deterministic data or different date alignment. - _db_has_reaction = enriched.get("reaction_day_return") is not None + # Compute market features from Oracle bars if missing in DB feature_json. + # These can be None when the feature builder ran before reaction-day bars settled. sym_bars = bars_by_symbol.get(sym, {}) rd = _parse_date(enriched.get("reaction_date")) - if rd and rd in sym_bars and not _db_has_reaction: + if rd and rd in sym_bars: sorted_dates = sorted(sym_bars.keys()) try: rd_idx = sorted_dates.index(rd)