Overall Statistics |
Total Trades 15 Average Win 64.62% Average Loss -30.72% Compounding Annual Return 29.244% Drawdown 67.200% Expectancy 1.217 Net Profit 386.395% Sharpe Ratio 0.71 Probabilistic Sharpe Ratio 12.085% Loss Rate 29% Win Rate 71% Profit-Loss Ratio 2.10 Alpha 0.367 Beta 0.115 Annual Standard Deviation 0.524 Annual Variance 0.275 Information Ratio 0.621 Tracking Error 0.528 Treynor Ratio 3.249 Total Fees $27.50 Estimated Strategy Capacity $890000.00 Lowest Capacity Asset SPY YP8G69PV4W7A|SPY R735QTJ8XC9X |
#region imports from AlgorithmImports import * import numpy as np from math import log, sqrt, exp from scipy.stats import norm from QuantConnect.DataSource import * #endregion Balance = 10000 Minimum_Expiry = 720 Maximum_Expiry = 540 Min_Option_Price = 0 Profit_Taking = 2 class NOKLeapsBreakout(QCAlgorithm): def Initialize(self): self.SetStartDate(2017, 1, 1) self.SetEndDate(2023, 3, 1) self.SetCash(Balance) equity = self.AddEquity("SPY", Resolution.Minute) equity.SetDataNormalizationMode(DataNormalizationMode.Raw) self.equity = equity.Symbol self.SetBenchmark(self.equity) option = self.AddOption("SPY", Resolution.Minute) option.SetFilter(0, 5, timedelta(Minimum_Expiry), timedelta(99999)) self.yieldCurveTwo = self.AddData(USTreasuryYieldCurveRate, "USTYCR").Symbol self.SetWarmUp(timedelta(200)) self.sigma = 0.25 # Set predetermined volatility self.T = 720/365 # Set predetermined time to expiration self.r = None self.Schedule.On(self.DateRules.EveryDay(self.equity), \ self.TimeRules.AfterMarketOpen(self.equity, 30), \ self.Plotting) def OnData(self,data): if self.IsWarmingUp: return if data.ContainsKey(self.yieldCurveTwo): rates = data[self.yieldCurveTwo] self.r = ((rates.TwoYear) / 100) option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] if option_invested: if self.Time + timedelta(Maximum_Expiry) > option_invested[0].ID.Date: self.Liquidate(option_invested[0], "Too close to expiration") last_o_id = self.Transactions.LastOrderId self.Debug (f"| {self.Time} [+]--- Liquidate @ {str(self.Transactions.GetOrderTicket(last_o_id).AverageFillPrice)} || Stock @ {str(self.Portfolio[self.equity].Price)}|| Profit/Loss @ {str(self.Portfolio[option_invested[0]].LastTradeProfit)}") self.Debug (f"| {self.Time} [-]--- REASON: || <{(Maximum_Expiry)} DTE | {(option_invested[0].ID.Date - self.Time).days} DTE") return if not option_invested: for i in data.OptionChains: chains = i.Value self.BuyCall(chains) # Buy options - Call def BuyCall(self,chains): self.S = self.Securities[self.equity].Close self.K = self.S * 1.05 if self.r is None: return self.option_price = self.BlackScholesCall(self.S, self.K, self.T, self.r, self.sigma) expiry = sorted(chains,key = lambda x: x.Expiry, reverse=True)[0].Expiry calls = [i for i in chains if i.Expiry == expiry and i.Right == OptionRight.Call and i.AskPrice < self.option_price and i.AskPrice > Min_Option_Price] call_contracts = sorted(calls,key = lambda x: abs(x.Strike - x.UnderlyingLastPrice)) if len(call_contracts) == 0: return self.call = call_contracts[0] self.SetHoldings(self.call.Symbol, 0.8) quantity = self.Portfolio[self.call.Symbol].Quantity # <-- quantity is zero and the following orders will be invalid self.LimitOrder(self.call.Symbol, -quantity, (self.call.AskPrice * Profit_Taking)) self.Debug ("\r+-------------") self.Debug (f"| {self.Time} [+]--- BUY {str(self.call)} || Stock @ {str(self.call.UnderlyingLastPrice)} Option Price BS @ {(self.option_price)} rates: {self.r}") self.Debug(f"Order Quantity filled: {self.Portfolio[self.call.Symbol].Quantity}; Fill price: {self.Portfolio[self.call.Symbol].AveragePrice}") self.Log("Bought NOK Options") def Plotting(self): # plot underlying's price self.Plot("Data Chart", self.equity, self.Securities[self.equity].Close) # plot strike of call option option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] if option_invested: self.Plot("Data Chart", "strike", option_invested[0].ID.StrikePrice) self.Plot("Option Pricing", "BS - Pricing", self.option_price) self.Plot("Option Pricing", "Market - Pricing", self.Portfolio[self.call.Symbol].AveragePrice) def OnOrderEvent(self, orderEvent): order = self.Transactions.GetOrderById(orderEvent.OrderId) # Cancel remaining order if limit order or stop loss order is executed if order.Status == OrderStatus.Filled: if order.Type == OrderType.Limit or OrderType.StopMarket: self.Transactions.CancelOpenOrders(order.Symbol) if order.Status == OrderStatus.Filled and order.Type == OrderType.Limit: self.Debug(f"{self.Time} [+]--- SELL {str(order.Quantity)} || Price @ {str(order.LimitPrice)}|| Profit @ {str(self.Portfolio[self.call.Symbol].LastTradeProfit)}") if order.Status == OrderStatus.Canceled: self.Log(str(orderEvent)) # Liquidate before options are exercised if order.Type == OrderType.OptionExercise: self.Liquidate() def BlackScholesCall(self, S, K, T, r, sigma): d1 = (log(S / K) + (r + sigma ** 2 / 2) * T) / (sigma * sqrt(T)) d2 = d1 - sigma * sqrt(T) return S * norm.cdf(d1) - K * exp(-r * T) * norm.cdf(d2)