"""Unit tests for live ORB opening-burst entry parity.""" from __future__ import annotations import datetime as dt from types import SimpleNamespace from unittest.mock import MagicMock, patch from zoneinfo import ZoneInfo from apps.orb_trader.engine import ORBTradingEngine _DATE = "2026-01-05" _ET = ZoneInfo("America/New_York") def _make_engine() -> ORBTradingEngine: params = SimpleNamespace( sim_bar_minutes=5, orb_minutes=5, order_timeout_minutes=25, live_breakout_use_bar_high=False, market_thrust_liquid_continuation_entry_mode="opening_burst", opening_burst_liquid_max_entry_minutes_after_open=15, daily_budget_reset=True, drawdown_governor_threshold=None, drawdown_governor_min_scale=0.30, streak_sizing_win_bonus=None, streak_sizing_loss_penalty=None, streak_sizing_max=2.5, streak_sizing_min=0.5, max_simultaneous_entries=3, atr_stop_multiplier=0.75, risk_per_trade_pct=0.05, max_position_pct=0.70, ) session = SimpleNamespace( session_id="test-session", session_name="test", initial_equity=10_000.0, ) state = MagicMock() state.get_open_positions.return_value = [] state.get_equity.return_value = 10_000.0 state.get_peak_equity.return_value = 10_000.0 state.get_daily_state.return_value = SimpleNamespace(kill_switch=False) broker = MagicMock() broker.get_account.return_value = SimpleNamespace(buying_power=100_000.0) broker.submit_market_buy.return_value = SimpleNamespace(id="buy-1") broker.get_order.return_value = SimpleNamespace( status="filled", filled_avg_price=100.50, ) eng = object.__new__(ORBTradingEngine) eng._session = session eng._params = params eng._state = state eng._broker = broker eng._log_callback = None eng._date_str = _DATE eng._day_size_scale = 1.0 eng._market_orb_quality_max_trades = None eng._market_orb_quality_reason = None eng._pending_cands = [ { "ticker": "BURST", "direction": "bullish", "orb_bar": { "timestamp": f"{_DATE}T09:30:00-05:00", "open": 100.0, "high": 105.0, "low": 99.0, "close": 104.0, "volume": 1_000, }, "atr": 2.0, "rvol": 6.0, "gap_pct": 0.08, "score": 0.95, "market_thrust_liquid_continuation": True, } ] eng._candidates = list(eng._pending_cands) eng._now_et = lambda: dt.datetime(2026, 1, 5, 9, 40, tzinfo=_ET) # type: ignore[method-assign] return eng @patch("apps.orb_trader.engine.time") @patch("apps.orb_trader.engine.get_snapshots") def test_market_thrust_opening_burst_enters_without_snapshot_breakout( mock_snapshots, mock_time, ): mock_snapshots.return_value = {"BURST": SimpleNamespace(price=100.0)} eng = _make_engine() result = eng.run_breakout_check(_DATE) assert result["filled"] == 1 eng._broker.submit_market_buy.assert_called_once() pos = eng._state.save_position.call_args.args[0] assert pos.ticker == "BURST" assert pos.trigger_type == "market_thrust_opening_burst" assert pos.entry_price == 100.50