diff --git a/apps/paper_trader/engine.py b/apps/paper_trader/engine.py index 633e0ca..d4a666c 100644 --- a/apps/paper_trader/engine.py +++ b/apps/paper_trader/engine.py @@ -295,23 +295,38 @@ class PaperTradingEngine: ) if engines: + # Residual reserve: engines that set residual_reserve_selected=True + # prevent later engines from picking the same event_id/symbol. + # Matches BacktestRunner._select_candidates_for_date(). + reserved_event_ids: set[str] = { + ss.event_id for ss in strategy_states_after_exits.values() + } + reserved_symbols: set[str] = { + p.symbol for p in alpaca_positions_after_exits + if p.symbol in strategy_states_after_exits + } for engine_cfg in engines: + prelimit = self._config.signal.max_candidates_per_day + if self._attention_service.engine_requires_attention(engine_cfg): + prelimit = max(prelimit * 5, prelimit) engine_candidates = select_candidates( raw_rows=candidate_rows, universe_config=self._config.universe, signal_config=self._config.signal, event_type_profiles=self._config.event_type_profiles or {}, strategy_engine=engine_cfg, - excluded_event_ids={ - ss.event_id - for ss in strategy_states_after_exits.values() - }, - excluded_symbols={p.symbol for p in alpaca_positions_after_exits if p.symbol in strategy_states_after_exits}, + truncate_to=prelimit, + excluded_event_ids=reserved_event_ids, + excluded_symbols=reserved_symbols, ) # Attention filtering (matches BacktestRunner) engine_candidates = self._attention_service.apply_filters( engine_candidates, engine_cfg, self._config.signal, ) + # Residual reserve for next engine + if engine_cfg.residual_reserve_selected and engine_candidates: + reserved_event_ids.update(c.event_id for c in engine_candidates) + reserved_symbols.update(c.symbol.upper() for c in engine_candidates) engine_risk_used = engine_daily_risk_used.get(engine_cfg.engine_id, 0.0) for candidate in engine_candidates: @@ -945,21 +960,30 @@ class PaperTradingEngine: ) engine_list = engines if engines else [None] + reserved_event_ids: set[str] = {ss.event_id for ss in strategy_states.values()} + reserved_symbols: set[str] = {p.symbol for p in alpaca_positions if p.symbol in strategy_states} for engine_cfg in engine_list: if engine_cfg is not None: + prelimit = self._config.signal.max_candidates_per_day + if self._attention_service.engine_requires_attention(engine_cfg): + prelimit = max(prelimit * 5, prelimit) engine_candidates = select_candidates( raw_rows=candidate_rows, universe_config=self._config.universe, signal_config=self._config.signal, event_type_profiles=self._config.event_type_profiles or {}, strategy_engine=engine_cfg, - excluded_event_ids={ss.event_id for ss in strategy_states.values()}, - excluded_symbols={p.symbol for p in alpaca_positions if p.symbol in strategy_states}, + truncate_to=prelimit, + excluded_event_ids=reserved_event_ids, + excluded_symbols=reserved_symbols, ) # Attention filtering (matches BacktestRunner) engine_candidates = self._attention_service.apply_filters( engine_candidates, engine_cfg, self._config.signal, ) + if engine_cfg.residual_reserve_selected and engine_candidates: + reserved_event_ids.update(c.event_id for c in engine_candidates) + reserved_symbols.update(c.symbol.upper() for c in engine_candidates) engine_risk_used = engine_daily_risk_used.get(engine_cfg.engine_id, 0.0) else: engine_candidates = select_candidates( @@ -967,8 +991,8 @@ class PaperTradingEngine: universe_config=self._config.universe, signal_config=self._config.signal, event_type_profiles=self._config.event_type_profiles or {}, - excluded_event_ids={ss.event_id for ss in strategy_states.values()}, - excluded_symbols={p.symbol for p in alpaca_positions if p.symbol in strategy_states}, + excluded_event_ids=reserved_event_ids, + excluded_symbols=reserved_symbols, ) engine_risk_used = 0.0 @@ -1296,6 +1320,24 @@ class PaperTradingEngine: if len(closes) >= sma_period: macro[f"{key_prefix}_sma_{sma_period}"] = sum(closes[-sma_period:]) / sma_period + # Fetch FRED macro data (VIX, HY spread) for regime sizing + # Matches SnapshotStore._fetch_macro() which loads MacroObservation from DB + try: + from libs.oracle_client import FredService, OracleClient as _OC + async with _OC(base_url=self._detector._oracle_url) as fred_client: + fred_svc = FredService(fred_client) + for series_id in ("VIXCLS", "BAMLH0A0HYM2"): + try: + resp = await fred_svc.get_observations(series_id, start=start.isoformat(), end=date.isoformat()) + if resp.observations: + latest = [o for o in resp.observations if o.value is not None] + if latest: + macro[series_id] = latest[-1].value + except Exception: + pass + except Exception: + pass + return macro except Exception as exc: