Monitor the price of each spread and perform stop outs when necessary
This commit is contained in:
parent
1cc0c7867f
commit
21bf4c92f5
@ -1,6 +1,8 @@
|
||||
import logging
|
||||
import nest_asyncio
|
||||
import traceback
|
||||
|
||||
from dataclasses import replace
|
||||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
from ibkr import Client, OptionLeg
|
||||
@ -10,8 +12,73 @@ from os import getenv
|
||||
|
||||
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()
|
||||
|
||||
quantity = 1
|
||||
|
||||
def monitor_spread_price(short_leg: OptionLeg, long_leg: OptionLeg, stop_price: float, 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.
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
stopped_out = False
|
||||
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
|
||||
|
||||
logging.info(f'Short Contract: {short_leg.strike} {short_leg.option_type}')
|
||||
logging.info(f'Long Contract: {long_leg.strike} {long_leg.option_type}')
|
||||
logging.info(f'Current Spread Price: {current_spread_price}')
|
||||
logging.info(f'Stop Price: {stop_price}')
|
||||
|
||||
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:
|
||||
client.submit_combo_option_order([short_leg_exit, long_leg_exit], quantity)
|
||||
logging.info('Whole spread exited.')
|
||||
else:
|
||||
client.submit_option_order(short_leg_exit, quantity)
|
||||
logging.info('Short leg only exited.')
|
||||
|
||||
# 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():
|
||||
logging.basicConfig(filename=f'iron_condor_{datetime.now().strftime("%H%M")}.log', level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
filename=f'iron_condor_{datetime.now().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()
|
||||
except Exception as e:
|
||||
@ -22,6 +89,7 @@ def _enter_iron_condor():
|
||||
ibkr_port = getenv('IBKR_PORT')
|
||||
ibkr_client = Client(host = ibkr_host, port = ibkr_port)
|
||||
|
||||
# The weekly symbol for SPX (SPXW) is required in order to distinguish from monthly options.
|
||||
symbol, sub_symbol = 'SPX', 'SPXW'
|
||||
expiration = datetime.now()
|
||||
|
||||
@ -35,7 +103,6 @@ def _enter_iron_condor():
|
||||
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)
|
||||
|
||||
@ -77,23 +144,26 @@ def _enter_iron_condor():
|
||||
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)
|
||||
call_spread_order = ibkr_client.submit_combo_option_order([short_call_leg, long_call_leg], quantity)
|
||||
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)
|
||||
fill_price = abs(call_spread_order.orderStatus.avgFillPrice)
|
||||
logging.info(f'Call Spread Fill Price: {fill_price}')
|
||||
monitor_spread_price(short_call_leg, long_call_leg, fill_price * 2, 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_combo_option_order([short_put_leg, long_put_leg], 1)
|
||||
put_spread_order = ibkr_client.submit_combo_option_order([short_put_leg, long_put_leg], quantity)
|
||||
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)
|
||||
fill_price = abs(put_spread_order.orderStatus.avgFillPrice)
|
||||
logging.info(f'Put Spread Fill Price: {fill_price}')
|
||||
monitor_spread_price(short_put_leg, long_put_leg, fill_price * 2, ibkr_client)
|
||||
|
||||
# TODO: Add a shutdown hook.
|
||||
ibkr_client.run_event_loop()
|
Loading…
Reference in New Issue
Block a user