diff --git a/iron_condor.py b/iron_condor.py index 2ee69b6..a9e2389 100644 --- a/iron_condor.py +++ b/iron_condor.py @@ -2,19 +2,19 @@ import logging import nest_asyncio import traceback -from dataclasses import replace from datetime import datetime from dotenv import load_dotenv from os import getenv -from ibkr import Client, OptionLeg, OptionOrder +from ibkr import Client, OptionLeg from ibkr.option_type import CALL, PUT from ibkr.order_action import BUY, SELL from iron_condor_trade import IronCondorTrade +from monitor_spread import monitor_spread from options_chain import OptionsChain from option_type import OptionType -from trades_table import insert_trade, update_trade +from trades_table import insert_trade load_dotenv() @@ -22,64 +22,6 @@ load_dotenv() # Necessary for monitoring spread prices asynchronously while interacting with the IBKR client. nest_asyncio.apply() -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 - def enter_iron_condor(entry_time: datetime): logging.basicConfig( filename = f'iron_condor_{entry_time.strftime("%H%M")}.log', diff --git a/monitor_spread.py b/monitor_spread.py new file mode 100644 index 0000000..d83fe7e --- /dev/null +++ b/monitor_spread.py @@ -0,0 +1,67 @@ +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 \ No newline at end of file