Open Chocksy opened 6 months ago
New data for using ComboLimitOrder for executing spreads and the obvious issue of not respecting the limit order price when using it.
Below we have 2 screenshots of the 2 backtests one using Cancel -> Submit to update price and the other by using ComboLimitOrder.
Backtest with cancel and submit orders Backtest with ComboLimitOrder updates
BK with cancel SPX working_trades (1).csv Backtest using ComboLimitOrder updates_trades.csv
2023-01-01 00:00:00 Launching analysis for 1a12eb463ab9901671a47089708169d8 with LEAN Engine v2.5.0.0.16488
2023-01-03 09:45:00 INFO -> LimitOrderHandler.logOrderExecution: OPEN 5 CallCreditSpread-1, [3905.0, 3915.0] @ Mid: 0.98, NewLimit: 1.0, Limit: 1.0, DTTM: 2023-01-03 09:50:00, Spread: $0.15, Bid & Ask: [(1.8, 1.9), (0.85, 0.9)], Volume: [192.0, 121.0], OpenInterest: [1459, 3569]
2023-01-03 09:45:00 INFO -> LimitOrderHandler.logOrderExecution: OPEN 5 PutCreditSpread-2, [3830.0, 3820.0] @ Mid: 1.02, NewLimit: 1.05, Limit: 1.0, DTTM: 2023-01-03 09:50:00, Spread: $0.25, Bid & Ask: [(2.8, 3.0), (1.85, 1.9)], Volume: [112.0, 143.0], OpenInterest: [706, 1727]
2023-01-03 09:46:00 INFO -> CentralAlgorithm.Call: >>> OPEN: CallCreditSpread-1, Premium: $250.0 @ $0.5
2023-01-03 09:46:00 INFO -> CentralAlgorithm.Call: Working order progress of prices: [0.98]
2023-01-03 09:46:00 INFO -> CentralAlgorithm.Call: Position progress of prices: []
The limit was set to 1.05 and it filled the whole spread at 0.5. It seems from this more detailed log that the ComboLimitOrder is actually filling one log at 1.05 and the other at what ever price:
2023-01-03 09:45:00 DEBUG -> LimitOrderHandler.call: self.sinceLastRetry(context, order, timedelta(minutes = 1)): True
2023-01-03 09:45:00 DEBUG -> LimitOrderHandler.logOrderDetails: Executing Limit Order to open the position:
2023-01-03 09:45:00 DEBUG -> LimitOrderHandler.logOrderDetails: - orderType: open
2023-01-03 09:45:00 DEBUG -> LimitOrderHandler.logOrderDetails: - orderTag: PutCreditSpread-2
2023-01-03 09:45:00 DEBUG -> LimitOrderHandler.logOrderDetails: - underlyingPrice: 3868.23
2023-01-03 09:45:00 DEBUG -> LimitOrderHandler.logOrderDetails: - strikes: [3830.0, 3820.0]
2023-01-03 09:45:00 DEBUG -> LimitOrderHandler.logOrderDetails: - orderQuantity: 5
2023-01-03 09:45:00 DEBUG -> LimitOrderHandler.logOrderDetails: - midPrice: 1.025 (limitOrderPrice: 1.0)
2023-01-03 09:45:00 DEBUG -> LimitOrderHandler.logOrderDetails: - bidAskSpread: 0.25
2023-01-03 09:45:00 DEBUG -> CentralAlgorithm.OnOrderEvent: Time: 01/03/2023 14:45:00 OrderID: 3 EventID: 1 Symbol: SPXW 230103P03830000 Status: Submitted Quantity: -5 IsAssignment: False
2023-01-03 09:45:00 DEBUG -> CentralAlgorithm.OnOrderEvent: Time: 01/03/2023 14:45:00 OrderID: 4 EventID: 1 Symbol: SPXW 230103P03820000 Status: Submitted Quantity: 5 IsAssignment: False
2023-01-03 09:45:00 INFO -> LimitOrderHandler.logOrderExecution: OPEN 5 PutCreditSpread-2, [3830.0, 3820.0] @ Mid: 1.02, NewLimit: 1.05, Limit: 1.0, DTTM: 2023-01-03 09:50:00, Spread: $0.25, Bid & Ask: [(2.8, 3.0), (1.85, 1.9)], Volume: [112.0, 143.0], OpenInterest: [706, 1727]
2023-01-03 09:46:00 DEBUG -> CentralAlgorithm.OnOrderEvent: Time: 01/03/2023 14:46:00 OrderID: 1 EventID: 2 Symbol: SPXW 230103C03905000 Status: Filled Quantity: -5 FillQuantity: -5 FillPrice: $1.05 OrderFee: 3.2 USD IsAssignment: False
2023-01-03 09:46:00 DEBUG -> CentralAlgorithm.Call: -> Processing order id 1 (orderTag: CallCreditSpread-1 - orderType: open - Expiry: 2023-01-03 16:00:00)
2023-01-03 09:46:00 DEBUG -> CentralAlgorithm.OnOrderEvent: Time: 01/03/2023 14:46:00 OrderID: 2 EventID: 2 Symbol: SPXW 230103C03915000 Status: Filled Quantity: 5 FillQuantity: 5 FillPrice: $0.55 OrderFee: 3.2 USD IsAssignment: False
2023-01-03 09:46:00 DEBUG -> CentralAlgorithm.Call: -> Processing order id 1 (orderTag: CallCreditSpread-1 - orderType: open - Expiry: 2023-01-03 16:00:00)
2023-01-03 09:46:00 INFO -> CentralAlgorithm.Call: >>> OPEN: CallCreditSpread-1, Premium: $250.0 @ $0.5
2023-01-03 09:46:00 INFO -> CentralAlgorithm.Call: Working order progress of prices: [0.98]
2023-01-03 09:46:00 INFO -> CentralAlgorithm.Call: Position progress of prices: []
2023-01-03 09:46:00 DEBUG -> CentralAlgorithm.Call: The open event happened:
2023-01-03 09:46:00 DEBUG -> CentralAlgorithm.Call: - orderType: open
2023-01-03 09:46:00 DEBUG -> CentralAlgorithm.Call: - orderTag: CallCreditSpread-1
2023-01-03 09:46:00 DEBUG -> CentralAlgorithm.Call: - premium: $250.0
2023-01-03 09:46:00 DEBUG -> CentralAlgorithm.Call: - open price: $0.5
2023-01-01 00:00:00 Launching analysis for ba382406502dc8119893c370a6c85498 with LEAN Engine v2.5.0.0.16488
2023-01-03 09:45:00 INFO -> LimitOrderHandler.makeLimitOrder: OPEN 5 CallCreditSpread-1, SPXW 230103C03905000, newLimitPrice: 1.9
2023-01-03 09:45:00 INFO -> LimitOrderHandler.makeLimitOrder: OPEN 5 CallCreditSpread-1, SPXW 230103C03915000, newLimitPrice: 0.9
2023-01-03 09:45:00 INFO -> LimitOrderHandler.makeLimitOrder: OPEN 5 CallCreditSpread-1, [3905.0, 3915.0] @ Mid: 0.98, NewLimit: -1.0, Limit: 1.0, DTTM: 2023-01-03 09:50:00, Spread: $0.15, Bid & Ask: [(1.8, 1.9), (0.85, 0.9)], Volume: [192.0, 121.0], OpenInterest: [1459, 3569]
2023-01-03 09:46:00 INFO -> LimitOrderHandler.makeLimitOrder: OPEN 5 CallCreditSpread-1, SPXW 230103C03905000, newLimitPrice: 1.2
2023-01-03 09:46:00 INFO -> LimitOrderHandler.makeLimitOrder: OPEN 5 CallCreditSpread-1, SPXW 230103C03915000, newLimitPrice: 0.6
2023-01-03 09:46:00 INFO -> LimitOrderHandler.makeLimitOrder: OPEN 5 CallCreditSpread-1, [3905.0, 3915.0] @ Mid: 0.6, NewLimit: -0.6, Limit: 1.0, DTTM: 2023-01-03 09:50:00, Spread: $0.2, Bid & Ask: [(1.15, 1.25), (0.55, 0.65)], Volume: [197.0, 154.0], OpenInterest: [1459, 3569]
2023-01-03 09:47:00 INFO -> CentralAlgorithm.Call: >>> OPEN: CallCreditSpread-1, Premium: $300.0 @ $0.6
2023-01-03 09:47:00 INFO -> CentralAlgorithm.Call: Working order progress of prices: [0.98, 0.6]
2023-01-03 09:47:00 INFO -> CentralAlgorithm.Call: Position progress of prices: []
You will notice how it goes over the whole spread and it tries to fil at 1.9, 0.9, 1.0, 1.2, 0.6 and then eventually it fills at 0.6. This whole aggressive limit changes have been done to actually get a fill but idealy they should be removed. It is possible to do that but you can clearly see that the fill does happen at 0.6 and that is even on its own higher than the ComboLimitOrder call.
Here is the code that handles the ComboLimitOrder execution and updates: https://github.com/Chocksy/qc-options-framework/blob/feat/limit_orders_combo/Execution/Utils/LimitOrderHandlerWithCombo.py And here is the code that handles the ComboLegsLimitOrder execution and updates via cancel and submit: https://github.com/Chocksy/qc-options-framework/blob/feat/limit_orders_combo/Execution/Utils/LimitOrderHandler.py
It is clear that ComboLimitOrder is respecting the limit price only for one leg from what i understand from the logs. It also executes without retry in most instances because it does not actually respect that limit price we set. Using cancel and then ComboLegLimitOrder does indeed give us more flexibility but i would appreciate if we would not have to set the price per each leg as it requires calculation of the spread to try and match the required price and in reality when submitting to the broker that does not happen. The broker executes the legs at what ever price but it guarantees the spread between them to the submitter.
It seems like ComboLimitOrder can be used but its much worse in terms of getting a fill. I'll try and get it to be more aggressive than before and sort it out before closing this.
Merged a PR to add the ComboLimitOrder execution class that uses the QC method. We still are not 100% sure that the orders are respecting the IBKR limit prices so we are gonna keep this for now open.
There is an issue with the ComboLimitOrder execution on the lean framework. It seems that it does not respect the limit price so i had to change the code in LimitOrderHandler.py to use ComboLegLimitOrder. The problem with using this approach is that when i have to update the price of each individual legs and make adjusts based on that and also i have to cancel the order and submit it again to make sure that as i update one of the leg prices the execution does not happen at a bad price.
Here is what i found on the forum mentioning that the best option is to use ComboLegLimitOrder as the others are wrappers around MarketOrders: https://www.quantconnect.com/forum/discussion/14896/limit-orders-on-multi-leg-option-strategies-best-method/