2023-12-07 16:04:57 +00:00
|
|
|
import logging
|
|
|
|
import traceback
|
|
|
|
|
2023-12-01 20:47:46 +00:00
|
|
|
from datetime import datetime
|
2023-09-15 15:45:19 +00:00
|
|
|
from dotenv import load_dotenv
|
2023-12-01 20:47:46 +00:00
|
|
|
from ibkr import Client, OptionLeg
|
|
|
|
from ibkr.option_type import CALL, PUT
|
|
|
|
from ibkr.order_action import BUY, SELL
|
2023-09-15 15:45:19 +00:00
|
|
|
from os import getenv
|
2023-09-15 14:17:44 +00:00
|
|
|
|
2023-09-15 15:45:19 +00:00
|
|
|
load_dotenv()
|
|
|
|
|
2023-12-01 20:47:46 +00:00
|
|
|
def enter_iron_condor():
|
2023-12-07 16:04:57 +00:00
|
|
|
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():
|
2023-12-01 20:47:46 +00:00
|
|
|
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)
|
2023-12-07 16:04:57 +00:00
|
|
|
logging.info(option_chain)
|
2023-12-01 20:47:46 +00:00
|
|
|
|
|
|
|
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'])
|
|
|
|
|
2023-12-07 16:04:57 +00:00
|
|
|
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}')
|
2023-12-01 20:47:46 +00:00
|
|
|
|
|
|
|
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)
|