| Overall Statistics |
|
Total Trades 55 Average Win 6.84% Average Loss -13.09% Compounding Annual Return 15.680% Drawdown 32.300% Expectancy 0.456 Net Profit 276.612% Sharpe Ratio 0.532 Sortino Ratio 0.625 Probabilistic Sharpe Ratio 6.062% Loss Rate 4% Win Rate 96% Profit-Loss Ratio 0.52 Alpha 0.044 Beta 0.924 Annual Standard Deviation 0.21 Annual Variance 0.044 Information Ratio 0.246 Tracking Error 0.158 Treynor Ratio 0.121 Total Fees $56.80 Estimated Strategy Capacity $0 Lowest Capacity Asset QQQ YTG30PLFBECM|QQQ RIWIV7K5Z9LX Portfolio Turnover 0.26% |
# region imports
from AlgorithmImports import *
from QuantConnect.DataSource import *
import numpy as np
from math import log, sqrt, exp
from scipy.stats import norm
# endregion
VOLA = 126; BASE_RET = 85; LEV = 0.99;
class FormalOrangeBadger(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
#self.SetEndDate(2016, 12, 3)
self.SetCash(10000)
self.DCH_previous_Up = dict()
self.DCH_previous_Down = dict()
self.DCH_previous_Middle = dict()
self.dch = dict()
self.roc = dict()
self.aps = dict()
self.adx = dict()
#self.log_roc = dict()
self.std_log_roc = dict()
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash)
self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
#self.SetSecurityInitializer(self.customSecurityInitializer)
self.vix = self.AddData(CBOE, "VIX").Symbol
self.IVlvl = 0.5
self.Ivlvl_High = 0.9
self.threshold = 0.02
self.std_log_roc_MIN = dict()
self.volatility = 0
self.option_symbol = dict()
self.option = dict()
#self.ticket_initial = None
#self.ticket_dca = None
self.yieldCurveTwo = self.AddData(USTreasuryYieldCurveRate, "USTYCR").Symbol
history = self.History(USTreasuryYieldCurveRate, self.yieldCurveTwo, 2, Resolution.Daily)
#"IWM", "NVDA", "AAPL", "AMD", "AMZN", "MSFT", "DIS", "GOOGL", "META", "GOOG", "INTC", "XOM", "DD", "UPS", "JNJ", "JPM", "PG"
for ticker in ["SPY", "QQQ", ]:
symbol = self.AddEquity(ticker, Resolution.Minute).Symbol
self.option[symbol] = self.AddOption(symbol, Resolution.Minute)
self.option[symbol].SetFilter(lambda universe: universe.IncludeWeeklys().Strikes(-15, 15).Expiration(600, 999999))
self.option_symbol[symbol] = self.option[symbol].Symbol
self.option[symbol].PriceModel = OptionPriceModels.CrankNicolsonFD()
self.dch[symbol] = self.DCH(symbol, 20, 20, Resolution.Daily)
self.DCH_previous_Up[symbol] = None
self.DCH_previous_Down[symbol] = None
self.DCH_previous_Middle[symbol] = None
self.roc[symbol] = self.ROC(symbol, 60, Resolution.Minute)
self.SLV = self.AddEquity('SLV', Resolution.Daily).Symbol
self.GLD = self.AddEquity('GLD', Resolution.Daily).Symbol
self.XLI = self.AddEquity('XLI', Resolution.Daily).Symbol
self.XLU = self.AddEquity('XLU', Resolution.Daily).Symbol
self.DBB = self.AddEquity('DBB', Resolution.Daily).Symbol
self.UUP = self.AddEquity('UUP', Resolution.Daily).Symbol
self.EnableAutomaticIndicatorWarmUp = True
self.SetWarmUp(100, Resolution.Daily)
self.SetBenchmark("SPY")
self.spy = self.AddEquity('SPY', Resolution.Daily)
self.MKT = self.spy.Symbol
self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
self.pairs = [self.SLV, self.GLD, self.XLI, self.XLU, self.DBB, self.UUP]
self.bull = 1
self.count = 0
self.outday = 0
self.wt = {}
self.real_wt = {}
self.spy = []
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen('SPY', 30),
self.daily_check)
#self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 30), self.CheckDTEPuts)
self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen("SPY", 30), self.CheckDTECalls)
self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen("SPY", 10), self.DCA_Bear)
#self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen("SPY", 0), self.VIX)
#self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen("SPY", 40), self.Plotting)
symbols = [self.MKT] + self.pairs
for symbol in symbols:
self.consolidator = TradeBarConsolidator(timedelta(days=1))
self.consolidator.DataConsolidated += self.consolidation_handler
self.SubscriptionManager.AddConsolidator(symbol, self.consolidator)
self.history = self.History(symbols, VOLA + 1, Resolution.Daily)
if self.history.empty or 'close' not in self.history.columns:
return
self.history = self.history['close'].unstack(level=0).dropna()
def consolidation_handler(self, sender, consolidated):
self.history.loc[consolidated.EndTime, consolidated.Symbol] = consolidated.Close
self.history = self.history.iloc[-(VOLA + 1):]
def daily_check(self):
vola = self.history[[self.MKT]].pct_change().std() * np.sqrt(252)
wait_days = int(vola * BASE_RET)
period = int((1.0 - vola) * BASE_RET)
r = self.history.pct_change(period).iloc[-1]
VIXhistory = self.History(CBOE, self.vix, 90, Resolution.Daily)
# (Current - Min) / (Max - Min)
self.rank = ((self.Securities[self.vix].Price - min(VIXhistory["low"])) / (max(VIXhistory["high"]) - min(VIXhistory["low"])))
exit = ((r[self.SLV] < r[self.GLD]) and (r[self.XLI] < r[self.XLU]) and (r[self.DBB] < r[self.UUP]) and (self.rank > self.IVlvl))
if exit:
self.bull = 0
self.outday = self.count
if self.count >= self.outday + wait_days:
self.bull = 1
self.count += 1
#self.Debug(f"{self.Time} ----- Current Signal - {self.bull}")
def VIX_check(self):
if self.rank > self.IVlvl and self.rank < self.Ivlvl_High:
self.volatility = 1
elif self.rank > self.Ivlvl_High:
self.volatility = 2
else: self.volatility = 0
def VIX(self):
VIXhistory = self.History(CBOE, self.vix, 90, Resolution.Daily)
# (Current - Min) / (Max - Min)
self.rank = ((self.Securities[self.vix].Price - min(VIXhistory["low"])) / (max(VIXhistory["high"]) - min(VIXhistory["low"])))
def CheckDTECalls(self):
option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option and x.Key.ID.OptionRight == OptionRight.Call]
if option_invested:
for i, symbol in enumerate(option_invested):
if (self.Time + timedelta(300) > option_invested[i].ID.Date):
self.Liquidate(option_invested[i], tag = "Sold due to Expiration")
#self.Debug (f"| {self.Time} [+]--- Liquidate condition 1 ----- @ {str(self.Portfolio[option_invested[i]].Symbol.Value)} || Stock @ {str(self.Securities[option_invested[i].Underlying.Value].Price)}|| Profit/Loss @ {str(self.Portfolio[option_invested[i]].LastTradeProfit)}")
def DCA_Bear(self):
option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option and x.Key.ID.OptionRight == OptionRight.Call]
if option_invested and self.bull == 0:
for symbol, option_symbol in self.option_symbol.items():
invested1 = [option for option in option_invested if option.Underlying == symbol.Value]
for i, symbol in enumerate(invested1):
if self.Portfolio[invested1[i]].Quantity > 1: continue
option_buy = self.AddOptionContract(invested1[i], Resolution.Minute)
if (self.Portfolio[invested1[i]].AveragePrice - option_buy.AskPrice) / (self.Portfolio[invested1[i]].AveragePrice) > 0.4:
self.ticket_dca_1 = self.Buy(option_buy.Symbol, 1)
def customSecurityInitializer(self, security):
bar = self.GetLastKnownPrice(security)
security.SetMarketPrice(bar)
def OnData(self, slice: Slice):
if self.IsWarmingUp: return
if not all ([roc.IsReady for symbol, roc in self.roc.items()]):
return
if self.Time.hour < 10: return
for symbol, option_symbol in self.option_symbol.items():
option_invested1 = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option and x.Key.ID.OptionRight == OptionRight.Call]
option_invested2 = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option and x.Key.ID.OptionRight == OptionRight.Put]
#if self.DCH_previous_Up[symbol] is not None and self.DCH_previous_Down[symbol] is not None:
#self.price = self.Securities[symbol].Close
#self.high = self.Securities[symbol].High
#self.low = self.Securities[symbol].Low
invested1 = [option for option in option_invested1 if option.Underlying == symbol.Value]
if len(invested1) > 0:
#if self.price < self.DCH_previous_Down[symbol]:
#for i, symbol in enumerate(invested1):
#self.Liquidate(invested1[i], tag = "sold due to lower lows")
#self.Debug (f"| {self.Time} [-]--- Liquidate Condition 2 ---- @ {str(self.Portfolio[invested1[i]].Symbol.Value)} || Stock @ {str(self.Securities[invested1[i].Underlying.Value].Price)}|| Profit/Loss @ {str(self.Portfolio[invested1[i]].LastTradeProfit)}")
#continue
continue
#invested2 = [option for option in option_invested2 if option.Underlying == self.Securities[symbol].Symbol.Value]
#if len(invested2) > 0:
#continue
#if self.volatility == 1:
#self.Debug(f"{self.Time} --- High VIX")
#return
#if self.volatility == 2:
#security = self.Securities[symbol]
#self.buyputs(security)
#self.Debug(f" {self.Time} --- Reset")
#return
#invested2 = [option for option in option_invested2 if option.Underlying == self.Securities[symbol].Symbol.Value]
#if len(invested2) > 0: return
if self.bull == 1:
#if ((((self.price - self.DCH_previous_Middle[symbol]) / (self.DCH_previous_Middle[symbol])) > self.threshold) and (self.roc[symbol].Current.Value > self.threshold)):
#if (((self.price - self.DCH_previous_Middle[symbol]) / (self.DCH_previous_Middle[symbol])) < -(self.threshold) or (self.price < self.DCH_previous_Down[symbol])) and (self.std_log_roc[symbol].Current.Value < self.std_log_roc_MIN[symbol].Current.Value) and (self.roc[symbol].Current.Value < -(self.threshold)):
#if ((self.price < self.DCH_previous_Down[symbol]) and (self.roc[symbol].Current.Value > self.threshold) and (self.aps[symbol].Current.Value < -2)) or ((((self.price - self.DCH_previous_Middle[symbol]) / (self.DCH_previous_Middle[symbol])) < -(self.threshold)) and (self.roc[symbol].Current.Value > self.threshold) and (self.aps[symbol].Current.Value < -2)):
chain = slice.OptionChains.get(self.option_symbol[symbol], None)
if not chain: return
# Get the furthest expiration date of the contracts
expiry = sorted(chain, key = lambda x: x.Expiry, reverse=True)[0].Expiry
# Select the call Option contracts with the furthest expiry
calls = [i for i in chain if i.Expiry == expiry and i.Right == OptionRight.Call and i.Strike > i.UnderlyingLastPrice and i.ImpliedVolatility < 0.2]
if len(calls) == 0: return
# Select the ITM and OTM contract strike prices from the remaining contracts
#call_strikes = sorted([x.Strike for x in calls])
itm_strike_call = calls[0].Symbol
#option_strategy = OptionStrategies.BullCallSpread(self.option_symbol[symbol], itm_strike, otm_strike, expiry)
self.ticket_initial = self.Buy(itm_strike_call, 1)
#self.buyoptions(security)
#self.Debug(f"{self.Time} --- stock {symbol} --- Price {self.price} --- DCH UpperValue -- {self.DCH_previous_Up[symbol]} -- DCH MiddleValue {self.DCH_previous_Middle} ---- ROC-- {self.roc[symbol].Current.Value} ---- Volatility {self.std_log_roc[symbol].Current.Value} ----- VMIN {self.std_log_roc_MIN[symbol].Current.Value}")
#self.SetHoldings(symbol, 0.1)
#self.DCH_previous_Up[symbol] = self.dch[symbol].UpperBand.Current.Value
#self.DCH_previous_Down[symbol] = self.dch[symbol].LowerBand.Current.Value
#self.DCH_previous_Up[symbol] = self.dch[symbol].UpperBand.Current.Value
#self.DCH_previous_Down[symbol] = self.dch[symbol].LowerBand.Current.Value
#self.DCH_previous_Middle[symbol] = self.dch[symbol].Current.Value
def OnOrderEvent(self, orderEvent):
order = self.Transactions.GetOrderById(orderEvent.OrderId)
if order.Status == OrderStatus.Filled and order.Direction == OrderDirection.Buy and order.SecurityType == SecurityType.Option:
if (self.Portfolio[order.Symbol].Quantity) > 1:
response = self.ticket.Cancel("Cancelled Trade")
if response.IsSuccess:
self.Debug("Order successfully cancelled")
quantity = self.Portfolio[order.Symbol].Quantity
fill_price = self.Portfolio[order.Symbol].AveragePrice
limit_price = fill_price * 1.5
self.ticket_1 = self.LimitOrder(order.Symbol, -quantity, limit_price, tag = "Sold at 50% Profit")
self.Debug(f" {self.Time} -- -- Bought & Update -- {order.Symbol} Average Price {self.Portfolio[order.Symbol].AveragePrice} // Contract Price at {self.Securities[order.Symbol].AskPrice} // {self.Securities[order.Symbol].Underlying.Price} ---- Limit Price {limit_price} ")
elif (self.Portfolio[order.Symbol].Quantity) == 1:
quantity = self.Portfolio[order.Symbol].Quantity
fill_price = self.Portfolio[order.Symbol].AveragePrice
limit_price = fill_price * 1.5
self.ticket = self.LimitOrder(order.Symbol, -quantity, limit_price, tag = "Sold at 50% Profit")
self.Debug(f" {self.Time} -- -- Bought {order.Symbol} for {self.Portfolio[order.Symbol].AveragePrice} // // {self.Securities[order.Symbol].Underlying.Price} ----- Limit Price {limit_price}")
#if order.Status == OrderStatus.Filled and order.Direction == OrderDirection.Buy and order.SecurityType == SecurityType.Equity:
#self.Debug(f" {self.Time} -- -- Bought {order.Symbol} for {self.Portfolio[order.Symbol].AveragePrice} // ")
#if order.Status == OrderStatus.Filled and order.Direction == OrderDirection.Buy and self.ticket_dca_1.OrderId:
#updateSettings = UpdateOrderFields()
#updateSettings.LimitPrice = self.Portfolio[order.Symbol].AveragePrice * 1.3
#updateSettings.Quantity = -(self.Portfolio[order.Symbol].Quantity)
#updateSettings.Tag = "Limit Price Updated for DCA Trade - Sold at new 30% Profit"
#response = ticket.Update(updateSettings)
#if response.IsSuccess:
#self.Debug("Order updated successfully")
if order.Status == OrderStatus.Filled and order.Direction == OrderDirection.Sell and order.SecurityType == SecurityType.Option and not order.Type == OrderType.Limit:
self.Debug(f" {self.Time} -- -- Sold {order.Symbol} for {self.Portfolio[order.Symbol].Price} // stock price // {self.Securities[order.Symbol].Underlying.Price} ---- Profit/Loss {self.Portfolio[order.Symbol].LastTradeProfit} ")
#if order.Status == OrderStatus.Filled and order.Direction == OrderDirection.Sell and order.SecurityType == SecurityType.Equity:
#self.Debug(f" {self.Time} -- -- Sold {order.Symbol} for {self.Portfolio[order.Symbol].Price} // stock price // {self.Securities[order.Symbol].Price} ---- Profit/Loss {self.Portfolio[order.Symbol].LastTradeProfit} ")
if order.Status == OrderStatus.Filled and order.Type == OrderType.Limit and (order.Direction == OrderDirection.Sell):
self.Debug(f" {self.Time} -- -- Closed{order.Symbol} for {self.Portfolio[order.Symbol].Price} // stock price // {self.Securities[order.Symbol].Underlying.Price} ---- Profit/Loss {self.Portfolio[order.Symbol].LastTradeProfit} ")
#options_quantity = self.Portfolio[order.Symbol].Quantity
#if options_quantity > 0:
#fill_price = self.Portfolio[order.Symbol].AveragePrice
#limit_price2 = fill_price * 1.5
#stop_price = fill_price * 0.8
#self.LimitOrder(order.Symbol, -options_quantity, limit_price2, tag = "Sold at 50% Profit")
#self.TrailingStopOrder(order.Symbol, -options_quantity, 0.20, True)
#self.Debug(f" {self.Time} -- -- Trailing Updated {order.Symbol} quantity {self.Portfolio[order.Symbol].Quantity} // Limit {limit_price2} stock price // {self.Securities[order.Symbol].Underlying.Price} ")
if order.Type == OrderType.OptionExercise:
self.Liquidate(order.Symbol)
def buyoptions(self, security):
security.SetDataNormalizationMode(DataNormalizationMode.Raw)
# Filter for out of the money call options and expiring at least 60 days
option_chain = self.OptionChainProvider.GetOptionContractList(security.Symbol, self.Time)
call_options = [option for option in option_chain if option.ID.OptionRight == 0]
otm_call_options = sorted([option for option in call_options if ((option.ID.StrikePrice - security.Close)/(security.Close) > 0.05) and (option.ID.Date - self.Time).days >= 450])
if len(otm_call_options) > 0:
self.call = otm_call_options[0]
#subscribe to the option contract, so that we can buy it
option_buy = self.AddOptionContract(self.call, Resolution.Minute)
option_buy.PriceModel = OptionPriceModels.CrankNicolsonFD()
if option_buy.AskPrice == 0:
self.Debug("No prices")
return
#if option_buy.AskPrice > option_price_BS:
#self.Log((f"| Option too Expensive! {self.Time})"))
#return
#if self.Portfolio[option_buy.Symbol].Invested:
#return
#quantity = 10000 / option_buy.AskPrice
#quantity1 = math.floor(quantity / 100)
#if quantity1 < 1: return
self.SetHoldings(option_buy.Symbol, 0.3)
#self.MarketOrder(option_buy.Symbol, quantity1)
#self.MarketOrder(option_buy.Symbol, 1)
def buyputs(self, security):
security.SetDataNormalizationMode(DataNormalizationMode.Raw)
option_chain = self.OptionChainProvider.GetOptionContractList(security.Symbol, self.Time)
put_options = [option for option in option_chain if option.ID.OptionRight == 1]
# Filter for out of the money call options and expiring at least 60 days
otm_put_options = sorted([option for option in put_options if (option.ID.Date - self.Time).days <= 60])
otm_expiry = sorted([option for option in otm_put_options if ((option.ID.StrikePrice - security.Close)/(security.Close) < -0.05)], reverse = True)
if len(otm_expiry) > 0:
self.put = otm_expiry[0]
#subscribe to the option contract, so that we can buy it
option_buyput = self.AddOptionContract(self.put, Resolution.Minute)
if self.Portfolio[option_buyput.Symbol].Invested:
return
if option_buyput.AskPrice == 0:
self.Debug("No prices")
return
self.SetHoldings(option_buyput.Symbol, 0.3)
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)
def Plotting(self):
self.Plot('VIX', 'Rank', self.rank)