import logging from dataclasses import replace from ibkr import Client, OptionOrder from ibkr.order_action import BUY, SELL from iron_condor_trade import IronCondorTrade from trades_table import update_trade def monitor_spread(trade: IronCondorTrade, spread_order: OptionOrder, client: Client): """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" Stop loss orders will not execute if trying to sell back a contract with no bid while paper trading. Therefore, the spread price must be monitored and the spread manually exited if the stop price is reached. If there is no bid for the long leg, only the short leg will be exited. """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" stop_price = spread_order.fill_price * trade.stop_multiple stopped_out = False short_leg = spread_order.legs[0] long_leg = spread_order.legs[1] market_data = {} # Stores real-time market data for each leg. def on_market_data_update(update_event): # Prevent the trade from being exited multiple times if there are other updates queued. # This will prevent unintentionally entering new trades. nonlocal stopped_out if stopped_out: return # Ensure there is market data for both legs before proceeding. if short_leg in market_data and long_leg in market_data: short_contract = market_data[short_leg] long_contract = market_data[long_leg] # If a contract has no bid -1.0 is returned, set it to 0 to avoid negative mid prices. mid_price_short = (max(short_contract.bid, 0) + short_contract.ask) / 2 mid_price_long = (max(long_contract.bid, 0) + long_contract.ask) / 2 current_spread_price = mid_price_short - mid_price_long if current_spread_price >= stop_price: stopped_out = True logging.info('Stop price reached or exceeded. Exiting trade.') short_leg_exit = replace(short_leg, action = BUY if short_leg.action == SELL else SELL) long_leg_exit = replace(long_leg, action = BUY if long_leg.action == SELL else SELL) if long_contract.bid > 0: exit_order = client.submit_spread_order(short_leg_exit, long_leg_exit) logging.info('Whole spread exited.') else: exit_order = client.submit_option_order(short_leg_exit) logging.info('Short leg only exited.') exit_slippage = round(exit_order.fill_price - stop_price, 3) logging.info(f'Exit Slippage: {exit_slippage}') update_trade(trade, exit_order) # Unsubscribe from market data updates once the trade has exited. for leg in [short_leg, long_leg]: market_data[leg].updateEvent -= on_market_data_update for leg in [short_leg, long_leg]: option_contract = client.get_option_contract(leg) leg_market_data = client.get_market_data(option_contract, streaming = True) market_data[leg] = leg_market_data leg_market_data.updateEvent += on_market_data_update