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

216315_1671813502.jpgShowing all submissions, updates, and fills being recorded as happening at the time of fill216315_1671813756.jpgShowing multiple orders as “invalid” and with strange limits. Also at bottom shows a Sell Market order for twice the intended quantity
  1. def Initialize(self):
  2. # Backtesting / Papertrading parameters if necessary
  3. #self.SetCash(15000)
  4. #self.SetStartDate(2022, 11, 15)
  5. #self.SetEndDate(2022, 11, 15)
  6. # Create futures contract symbol for ES using expiration date {UPDATE as necessary}
  7. # Request contract data with tick resolution
  8. self.esContract = Symbol.CreateFuture(Futures.Indices.MicroSP500EMini, Market.CME, datetime(2023,3,17))
  9. self.es = self.AddFutureContract(self.esContract, Resolution.Tick)
  10. # Futures.Indices.MicroSP500EMini // Futures.Indices.SP500EMini
  11. # Set fees to zero for backtesting purposes
  12. #self.Securities[self.es.Symbol].SetFeeModel(ConstantFeeModel(0.25))
  13. # Variables to record W/L ratio & highest/lowest intraday P/L
  14. self.tallyWin = 0
  15. self.tallyLoss = 0
  16. self.tallyEven = 0
  17. self.tallyS = 0
  18. self.tallyL = 0
  19. self.shortWin = 0
  20. self.longWin = 0
  21. # Variables for intraday high/low P/L
  22. self.lowBal = self.Portfolio.Cash
  23. self.highBal = self.Portfolio.Cash
  24. self.begBal = self.Portfolio.Cash
  25. self.endBal = self.Portfolio.Cash
  26. self.lowTime = 0
  27. self.highTime = 0
  28. self.startTime = 0
  29. self.maxDrawD = 0
  30. self.dDTime = 0
  31. self.drawD = 0
  32. self.haltNum = 1
  33. self.dDHalt = False
  34. # Variables for both short and long entry and exit tickets, trade cooldowns, prices for trailing stops and cancellations, and contracts per trade
  35. self.sEnTicket = None
  36. self.lEnTicket = None
  37. self.sExTick = None
  38. self.lExTick = None
  39. self.liquidateTicket = None
  40. self.exitFillTime = datetime.min
  41. self.cancelTime = datetime.min
  42. self.trgPrice = 0
  43. self.highestPrice = 0
  44. self.lowestPrice = 25000
  45. # Quantity {UPDATE manually}
  46. self.qty = 1
  47. # Variables for price levels {Must be > 3 pts away from any other}
  48. # ! Intended to be updated daily, ensure correct futures contract prices
  49. # Daily Support and Resistance price levels
  50. self.resi6 = 0
  51. self.resi5 = 3901
  52. self.resi4 = 3892
  53. self.resi3 = 3880
  54. self.resi2 = 3869
  55. self.resi1 = 3856
  56. self.supp1 = 3838
  57. self.supp2 = 3826
  58. self.supp3 = 3807
  59. self.supp4 = 3784
  60. self.supp5 = 3773
  61. self.supp6 = 0
  62. # Dynamic price levels {e.g. previous day low, overnight high, etc.}
  63. self.dyna1 = 3788
  64. self.dyna2 = 3875
  65. self.dyna3 = 3830
  66. self.dyna4 = 0
  67. self.dyna5 = 0
  68. # Macroenvironment price levels {These weren't used in back tests}
  69. self.macr1 = 0
  70. self.macr2 = 0
  71. self.macr3 = 0
  72. # Multiples of one-hundred
  73. self.hund1 = 4000
  74. self.hund2 = 3700
  75. self.hund3 = 3800
  76. # Miscellaneous price levels
  77. self.misc1 = 0
  78. self.misc2 = 0
  79. self.misc3 = 0
  80. # Variable for entry trail stop
  81. self.trailEntry = 3
  82. # Variable for order cancel based on PA
  83. self.entryCancel = 4
  84. # Variables for cooldowns {in seconds}
  85. self.exitCool = 30
  86. self.cancelCool = 15
  87. # no entries occur outside of RTH
  88. self.stopPreM = self.Time.replace(hour=9,minute=32, second=59)
  89. self.stopAftM = self.Time.replace(hour=15,minute=55, second=59)
  90. def OnData(self, slice):
  91. # Plot W/L and high/low intraday P/L at EOD & update ending balance
  92. if self.Time == self.Time.replace(hour=15, minute=59, second=59):
  93. self.endBal = self.Portfolio.Cash
  94. if self.endBal == self.Portfolio.Cash:
  95. self.Plot("W/L", "Wins", self.tallyWin)
  96. self.Plot("W/L", "Losses", self.tallyLoss)
  97. #self.Plot("W/L", "Draws", self.tallyEven)
  98. self.Plot("P/L", "Highest", self.highBal - self.begBal)
  99. self.Plot("P/L", "Lowest", self.lowBal - self.begBal)
  100. self.Plot("P/L", "Ending", self.endBal - self.begBal)
  101. self.Plot("Intraday Times", "High Time", int(round(self.highTime / 60)))
  102. self.Plot("Intraday Times", "Low Time", int(round(self.lowTime / 60)))
  103. self.Plot("Intraday Times", "Max DD Time", int(round(self.dDTime / 60)))
  104. self.Plot("Misc. Stats", "Max Drawdown", self.maxDrawD)
  105. #self.Plot("Misc. Stats", "W/L", self.tallyWin * 100 / self.tallyTtl)
  106. self.Plot("Misc. Stats", "Long w/l", self.longWin * 100 / self.tallyL)
  107. self.Plot("Misc. Stats", "Short w/l", self.shortWin * 100 / self.tallyS)
  108. # Reset balances daily
  109. if self.Time == self.Time.replace(hour=9, minute=30, second=0):
  110. self.highBal = self.Portfolio.Cash
  111. self.lowBal = self.Portfolio.Cash
  112. self.begBal = self.Portfolio.Cash
  113. self.startTime = int(round(self.Time.timestamp()))
  114. self.lowTime = 0
  115. self.highTime = 0
  116. self.maxDrawD = 0
  117. self.dDTime = 0
  118. self.drawD = 0
  119. self.haltNum = 1
  120. self.dDHalt = False
  121. # no entries occur outside of RTH
  122. self.stopPreM = self.Time.replace(hour=9,minute=32, second=59)
  123. self.stopAftM = self.Time.replace(hour=15,minute=55, second=59)
  124. # Variable for ES contract price
  125. price = self.Securities[self.es.Symbol].Price
  126. # Cooldowns
  127. if (self.Time - self.exitFillTime).seconds <= self.exitCool or (self.Time - self.cancelTime).seconds <= self.cancelCool:
  128. return
  129. # Submit entry order if there are no positions or open orders and time conditions are met
  130. if not self.Portfolio.Invested and not self.Transactions.GetOpenOrders(self.es.Symbol) and self.stopPreM < self.Time < self.stopAftM and self.dDHalt != True:
  131. # Short entries
  132. 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:
  133. if not self.Transactions.GetOpenOrders(self.es.Symbol):
  134. self.sEnTicket = self.StopMarketOrder(self.es.Symbol, -self.qty, price-5)
  135. self.trgPrice = price
  136. # Long entries
  137. 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:
  138. if not self.Transactions.GetOpenOrders(self.es.Symbol):
  139. self.lEnTicket = self.StopMarketOrder(self.es.Symbol, self.qty, price+5)
  140. self.trgPrice = price
  141. # Trailing stop and possible cancellation if a short entry order is open
  142. if self.sEnTicket is not None and self.sEnTicket.Status != OrderStatus.Filled:
  143. # Trailing stop
  144. if price < self.lowestPrice:
  145. self.lowestPrice = price
  146. if self.lowestPrice + self.trailEntry <= price:
  147. updateFields = UpdateOrderFields()
  148. updateFields.StopPrice = price+5
  149. self.sEnTicket.Update(updateFields)
  150. # Cancel order and save time if price is not rejecting the level
  151. if price <= self.trgPrice - self.entryCancel:
  152. self.sEnTicket.Cancel()
  153. self.cancelTime = self.Time
  154. # Cancel order if near end of RTH
  155. if self.Time > self.stopAftM:
  156. self.sEnTicket.Cancel()
  157. # Reset long entry ticket, lowest price, and trigger price variables if order is canceled
  158. if self.sEnTicket.Status == OrderStatus.Canceled:
  159. self.sEnTicket = None
  160. self.lowestPrice = 25000
  161. self.trgPrice = 0
  162. # Trailing stop and possible cancellation if a long entry order is open
  163. if self.lEnTicket is not None and self.lEnTicket.Status != OrderStatus.Filled:
  164. # Trailing stop
  165. if price > self.highestPrice:
  166. self.highestPrice = price
  167. if self.highestPrice - self.trailEntry >= price:
  168. updateFields = UpdateOrderFields()
  169. updateFields.StopPrice = price-5
  170. self.lEnTicket.Update(updateFields)
  171. # Cancel order and save time if price is not rejecting the level
  172. if price >= self.trgPrice + self.entryCancel:
  173. self.lEnTicket.Cancel()
  174. self.cancelTime = self.Time
  175. # Cancel order if near end of RTH
  176. if self.Time > self.stopAftM:
  177. self.lEnTicket.Cancel()
  178. # Reset long entry ticket, lowest price, and trigger price variables if order is canceled
  179. if self.lEnTicket.Status == OrderStatus.Canceled:
  180. self.lEnTicket = None
  181. self.highestPrice = 25000
  182. self.trgPrice = 0
  183. # Trailing stop and possible cancellation and liquidation if a short closing order is open
  184. if self.sExTick is not None and self.Portfolio.Invested:
  185. # Trailing stop which updates as the position moves favorably
  186. if self.sEnTicket.AverageFillPrice + 2.5 <= price:
  187. updateFields = UpdateOrderFields()
  188. updateFields.LimitPrice = price + 2
  189. self.sExTick.Update(updateFields)
  190. # Trailing stop and possible cancellation and liquidation if a long closing order is open
  191. if self.lExTick is not None and self.Portfolio.Invested:
  192. # Trailing stop which updates as the position moves favorably
  193. if self.lEnTicket.AverageFillPrice - 2.5 >= price:
  194. updateFields = UpdateOrderFields()
  195. updateFields.LimitPrice = price - 2
  196. self.lExTick.Update(updateFields)
  197. def OnOrderEvent(self, orderEvent):
  198. if orderEvent.Status != OrderStatus.Filled:
  199. return
  200. # Submit short exit order when long entry is filled
  201. if self.sEnTicket is not None and self.Portfolio.Invested:
  202. self.sExTick = self.LimitOrder(self.es.Symbol, self.qty, self.sEnTicket.AverageFillPrice - 0.5)
  203. # Reset price variables
  204. self.trgPrice = 0
  205. self.highestPrice = 0
  206. self.lowestPrice = 25000
  207. # Submit long exit order when short entry is filled
  208. if self.lEnTicket is not None and self.Portfolio.Invested:
  209. self.lExTick = self.LimitOrder(self.es.Symbol, -self.qty, self.lEnTicket.AverageFillPrice + 0.5)
  210. # Reset price variables
  211. self.trgPrice = 0
  212. self.highestPrice = 0
  213. self.lowestPrice = 25000
  214. # Save time and reset price and ticket variables when a short exit order fills & record W/L & high/low intraday P/L
  215. if self.sExTick is not None and self.sExTick.OrderId == orderEvent.OrderId:
  216. self.tallyS += 1
  217. if self.sEnTicket.AverageFillPrice > self.sExTick.AverageFillPrice:
  218. self.tallyWin += 1
  219. self.shortWin += 1
  220. if self.sEnTicket.AverageFillPrice < self.sExTick.AverageFillPrice:
  221. self.tallyLoss += 1
  222. if self.sEnTicket.AverageFillPrice == self.sExTick.AverageFillPrice:
  223. self.tallyEven += 1
  224. if self.Portfolio.Cash > self.highBal:
  225. self.drawD = 0
  226. self.highBal = self.Portfolio.Cash
  227. self.highTime = int(round(self.Time.timestamp())) - self.startTime
  228. if self.Portfolio.Cash < self.lowBal:
  229. self.lowBal = self.Portfolio.Cash
  230. self.lowTime = int(round(self.Time.timestamp())) - self.startTime
  231. if self.Portfolio.Cash < self.highBal:
  232. if self.highBal - self.Portfolio.Cash > self.maxDrawD:
  233. self.maxDrawD = self.highBal - self.Portfolio.Cash
  234. self.dDTime = int(round(self.Time.timestamp())) - self.startTime
  235. if self.highBal - self.Portfolio.Cash > self.drawD:
  236. self.drawD = self.highBal - self.Portfolio.Cash
  237. self.exitFillTime = self.Time
  238. self.sEnTicket = None
  239. self.sExTick = None
  240. self.highestPrice = 0
  241. self.lowestPrice = 25000
  242. # UPDATE THESE VALUES FOR MICRO VS TRAD MINI [/*10]
  243. #if self.begBal+1450*self.qty <= self.highBal < self.begBal+1700*self.qty and self.haltNum != 3:
  244. #self.haltNum = 2
  245. if self.begBal+170*self.qty <= self.highBal:
  246. self.haltNum = 3
  247. if self.drawD >= 75*self.qty + 2.8*self.qty*3 and self.haltNum == 1:
  248. self.dDHalt = True
  249. #if self.drawD >= 500*self.qty + 2.8*self.qty*2 and self.haltNum == 2:
  250. #self.dDHalt = True
  251. if self.drawD >= 25*self.qty + 2.8*self.qty and self.haltNum == 3:
  252. self.dDHalt = True
  253. # Save time and reset price and ticket variables when a long exit order fills and record W/L & high/low intraday P/L
  254. if self.lExTick is not None and self.lExTick.OrderId == orderEvent.OrderId:
  255. self.tallyL += 1
  256. if self.lEnTicket.AverageFillPrice < self.lExTick.AverageFillPrice:
  257. self.tallyWin += 1
  258. self.longWin += 1
  259. if self.lEnTicket.AverageFillPrice > self.lExTick.AverageFillPrice:
  260. self.tallyLoss += 1
  261. if self.lEnTicket.AverageFillPrice == self.lExTick.AverageFillPrice:
  262. self.tallyEven += 1
  263. if self.Portfolio.Cash > self.highBal:
  264. self.drawD = 0
  265. self.highBal = self.Portfolio.Cash
  266. self.highTime = int(round(self.Time.timestamp())) - self.startTime
  267. if self.Portfolio.Cash < self.lowBal:
  268. self.lowBal = self.Portfolio.Cash
  269. self.lowTime = int(round(self.Time.timestamp())) - self.startTime
  270. if self.Portfolio.Cash < self.highBal:
  271. if self.highBal - self.Portfolio.Cash > self.maxDrawD:
  272. self.maxDrawD = self.highBal - self.Portfolio.Cash
  273. self.dDTime = int(round(self.Time.timestamp())) - self.startTime
  274. if self.highBal - self.Portfolio.Cash > self.drawD:
  275. self.drawD = self.highBal - self.Portfolio.Cash
  276. self.exitFillTime = self.Time
  277. self.lEnTicket = None
  278. self.lExTick = None
  279. self.highestPrice = 0
  280. self.lowestPrice = 25000
  281. # UPDATE THESE VALUES FOR MICRO VS TRAD MINI [/*10]
  282. #if self.begBal+1450*self.qty <= self.highBal < self.begBal+1700*self.qty and self.haltNum != 3:
  283. #self.haltNum = 2
  284. if self.begBal+170*self.qty <= self.highBal:
  285. self.haltNum = 3
  286. if self.drawD >= 75*self.qty + 2.8*self.qty*3 and self.haltNum == 1:
  287. self.dDHalt = True
  288. #if self.drawD >= 500*self.qty + 2.8*self.qty*2 and self.haltNum == 2:
  289. #self.dDHalt = True
  290. if self.drawD >= 25*self.qty + 2.8*self.qty and self.haltNum == 3:
  291. self.dDHalt = True
+ Expand

Author

LoganW

December 2022