Overall Statistics |
Total Trades 16 Average Win 0.02% Average Loss -0.10% Compounding Annual Return -1.262% Drawdown 0.300% Expectancy 0.019 Net Profit -0.110% Sharpe Ratio -7.39 Probabilistic Sharpe Ratio 19.533% Loss Rate 14% Win Rate 86% Profit-Loss Ratio 0.19 Alpha -0.069 Beta 0.02 Annual Standard Deviation 0.007 Annual Variance 0 Information Ratio -6.086 Tracking Error 0.134 Treynor Ratio -2.732 Total Fees $16.00 Estimated Strategy Capacity $320000.00 Lowest Capacity Asset SPY 325YRWXDVRCRQ|SPY R735QTJ8XC9X Portfolio Turnover 0.02% |
from AlgorithmImports import * from OrderStatuses import OrderStatusCodes ''' Class to handle bracket orders TODO: CREATE STOP LOSS AND PROFIT ORDERS BASED ON THE ACTUAL FILL PRICE ''' class BracketOrder: def __init__(self, algorithm, symbol, totQuantity, placementPrice, limitPrice, stopPrice, profitPrice): self.algorithm = algorithm self.symbol = symbol self.totQuantity = totQuantity self.placementPrice = placementPrice self.limitPrice = limitPrice self.stopPrice = stopPrice self.profitPrice = profitPrice # Initialize future properties self.filledQuantity = 0 self.parentTicket = None self.stopOrderTicket = None self.profitOrderTicket = None # Place initial order self.PlaceInitialOrder() ''' Place initial order ''' def PlaceInitialOrder(self): self.parentTicket = self.algorithm.StopLimitOrder(self.symbol, self.totQuantity, self.placementPrice, self.limitPrice, tag="Initial order") self.algorithm.bracketOrders[self.parentTicket.OrderId] = self self.algorithm.Debug(f"Parent order placed for {self.symbol}") return ''' Handle order changes ''' def HandleOrderChange(self, orderEvent): # If the orderstatus is PartiallyFilled or Filled it will have a filledQuantity > 0 # Currently make no changes for order statuses that does not (partially) fill an order eventFillQuantity = orderEvent.FillQuantity if (orderEvent.FillQuantity == 0): return ''' Handle scenario where parent order is updated Add/update stop and profit orders ''' if (orderEvent.OrderId == self.parentTicket.OrderId): self.algorithm.Debug(f"Parent order ({self.symbol}) {OrderStatusCodes[orderEvent.Status]}, quantity: {eventFillQuantity}") self.filledQuantity += eventFillQuantity # Place/add to stop order (negative because we are selling) self.PlaceStopOrder(-eventFillQuantity) # Place/add to profit order (negative because we are selling) self.PlaceProfitOrder(-eventFillQuantity) return ''' Handle scenario where stop loss is hit - Cancel all open orders on the symbol - Liquidate portfolio of that stock (should not be necessary, but extra precaution) ''' if (orderEvent.OrderId == self.stopOrderTicket.OrderId): self.CancelTrade() self.algorithm.Debug(f"Stop order ({self.symbol}) {OrderStatusCodes[orderEvent.Status]}, quantity: {eventFillQuantity}") return ''' Handle scenario where profit target is hit - Cancel all open orders on the symbol (for now) - Should in reality check if profit is partially filled (not equal to self.filledQuantity), and update stop loss order - Should probably cancel parent order in case it is partially filled ''' if (orderEvent.OrderId == self.profitOrderTicket.OrderId): self.algorithm.Debug(f"Profit order ({self.symbol}) {OrderStatusCodes[orderEvent.Status]}, quantity: {eventFillQuantity}") self.CancelTrade() return ''' Place/add to stop market order when the parent order is (partially) filled ''' def PlaceStopOrder(self, stopQuantity): # Create new profit order ticket if non exists if (self.stopOrderTicket is None): self.stopOrderTicket = self.algorithm.StopMarketOrder(self.symbol, stopQuantity, self.stopPrice, tag=f"Stop order ({self.stopPrice})") # Connect the stop order ticket to this object self.algorithm.bracketOrders[self.stopOrderTicket.OrderId] = self self.algorithm.Debug(f"Stop order placed ({self.symbol})") return # Update existing stop order ticket updateSettings = UpdateOrderFields() updateSettings.Quantity = stopQuantity response = self.stopOrderTicket.Update(updateSettings) self.algorithm.Debug(f"Stop order updated ({self.symbol})") return ''' Place/add to profit taking order when the parent order is (partially) filled ''' def PlaceProfitOrder(self, profitQuantity): # Create new profit order ticket if non exists if (self.profitOrderTicket is None): self.profitOrderTicket = self.algorithm.LimitOrder(self.symbol, profitQuantity, self.profitPrice, tag=f"Profit order ({self.profitPrice})") # Connect the stop order ticket to this object self.algorithm.bracketOrders[self.profitOrderTicket.OrderId] = self self.algorithm.Debug(f"Profit order placed ({self.symbol})") return # Update existing profit order ticket updateSettings = UpdateOrderFields() updateSettings.Quantity = profitQuantity response = self.profitOrderTicket.Update(updateSettings) self.algorithm.Debug(f"Profit order updated ({self.symbol})") return ''' Cancel trade ''' def CancelTrade(self): # Cancel open orders and liquidate position (pre-caution) self.algorithm.Transactions.CancelOpenOrders(self.symbol) self.algorithm.Liquidate(self.symbol, tag="LIQUIDATION ORDER INSIDE") # Remove order IDs from bracketOrders dictionary # self.algorithm.bracketOrders.pop(self.parentTicket.OrderId, None) # self.algorithm.bracketOrders.pop(self.profitOrderTicket.OrderId, None) # self.algorithm.bracketOrders.pop(self.stopOrderTicket.OrderId, None) return
#region imports from AlgorithmImports import * #endregion OrderStatusCodes = { 0:'NEW', # new order pre-submission to the order processor 1:'SUBMITTED', # order submitted to the market 2:'PARTIALLY FILLED', # partially filled, in market order 3:'FILLED', # completed, filled, in market order 5:'CANCELED', # order cancelled before filled 6:'NONE', # no order state yet 7:'INVALID', # order invalidated before it hit the market (e.g. insufficient capital) 8:'CANCEL PENDING', # order waiting for confirmation of cancellation 9:'UPDATE SUBMITTED' # Order update submitted to the market }
# region imports from AlgorithmImports import * from QuantConnect.Securities.Option import OptionPriceModels from BracketOrder import BracketOrder # endregion class BracketOrder: def __init__(self, algorithm, symbol, totQuantity, placementPrice, limitPrice, stopPrice, profitPrice): self.algorithm = algorithm self.symbol = symbol self.totQuantity = totQuantity self.placementPrice = placementPrice self.limitPrice = limitPrice self.stopPrice = stopPrice self.profitPrice = profitPrice self.entryTicket = None self.stopLossTicket = None self.takeProfitTicket = None def placeOrder(self): self.entryTicket = self.algorithm.MarketOrder(self.symbol, self.totQuantity) self.stopLossTicket = self.algorithm.StopMarketOrder(self.symbol, -self.totQuantity, self.stopPrice) self.takeProfitTicket = self.algorithm.LimitOrder(self.symbol, -self.totQuantity, self.profitPrice) def onOrderEvent(self, orderEvent): if orderEvent.Status == OrderStatus.Filled: if orderEvent.OrderId == self.entryTicket.OrderId: # The main order (parent sell) has been filled, place SL and TP orders return elif orderEvent.OrderId == self.stopLossTicket.OrderId or orderEvent.OrderId == self.takeProfitTicket.OrderId: # Either SL or TP has been filled, cancel the opposite order if self.stopLossTicket is not None and self.takeProfitTicket is not None: # Make sure both orders exist before attempting to cancel if orderEvent.OrderId == self.stopLossTicket.OrderId: #SL order was filled, cancel TP self.algorithm.Transactions.CancelOrder(self.takeProfitTicket.OrderId) elif orderEvent.OrderId == self.takeProfitTicket.OrderId: # TP order was filled, cancel SL self.algorithm.Transactions.CancelOrder(self.stopLossTicket.OrderId) class MyAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2023, 1, 1) self.SetEndDate(2023, 2, 1) self.SetCash(1000000) equity = self.AddEquity("SPY", Resolution.Minute) equity.SetDataNormalizationMode(DataNormalizationMode.Raw) # Add an option universe option = self.AddOption("SPY", Resolution.Minute) option.PriceModel = OptionPriceModels.CrankNicolsonFD() option.SetFilter(-70, 0, timedelta(55), timedelta(65)) self.symbol = option.Symbol self.linked_orders = {} # Initiate bracket order handling dictionary self.bracketOrders = {} self.tickets = [] def OnData(self, slice): # Check if it's 10:00 AM if self.Time.hour == 10 and self.Time.minute == 0: option_chain = slice.OptionChains.get(self.symbol, None) if option_chain is None or not option_chain: self.Debug("No option chain data available.") return puts_in_range = [option for option in option_chain if 55 <= (option.Expiry - self.Time).days <= 65] if not puts_in_range: self.Debug("No options within the specified range of days until expiry.") return puts_sorted_by_expiry = sorted(puts_in_range, key=lambda x: abs(x.Expiry - self.Time - timedelta(days=60))) put_to_sell = None min_delta_difference = float('inf') for put_option in puts_sorted_by_expiry: delta_difference = abs(put_option.Greeks.Delta - (-0.2)) if delta_difference < min_delta_difference: put_to_sell = put_option min_delta_difference = delta_difference if put_to_sell is not None: ask_price = slice.Bars[put_to_sell.Symbol].Close stop_loss_price = round(ask_price * 3, 2) take_profit_price = round(ask_price * 0.5, 2) # Store the BracketOrder instance in a variable bracket_order_instance = BracketOrder(self, put_to_sell.Symbol, -1, ask_price, ask_price, stop_loss_price, take_profit_price) # Use the BracketOrder class to manage the orders bracket_order_instance.placeOrder() # Store the BracketOrder instance in a list or dictionary for future reference if needed self.bracketOrders[put_to_sell.Symbol] = bracket_order_instance # Print information about the selected put option for debugging self.Debug(f"Selected Contract Symbol: {put_to_sell.Symbol}, " + f"Strike Price: {put_to_sell.Strike}, " + f"Delta: {put_to_sell.Greeks.Delta}, " + f"Days Until Expiry: {(put_to_sell.Expiry - self.Time).days}") else: self.Debug("No options found that meet the criteria")