import logging import nest_asyncio import traceback from datetime import datetime from dotenv import load_dotenv from os import getenv 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 load_dotenv() # Allows for starting an event loop even if there's already one running in the current thread. # Necessary for monitoring spread prices asynchronously while interacting with the IBKR client. nest_asyncio.apply() def enter_iron_condor(entry_time: datetime): logging.basicConfig( filename = f'iron_condor_{entry_time.strftime("%H%M")}.log', level = logging.INFO, format = '%(asctime)s : %(levelname)s : %(message)s', datefmt = '%Y-%m-%d %H:%M:%S' ) try: _enter_iron_condor(entry_time) except Exception as e: logging.error('Error: %s', traceback.format_exc()) def _enter_iron_condor(entry_time: datetime): # The weekly symbol for SPX (SPXW) is required in order to distinguish from monthly options. symbol, sub_symbol = 'SPX', 'SPXW' expiration = datetime.now() options_chain = OptionsChain('$SPXW.X', expiration) logging.info(options_chain) credit_target = float(getenv('CREDIT_TARGET')) short_put_contract = options_chain.closest_contract_by_credit(credit_target, OptionType.PUT) short_call_contract = options_chain.closest_contract_by_credit(credit_target, OptionType.CALL) # When selecting long strikes, minimize the distance to a 50 point spread. # TODO: Select long strike based on preferred price. target_long_put_strike = short_put_contract['Strike'] - 50 target_long_call_strike = short_call_contract['Strike'] + 50 long_put_contract = options_chain.closest_contract_by_strike(target_long_put_strike, OptionType.PUT) long_call_contract = options_chain.closest_contract_by_strike(target_long_call_strike, OptionType.CALL) # Build the iron condor. short_put_strike = float(short_put_contract['Strike']) long_put_strike = float(long_put_contract['Strike']) short_call_strike = float(short_call_contract['Strike']) long_call_strike = float(long_call_contract['Strike']) logging.info(f'Short Put Strike: {short_put_strike}') logging.info(f'Long Put Strike: {long_put_strike}') logging.info(f'Short Call Strike: {short_call_strike}') logging.info(f'Long Call Strike: {long_call_strike}') ibkr_client = Client() trade = IronCondorTrade(symbol, credit_target, entry_time, float(getenv('STOP_MULTIPLE'))) short_call_leg = OptionLeg(symbol, expiration, short_call_strike, CALL, SELL, sub_symbol) long_call_leg = OptionLeg(symbol, expiration, long_call_strike, CALL, BUY, sub_symbol) call_spread_order = ibkr_client.submit_spread_order(short_call_leg, long_call_leg) logging.info(f'Call Spread Mid Price: {call_spread_order.mid_price}') logging.info(f'Call Spread Fill Price: {call_spread_order.fill_price}') logging.info(f'Call Spread Slippage: {call_spread_order.mid_price - call_spread_order.fill_price}') monitor_spread(trade, call_spread_order, ibkr_client) short_put_leg = OptionLeg(symbol, expiration, short_put_strike, PUT, SELL, sub_symbol) long_put_leg = OptionLeg(symbol, expiration, long_put_strike, PUT, BUY, sub_symbol) put_spread_order = ibkr_client.submit_spread_order(short_put_leg, long_put_leg) logging.info(f'Put Spread Mid Price: {put_spread_order.mid_price}') logging.info(f'Put Spread Fill Price: {put_spread_order.fill_price}') logging.info(f'Put Spread Slippage: {put_spread_order.mid_price - put_spread_order.fill_price}') monitor_spread(trade, put_spread_order, ibkr_client) insert_trade(trade, call_spread_order, put_spread_order) # TODO: Add a shutdown hook. ibkr_client.run_event_loop()