Overall Statistics |
Total Trades 540 Average Win 0.69% Average Loss -0.80% Compounding Annual Return 15.679% Drawdown 6.700% Expectancy 0.135 Net Profit 32.207% Sharpe Ratio 0.937 Probabilistic Sharpe Ratio 68.815% Loss Rate 39% Win Rate 61% Profit-Loss Ratio 0.87 Alpha 0.08 Beta 0.212 Annual Standard Deviation 0.08 Annual Variance 0.006 Information Ratio 0.664 Tracking Error 0.148 Treynor Ratio 0.353 Total Fees $2550.80 Estimated Strategy Capacity $850000.00 Lowest Capacity Asset SPY 32C89ACDSYUZQ|SPY R735QTJ8XC9X Portfolio Turnover 0.65% |
#region imports from AlgorithmImports import * #endregion class VirtualYellowGiraffe(QCAlgorithm): def Initialize(self): self.SetStartDate(2022, 1, 1) self.SetEndDate(2023, 12, 1) self.SetCash(100000) self.equity = self.AddEquity("SPY", Resolution.Minute) self.symbol = self.equity.Symbol self.SetBenchmark(self.equity.Symbol) self.last_time_invested = self.Time self.InitOptionsAndGreeks(self.equity) self.order_list = {} self.rsi = self.RSI(self.symbol, 2) self.volatility_target = self.GetParameter("volatility", 0.15) self.delta_target = self.GetParameter("delta", 0.5) ## Initialize Options settings, chain filters, pricing models, etc ## ==================================================================== def InitOptionsAndGreeks(self, theEquity ): ## 1. Specify the data normalization mode (must be 'Raw' for options) theEquity.SetDataNormalizationMode(DataNormalizationMode.Raw) ## 2. Set Warmup period of at least 80 days self.SetWarmup(10, Resolution.Daily) ## 3. Set the security initializer to call SetMarketPrice self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x))) ## 4. Subscribe to the option feed for the symbol theOptionSubscription = self.AddOption(theEquity.Symbol) ## 5. set the pricing model, to calculate Greeks and volatility theOptionSubscription.PriceModel = OptionPriceModels.CrankNicolsonFD() # both European & American, automatically ## 6. Set the function to filter out strikes and expiry dates from the option chain theOptionSubscription.SetFilter(self.OptionsFilterFunction) self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 5), self.Liq) def OnData(self, data): ## If we're done warming up, and not invested, Sell a put. if (not self.IsWarmingUp) and (self.Time > self.last_time_invested + timedelta(days = 1) and self.Time.minute > 44 and self.Time.hour < 12 and self.Portfolio.MarginRemaining > 0.5 * self.Portfolio.TotalPortfolioValue): if data.Bars.ContainsKey(self.symbol) and self.rsi.IsReady:# and self.rsi.Current.Value < 20: ticket = self.SellAnOTMPut() if ticket is not None: self.last_time_invested = self.Time self.order_list[ticket.OrderId] = {"id" : ticket.OrderId,"fillPrice" : ticket.AverageFillPrice, "qty": ticket.Quantity,"stopLoss" : 2 * ticket.AverageFillPrice, "takeProfit" : 0.00 * ticket.AverageFillPrice, "symbol" : ticket.Symbol} else: return #self.Log(self.order_list[ticket.OrderId]) for key, value in self.order_list.items(): try: current_symbol = value['symbol'] current_quantity = value['qty'] current_stop_loss = value['stopLoss'] current_take_profit = value['takeProfit'] current_put_price = data[current_symbol].Close #self.Debug(f'the stop loss is {current_stop_loss} while put price is {current_put_price}') #self.Debug(f'the take profit is {current_take_profit} while put price is {current_put_price}') if current_put_price > current_stop_loss: orderMessageSL = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} | " + \ f"Buy Stop Loss {current_symbol.Value}, price {current_put_price}" stop_loss_ticket = self.Order(current_symbol.Value, -current_quantity, False, orderMessageSL) self.Log(f"{self.Time} {orderMessageSL}") del self.order_list[key] break if current_put_price < current_take_profit: orderMessageTP = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} | " + \ f"Buy Take Profit {current_symbol.Value}, price {current_put_price}" take_profit_ticket = self.Order(current_symbol.Value, -current_quantity, False, orderMessageTP) self.Log(f"{self.Time} {orderMessageTP}") del self.order_list[key] break except: continue # option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] # if option_invested: # self.Portfolio[option_invested[0]] # pass # if self.Time + timedelta(28) > option_invested[0].ID.Date: # self.Liquidate(option_invested[0], "Too close to expiration") # return if self.Portfolio[self.symbol].HoldStock: self.MarketOrder(self.symbol, -self.Portfolio[self.symbol].Quantity) if self.Portfolio.MarginRemaining < 0: pass ## Sell an OTM Put Option. ## Use Delta to select a put contract to sell ## ================================================================== def SellAnOTMPut(self): ## Sell a 15 delta put expiring in 90 days putContract = self.SelectContractByDelta(self.equity.Symbol, self.delta_target, 0, OptionRight.Put) #30 delta if putContract.BidPrice != 0 and putContract.BidPrice > 0.60 and putContract.ImpliedVolatility > self.volatility_target: self.quantity = max(8,round((self.Portfolio.TotalPortfolioValue * 0.005) / (putContract.BidPrice * 100),0)) ## construct an order message -- good for debugging and order records orderMessage = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} | " + \ f"Sell {putContract.Symbol} "+ \ f"({round(putContract.Greeks.Delta,2)} Delta)" + \ f"({round(putContract.BidPrice,2)} Ask)" + \ f"(Quantity: {self.quantity})" self.Log(f"{self.Time} {orderMessage}") ticket_p = self.Order(putContract.Symbol, -self.quantity, False, orderMessage) return ticket_p else: return ## Get an options contract that matches the specified criteria: ## Underlying symbol, delta, days till expiration, Option right (put or call) ## ============================================================================ def SelectContractByDelta(self, symbolArg, strikeDeltaArg, expiryDTE, optionRightArg= OptionRight.Call): canonicalSymbol = self.AddOption(symbolArg) theOptionChain = self.CurrentSlice.OptionChains[canonicalSymbol.Symbol] theExpiryDate = self.Time + timedelta(days=expiryDTE) ## Filter the Call/Put options contracts filteredContracts = [x for x in theOptionChain if x.Right == optionRightArg] ## Sort the contracts according to their closeness to our desired expiry contractsSortedByExpiration = sorted(filteredContracts, key=lambda p: abs(p.Expiry - theExpiryDate), reverse=False) closestExpirationDate = contractsSortedByExpiration[0].Expiry ## Get all contracts for selected expiration contractsMatchingExpiryDTE = [contract for contract in contractsSortedByExpiration if contract.Expiry == closestExpirationDate] ## Get the contract with the contract with the closest delta closestContract = min(contractsMatchingExpiryDTE, key=lambda x: abs(abs(x.Greeks.Delta)-strikeDeltaArg)) return closestContract ## The options filter function. ## Filter the options chain so we only have relevant strikes & expiration dates. ## ============================================================================= def OptionsFilterFunction(self, optionsContractsChain): strikeCount = 35 # no of strikes around underyling price => for universe selection #35 minExpiryDTE = 0 # min num of days to expiration => for uni selection maxExpiryDTE = 5 # max num of days to expiration => for uni selection return optionsContractsChain.IncludeWeeklys()\ .Strikes(-strikeCount, -0)\ .Expiration(timedelta(minExpiryDTE), timedelta(maxExpiryDTE)) def Liq(self): self.Liquidate()
from AlgorithmImports import * from datetime import timedelta class PutCalendarSpreadStrategy(QCAlgorithm): def Initialize(self): self.SetStartDate(2022, 12, 29) self.SetEndDate(2023, 2, 20) self.SetCash(500000) self.underlying = self.AddEquity("SPY", Resolution.Minute) self.options = self.AddOption(self.underlying.Symbol, Resolution.Minute) self.options.SetFilter(lambda u: u.WeeklysOnly().Strikes(-2,0).Expiration(0,1)) self.symbol = self.options.Symbol self.action_taken = False def OnData(self, data): # avoid extra orders if self.Portfolio.Invested: self.holdings = [x.Symbol for x in self.Portfolio.Values if x.Invested] self.positions_debug = [x for x in self.Portfolio.Values if x.Invested] for item in self.holdings: if item.HasUnderlying == False: self.MarketOrder(self.underlying.Symbol, 100) self.Debug(f'ho comprato 100 SPY al {self.Time}') if item.HasUnderlying: chain_invested_opt = data.OptionChains.get(item.Canonical, None) if chain_invested_opt: self.contract = chain_invested_opt.Contracts.get(item) if self.contract: delta = self.contract.Greeks.Delta if delta <= -0.8 and self.action_taken and self.Time.date() == self.last_action_date: return self.MarketOrder(self.underlying.Symbol, -100) self.Debug(f'Delta contratto era {delta} e quindi ho venduto 100 SPY al {self.Time}') self.action_taken = True self.last_action_date = self.Time.date() return # Get the OptionChain of the self.symbol chain = data.OptionChains.get(self.options.Symbol, None) if not chain: return # get at-the-money strike atm_strike = sorted(chain, key=lambda x: abs(x.Strike - chain.Underlying.Price))[0].Strike # filter the put options from the contracts which is ATM in the option chain. puts = [i for i in chain if i.Strike == atm_strike and i.Right == OptionRight.Put] if len(puts) == 0: return # sorted the optionchain by expiration date expiries = sorted([x.Expiry for x in puts], key = lambda x: x) # select the farest expiry as far-leg expiry, and the nearest expiry as near-leg expiry near_expiry = expiries[0] #far_expiry = expiries[-1] naked_put = OptionStrategies.NakedPut(self.symbol, atm_strike, near_expiry) #option_strategy = OptionStrategies.PutCalendarSpread(self.symbol, atm_strike, near_expiry, far_expiry) # We open a position with 1 unit of the option strategy self.Buy(naked_put, 1) # self.Sell(option_strategy, 1) if short put calendar spreadSecurities[holdings[0].Value].Price def OnEndOfAlgorithm(self): for symbol, sec in self.Securities.items(): self.Log(f"{symbol} :: {sec.Price}") def OnEndOfDay(self): self.action_taken = False