import logging import traceback from datetime import datetime from dotenv import load_dotenv from ibkr import Client, OptionLeg from ibkr.option_type import CALL, PUT from ibkr.order_action import BUY, SELL from os import getenv load_dotenv() def enter_iron_condor(): logging.basicConfig(filename=f'iron_condor_{datetime.now().strftime("%H%M")}.log', level=logging.INFO) try: _enter_iron_condor() except Exception as e: logging.error("Error: %s", traceback.format_exc()) def _enter_iron_condor(): ibkr_host = getenv('IBKR_HOST') ibkr_port = getenv('IBKR_PORT') ibkr_client = Client(host = ibkr_host, port = ibkr_port) symbol, sub_symbol = 'SPX', 'SPXW' expiration = datetime.now() underlying_ticker = ibkr_client.get_ticker(symbol, 'CBOE') current_price = underlying_ticker.last # Filtering strikes based on distance from current price speeds up the request. max_strike_distance = 100 def contract_filter(contract): if contract.right == CALL: return contract.strike <= (current_price + max_strike_distance) and contract.strike >= current_price return contract.strike <= current_price and contract.strike >= (current_price - max_strike_distance) # The weekly symbol for SPX (SPXW) is required in order to distinguish from monthly options. option_chain = ibkr_client.get_option_chain(symbol, expiration, sub_symbol = sub_symbol, contract_filter = contract_filter) logging.info(option_chain) target_delta = 0.10 def closest_contract_by_delta(target_delta, option_chain, option_type): options = option_chain[option_chain['Type'] == option_type].copy() options['Delta Distance'] = abs(options['Delta'] - target_delta) return options.loc[options['Delta Distance'].idxmin()] # Find the strikes that minimize the distance to the target delta. short_put_contract = closest_contract_by_delta(-target_delta, option_chain, PUT) short_call_contract = closest_contract_by_delta(target_delta, option_chain, 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 def closest_contract_by_strike(target_strike, option_chain, option_type): options = option_chain[option_chain['Type'] == option_type].copy() options['Strike Distance'] = abs(options['Strike'] - target_strike) return options.loc[options['Strike Distance'].idxmin()] long_put_contract = closest_contract_by_strike(target_long_put_strike, option_chain, PUT) long_call_contract = closest_contract_by_strike(target_long_call_strike, option_chain, 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}') 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_combo_option_order([short_call_leg, long_call_leg], 1) while not call_spread_order.isDone(): ibkr_client.ib.waitOnUpdate() if call_spread_order.orderStatus.status == 'Filled': fill_price = call_spread_order.orderStatus.avgFillPrice print('Call Spread Fill Price: ', fill_price) ibkr_client.submit_stop_loss_order(call_spread_order, fill_price * 2) 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_combo_option_order([short_put_leg, long_put_leg], 1) while not put_spread_order.isDone(): ibkr_client.ib.waitOnUpdate() if put_spread_order.orderStatus.status == 'Filled': fill_price = put_spread_order.orderStatus.avgFillPrice print('Put Spread Fill Price: ', fill_price) ibkr_client.submit_stop_loss_order(put_spread_order, fill_price * 2)