self = def test_full_bundle_execution_with_mocked_alpaca(self): """ Execute a full 6-symbol bundle (3 long + 3 short) and verify: - Correct symbols receive buy_market() calls - Correct symbols receive short() calls - No cross-contamination (long symbols not shorted, short symbols not bought) - Allocation amounts are positive Note: Stock splits may cause multiple calls per symbol (BUY_TRADER_STOCK_SPLIT_RATIOS). We verify by unique symbols, not by call count. """ from rtrader.services.buy_trader_executor import BuyTraderExecutor # Create payload exactly as place-order would generate payload = create_test_payload_long_short( account_nick="yuchao", long_symbols=["AAPL", "MSFT", "GOOGL"], short_symbols=["SQQQ", "UVXY", "QID"], long_allocations=[0.30, 0.30, 0.40], short_allocations=[0.30, 0.30, 0.40], total_buying_power=10000.0, ) # Create executor in direct mode (immediate execution, not plan mode) executor = BuyTraderExecutor( broker_backend="alpaca", execution_mode="direct", account_nick="yuchao", disable_split_sleep=True, # Disable sleep between split orders ) # Create comprehensive mock broker mock_broker = MagicMock() # Track all calls for verification buy_calls = [] short_calls = [] def mock_buy_market(symbol, **kwargs): buy_calls.append({ "symbol": symbol, "shares": kwargs.get("shares"), "amount": kwargs.get("amount"), }) return { "status": "success", "order_id": f"buy_{symbol}_{len(buy_calls)}", "filled_qty": kwargs.get("shares", 1), } def mock_short(symbol, **kwargs): short_calls.append({ "symbol": symbol, "shares": kwargs.get("shares"), "amount": kwargs.get("amount"), }) return { "status": "success", "order_id": f"short_{symbol}_{len(short_calls)}", "filled_qty": kwargs.get("shares", 1), } def mock_get_quote(symbol): # Return different prices for different symbols prices = { "AAPL": 185.0, "MSFT": 420.0, "GOOGL": 175.0, "SQQQ": 12.0, "UVXY": 28.0, "QID": 18.0, } return {"last_trade_price": prices.get(symbol, 100.0)} mock_broker.buy_market.side_effect = mock_buy_market mock_broker.short.side_effect = mock_short mock_broker.get_quote.side_effect = mock_get_quote # Execute with mocked broker with patch.object(executor, '_resolve_broker', return_value=mock_broker), \ patch.object(executor, 'fetch_buying_power', return_value=10000.0), \ patch('rtrader.cli.buy_trader._resolve_trading_day', return_value=date.today()), \ patch('rtrader.scheduler.trading_day_gap', return_value=0): result = executor.execute_job(payload) # ============================================================ # VERIFICATION: Check correct symbols received correct calls # Note: Stock splits may cause multiple calls per symbol # ============================================================ # Get unique symbols from each call type buy_symbols_unique = set(call["symbol"] for call in buy_calls) short_symbols_unique = set(call["symbol"] for call in short_calls) # Verify all 3 long symbols received buy_market calls > assert buy_symbols_unique == {"AAPL", "MSFT", "GOOGL"}, \ f"Buy symbols should be AAPL, MSFT, GOOGL, got {buy_symbols_unique}" E AssertionError: Buy symbols should be AAPL, MSFT, GOOGL, got set() E assert set() == {'AAPL', 'GOOGL', 'MSFT'} E E Extra items in the right set: E 'MSFT' E 'AAPL' E 'GOOGL' E E Full diff:... E E ...Full output truncated (6 lines hidden), use '-vv' to show tests/test_buy_trader_long_short_payload.py:844: AssertionError