I've got a day trading algorithm that both shorts and longs futures. Only one trade is meant to happen at a time and when self.Portfolio.Invested is true or there are open orders, no new entries should submit. Entries are always stop market and exits are limit orders. It seems that if-testing for open orders is not working in Live Deployment with IBKR because QC is not recognizing any orders as open. In the order log, it appears that the all orders when expanded show every event happening at the same second (see picture). This implies that while orders and order updates are functioning properly, orders are not recognized as open. This was not an issue when back testing. I am using IBKR data if it matters.
There may be something else going on as well, because many orders are returning as “invalid” status, even those that have been filled or get filled after becoming “invalid.” This could be a result of the above, though.
These issues are resulting in a long entry filling, a short entry filling (which in fact acts as a long exit), and vice versa, then the respective exit orders being submitted. This ended up eventually resulting in orders being placed with double the intended quantity (see other picture) for a Sell Market order, leaving me short 1 contract. There aren't any market orders in the code at all… This “soft-locked” the algorithm since if-testing for self.Portfolio.Invested prevented a new “entry” order from submitting, and no “exit” orders could be triggered under OnOrderEvent, the position was held. This is dangerous with futures, and luckily the trade went in my favor and I was periodically checking the live deployment.
Is this a known bug / intended feature? What is the work around? Code below. Thank you in advance
Showing all submissions, updates, and fills being recorded as happening at the time of fillShowing multiple orders as “invalid” and with strange limits. Also at bottom shows a Sell Market order for twice the intended quantity def Initialize(self):
# Backtesting / Papertrading parameters if necessary
#self.SetCash(15000)
#self.SetStartDate(2022, 11, 15)
#self.SetEndDate(2022, 11, 15)
# Create futures contract symbol for ES using expiration date {UPDATE as necessary}
# Request contract data with tick resolution
self.esContract = Symbol.CreateFuture(Futures.Indices.MicroSP500EMini, Market.CME, datetime(2023,3,17))
self.es = self.AddFutureContract(self.esContract, Resolution.Tick)
# Futures.Indices.MicroSP500EMini // Futures.Indices.SP500EMini
# Set fees to zero for backtesting purposes
#self.Securities[self.es.Symbol].SetFeeModel(ConstantFeeModel(0.25))
# Variables to record W/L ratio & highest/lowest intraday P/L
self.tallyWin = 0
self.tallyLoss = 0
self.tallyEven = 0
self.tallyS = 0
self.tallyL = 0
self.shortWin = 0
self.longWin = 0
# Variables for intraday high/low P/L
self.lowBal = self.Portfolio.Cash
self.highBal = self.Portfolio.Cash
self.begBal = self.Portfolio.Cash
self.endBal = self.Portfolio.Cash
self.lowTime = 0
self.highTime = 0
self.startTime = 0
self.maxDrawD = 0
self.dDTime = 0
self.drawD = 0
self.haltNum = 1
self.dDHalt = False
# Variables for both short and long entry and exit tickets, trade cooldowns, prices for trailing stops and cancellations, and contracts per trade
self.sEnTicket = None
self.lEnTicket = None
self.sExTick = None
self.lExTick = None
self.liquidateTicket = None
self.exitFillTime = datetime.min
self.cancelTime = datetime.min
self.trgPrice = 0
self.highestPrice = 0
self.lowestPrice = 25000
# Quantity {UPDATE manually}
self.qty = 1
# Variables for price levels {Must be > 3 pts away from any other}
# ! Intended to be updated daily, ensure correct futures contract prices
# Daily Support and Resistance price levels
self.resi6 = 0
self.resi5 = 3901
self.resi4 = 3892
self.resi3 = 3880
self.resi2 = 3869
self.resi1 = 3856
self.supp1 = 3838
self.supp2 = 3826
self.supp3 = 3807
self.supp4 = 3784
self.supp5 = 3773
self.supp6 = 0
# Dynamic price levels {e.g. previous day low, overnight high, etc.}
self.dyna1 = 3788
self.dyna2 = 3875
self.dyna3 = 3830
self.dyna4 = 0
self.dyna5 = 0
# Macroenvironment price levels {These weren't used in back tests}
self.macr1 = 0
self.macr2 = 0
self.macr3 = 0
# Multiples of one-hundred
self.hund1 = 4000
self.hund2 = 3700
self.hund3 = 3800
# Miscellaneous price levels
self.misc1 = 0
self.misc2 = 0
self.misc3 = 0
# Variable for entry trail stop
self.trailEntry = 3
# Variable for order cancel based on PA
self.entryCancel = 4
# Variables for cooldowns {in seconds}
self.exitCool = 30
self.cancelCool = 15
# no entries occur outside of RTH
self.stopPreM = self.Time.replace(hour=9,minute=32, second=59)
self.stopAftM = self.Time.replace(hour=15,minute=55, second=59)
def OnData(self, slice):
# Plot W/L and high/low intraday P/L at EOD & update ending balance
if self.Time == self.Time.replace(hour=15, minute=59, second=59):
self.endBal = self.Portfolio.Cash
if self.endBal == self.Portfolio.Cash:
self.Plot("W/L", "Wins", self.tallyWin)
self.Plot("W/L", "Losses", self.tallyLoss)
#self.Plot("W/L", "Draws", self.tallyEven)
self.Plot("P/L", "Highest", self.highBal - self.begBal)
self.Plot("P/L", "Lowest", self.lowBal - self.begBal)
self.Plot("P/L", "Ending", self.endBal - self.begBal)
self.Plot("Intraday Times", "High Time", int(round(self.highTime / 60)))
self.Plot("Intraday Times", "Low Time", int(round(self.lowTime / 60)))
self.Plot("Intraday Times", "Max DD Time", int(round(self.dDTime / 60)))
self.Plot("Misc. Stats", "Max Drawdown", self.maxDrawD)
#self.Plot("Misc. Stats", "W/L", self.tallyWin * 100 / self.tallyTtl)
self.Plot("Misc. Stats", "Long w/l", self.longWin * 100 / self.tallyL)
self.Plot("Misc. Stats", "Short w/l", self.shortWin * 100 / self.tallyS)
# Reset balances daily
if self.Time == self.Time.replace(hour=9, minute=30, second=0):
self.highBal = self.Portfolio.Cash
self.lowBal = self.Portfolio.Cash
self.begBal = self.Portfolio.Cash
self.startTime = int(round(self.Time.timestamp()))
self.lowTime = 0
self.highTime = 0
self.maxDrawD = 0
self.dDTime = 0
self.drawD = 0
self.haltNum = 1
self.dDHalt = False
# no entries occur outside of RTH
self.stopPreM = self.Time.replace(hour=9,minute=32, second=59)
self.stopAftM = self.Time.replace(hour=15,minute=55, second=59)
# Variable for ES contract price
price = self.Securities[self.es.Symbol].Price
# Cooldowns
if (self.Time - self.exitFillTime).seconds <= self.exitCool or (self.Time - self.cancelTime).seconds <= self.cancelCool:
return
# Submit entry order if there are no positions or open orders and time conditions are met
if not self.Portfolio.Invested and not self.Transactions.GetOpenOrders(self.es.Symbol) and self.stopPreM < self.Time < self.stopAftM and self.dDHalt != True:
# Short entries
if self.resi6 + 1.5 >= price > self.resi6 or self.resi5 + 1.5 >= price > self.resi5 or self.resi4 + 1.5 >= price > self.resi4 or self.resi3 + 1.5 >= price > self.resi3 or self.resi2 + 1.5 >= price > self.resi2 or self.resi1 + 1.5 >= price > self.resi1 or self.supp6 + 1.5 >= price > self.supp6 or self.supp5 + 1.5 >= price > self.supp5 or self.supp4 + 1.5 >= price > self.supp4 or self.supp3 + 1.5 >= price > self.supp3 or self.supp2 + 1.5 >= price > self.supp2 or self.supp1 + 1.5 >= price > self.supp1 or self.dyna1 + 1.5 >= price > self.dyna1 or self.dyna2 + 1.5 >= price > self.dyna2 or self.dyna3 + 1.5 >= price > self.dyna3 or self.dyna4 + 1.5 >= price > self.dyna4 or self.dyna5 + 1.5 >= price > self.dyna5 or self.macr1 + 1.5 >= price > self.macr1 or self.macr2 + 1.5 >= price > self.macr2 or self.macr3 + 1.5 >= price > self.macr3 or self.hund1 + 1.5 >= price > self.hund1 or self.hund2 + 1.5 >= price > self.hund2 or self.hund3 + 1.5 >= price > self.hund3 or self.misc1 + 1.5 >= price > self.misc1 or self.misc2 + 1.5 >= price > self.misc2 or self.misc3 + 1.5 >= price > self.misc3:
if not self.Transactions.GetOpenOrders(self.es.Symbol):
self.sEnTicket = self.StopMarketOrder(self.es.Symbol, -self.qty, price-5)
self.trgPrice = price
# Long entries
if self.resi6 - 1.5 <= price < self.resi6 or self.resi5 - 1.5 <= price < self.resi5 or self.resi4 - 1.5 <= price < self.resi4 or self.resi3 - 1.5 <= price < self.resi3 or self.resi2 - 1.5 <= price < self.resi2 or self.resi1 - 1.5 <= price < self.resi1 or self.supp6 - 1.5 <= price < self.supp6 or self.supp5 - 1.5 <= price < self.supp5 or self.supp4 - 1.5 <= price < self.supp4 or self.supp3 - 1.5 <= price < self.supp3 or self.supp2 - 1.5 <= price < self.supp2 or self.supp1 - 1.5 <= price < self.supp1 or self.dyna1 - 1.5 <= price < self.dyna1 or self.dyna2 - 1.5 <= price < self.dyna2 or self.dyna3 - 1.5 <= price < self.dyna3 or self.dyna4 - 1.5 <= price < self.dyna4 or self.dyna5 - 1.5 <= price < self.dyna5 or self.macr1 - 1.5 <= price < self.macr1 or self.macr2 - 1.5 <= price < self.macr2 or self.macr3 - 1.5 <= price < self.macr3 or self.hund1 - 1.5 <= price < self.hund1 or self.hund2 - 1.5 <= price < self.hund2 or self.hund3 - 1.5 <= price < self.hund3 or self.misc1 - 1.5 <= price < self.misc1 or self.misc2 - 1.5 <= price < self.misc2 or self.misc3 - 1.5 <= price < self.misc3:
if not self.Transactions.GetOpenOrders(self.es.Symbol):
self.lEnTicket = self.StopMarketOrder(self.es.Symbol, self.qty, price+5)
self.trgPrice = price
# Trailing stop and possible cancellation if a short entry order is open
if self.sEnTicket is not None and self.sEnTicket.Status != OrderStatus.Filled:
# Trailing stop
if price < self.lowestPrice:
self.lowestPrice = price
if self.lowestPrice + self.trailEntry <= price:
updateFields = UpdateOrderFields()
updateFields.StopPrice = price+5
self.sEnTicket.Update(updateFields)
# Cancel order and save time if price is not rejecting the level
if price <= self.trgPrice - self.entryCancel:
self.sEnTicket.Cancel()
self.cancelTime = self.Time
# Cancel order if near end of RTH
if self.Time > self.stopAftM:
self.sEnTicket.Cancel()
# Reset long entry ticket, lowest price, and trigger price variables if order is canceled
if self.sEnTicket.Status == OrderStatus.Canceled:
self.sEnTicket = None
self.lowestPrice = 25000
self.trgPrice = 0
# Trailing stop and possible cancellation if a long entry order is open
if self.lEnTicket is not None and self.lEnTicket.Status != OrderStatus.Filled:
# Trailing stop
if price > self.highestPrice:
self.highestPrice = price
if self.highestPrice - self.trailEntry >= price:
updateFields = UpdateOrderFields()
updateFields.StopPrice = price-5
self.lEnTicket.Update(updateFields)
# Cancel order and save time if price is not rejecting the level
if price >= self.trgPrice + self.entryCancel:
self.lEnTicket.Cancel()
self.cancelTime = self.Time
# Cancel order if near end of RTH
if self.Time > self.stopAftM:
self.lEnTicket.Cancel()
# Reset long entry ticket, lowest price, and trigger price variables if order is canceled
if self.lEnTicket.Status == OrderStatus.Canceled:
self.lEnTicket = None
self.highestPrice = 25000
self.trgPrice = 0
# Trailing stop and possible cancellation and liquidation if a short closing order is open
if self.sExTick is not None and self.Portfolio.Invested:
# Trailing stop which updates as the position moves favorably
if self.sEnTicket.AverageFillPrice + 2.5 <= price:
updateFields = UpdateOrderFields()
updateFields.LimitPrice = price + 2
self.sExTick.Update(updateFields)
# Trailing stop and possible cancellation and liquidation if a long closing order is open
if self.lExTick is not None and self.Portfolio.Invested:
# Trailing stop which updates as the position moves favorably
if self.lEnTicket.AverageFillPrice - 2.5 >= price:
updateFields = UpdateOrderFields()
updateFields.LimitPrice = price - 2
self.lExTick.Update(updateFields)
def OnOrderEvent(self, orderEvent):
if orderEvent.Status != OrderStatus.Filled:
return
# Submit short exit order when long entry is filled
if self.sEnTicket is not None and self.Portfolio.Invested:
self.sExTick = self.LimitOrder(self.es.Symbol, self.qty, self.sEnTicket.AverageFillPrice - 0.5)
# Reset price variables
self.trgPrice = 0
self.highestPrice = 0
self.lowestPrice = 25000
# Submit long exit order when short entry is filled
if self.lEnTicket is not None and self.Portfolio.Invested:
self.lExTick = self.LimitOrder(self.es.Symbol, -self.qty, self.lEnTicket.AverageFillPrice + 0.5)
# Reset price variables
self.trgPrice = 0
self.highestPrice = 0
self.lowestPrice = 25000
# Save time and reset price and ticket variables when a short exit order fills & record W/L & high/low intraday P/L
if self.sExTick is not None and self.sExTick.OrderId == orderEvent.OrderId:
self.tallyS += 1
if self.sEnTicket.AverageFillPrice > self.sExTick.AverageFillPrice:
self.tallyWin += 1
self.shortWin += 1
if self.sEnTicket.AverageFillPrice < self.sExTick.AverageFillPrice:
self.tallyLoss += 1
if self.sEnTicket.AverageFillPrice == self.sExTick.AverageFillPrice:
self.tallyEven += 1
if self.Portfolio.Cash > self.highBal:
self.drawD = 0
self.highBal = self.Portfolio.Cash
self.highTime = int(round(self.Time.timestamp())) - self.startTime
if self.Portfolio.Cash < self.lowBal:
self.lowBal = self.Portfolio.Cash
self.lowTime = int(round(self.Time.timestamp())) - self.startTime
if self.Portfolio.Cash < self.highBal:
if self.highBal - self.Portfolio.Cash > self.maxDrawD:
self.maxDrawD = self.highBal - self.Portfolio.Cash
self.dDTime = int(round(self.Time.timestamp())) - self.startTime
if self.highBal - self.Portfolio.Cash > self.drawD:
self.drawD = self.highBal - self.Portfolio.Cash
self.exitFillTime = self.Time
self.sEnTicket = None
self.sExTick = None
self.highestPrice = 0
self.lowestPrice = 25000
# UPDATE THESE VALUES FOR MICRO VS TRAD MINI [/*10]
#if self.begBal+1450*self.qty <= self.highBal < self.begBal+1700*self.qty and self.haltNum != 3:
#self.haltNum = 2
if self.begBal+170*self.qty <= self.highBal:
self.haltNum = 3
if self.drawD >= 75*self.qty + 2.8*self.qty*3 and self.haltNum == 1:
self.dDHalt = True
#if self.drawD >= 500*self.qty + 2.8*self.qty*2 and self.haltNum == 2:
#self.dDHalt = True
if self.drawD >= 25*self.qty + 2.8*self.qty and self.haltNum == 3:
self.dDHalt = True
# Save time and reset price and ticket variables when a long exit order fills and record W/L & high/low intraday P/L
if self.lExTick is not None and self.lExTick.OrderId == orderEvent.OrderId:
self.tallyL += 1
if self.lEnTicket.AverageFillPrice < self.lExTick.AverageFillPrice:
self.tallyWin += 1
self.longWin += 1
if self.lEnTicket.AverageFillPrice > self.lExTick.AverageFillPrice:
self.tallyLoss += 1
if self.lEnTicket.AverageFillPrice == self.lExTick.AverageFillPrice:
self.tallyEven += 1
if self.Portfolio.Cash > self.highBal:
self.drawD = 0
self.highBal = self.Portfolio.Cash
self.highTime = int(round(self.Time.timestamp())) - self.startTime
if self.Portfolio.Cash < self.lowBal:
self.lowBal = self.Portfolio.Cash
self.lowTime = int(round(self.Time.timestamp())) - self.startTime
if self.Portfolio.Cash < self.highBal:
if self.highBal - self.Portfolio.Cash > self.maxDrawD:
self.maxDrawD = self.highBal - self.Portfolio.Cash
self.dDTime = int(round(self.Time.timestamp())) - self.startTime
if self.highBal - self.Portfolio.Cash > self.drawD:
self.drawD = self.highBal - self.Portfolio.Cash
self.exitFillTime = self.Time
self.lEnTicket = None
self.lExTick = None
self.highestPrice = 0
self.lowestPrice = 25000
# UPDATE THESE VALUES FOR MICRO VS TRAD MINI [/*10]
#if self.begBal+1450*self.qty <= self.highBal < self.begBal+1700*self.qty and self.haltNum != 3:
#self.haltNum = 2
if self.begBal+170*self.qty <= self.highBal:
self.haltNum = 3
if self.drawD >= 75*self.qty + 2.8*self.qty*3 and self.haltNum == 1:
self.dDHalt = True
#if self.drawD >= 500*self.qty + 2.8*self.qty*2 and self.haltNum == 2:
#self.dDHalt = True
if self.drawD >= 25*self.qty + 2.8*self.qty and self.haltNum == 3:
self.dDHalt = True
Rich McPharlin
Looks like you're entering limit orders for -$0.5 and $0.5 which are either invalid or not marketable. Make sure your self.sEnTicket.AverageFillPrice is valid before using it.
LoganW
What would be a good way to ensure this other than checking that the order is filled like I have? Following each limit order the ticket is reset to None to avoid duplicates or errors.
Do you know anything about testing for open orders?
Thanks
Louis Szeto
Hi LoganW
Since your limit orders' logic was based on average filled price, is would be the best if you also check whether
according to your need to ensure the AverageFilledPrice is not 0.
Best
Louis
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
LoganW
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!