Extract spread monitoring logic into its own file

This commit is contained in:
moshferatu 2024-02-29 05:45:59 -08:00
parent d7bce267ca
commit 84df97d96b
2 changed files with 70 additions and 61 deletions

View File

@ -2,19 +2,19 @@ import logging
import nest_asyncio import nest_asyncio
import traceback import traceback
from dataclasses import replace
from datetime import datetime from datetime import datetime
from dotenv import load_dotenv from dotenv import load_dotenv
from os import getenv from os import getenv
from ibkr import Client, OptionLeg, OptionOrder from ibkr import Client, OptionLeg
from ibkr.option_type import CALL, PUT from ibkr.option_type import CALL, PUT
from ibkr.order_action import BUY, SELL from ibkr.order_action import BUY, SELL
from iron_condor_trade import IronCondorTrade from iron_condor_trade import IronCondorTrade
from monitor_spread import monitor_spread
from options_chain import OptionsChain from options_chain import OptionsChain
from option_type import OptionType from option_type import OptionType
from trades_table import insert_trade, update_trade from trades_table import insert_trade
load_dotenv() load_dotenv()
@ -22,64 +22,6 @@ load_dotenv()
# Necessary for monitoring spread prices asynchronously while interacting with the IBKR client. # Necessary for monitoring spread prices asynchronously while interacting with the IBKR client.
nest_asyncio.apply() 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): def enter_iron_condor(entry_time: datetime):
logging.basicConfig( logging.basicConfig(
filename = f'iron_condor_{entry_time.strftime("%H%M")}.log', filename = f'iron_condor_{entry_time.strftime("%H%M")}.log',

67
monitor_spread.py Normal file
View File

@ -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