| Overall Statistics |
|
Total Trades 1593 Average Win 0.01% Average Loss -0.04% Compounding Annual Return 0.875% Drawdown 0.700% Expectancy 0.108 Net Profit 3.461% Sharpe Ratio 0.862 Probabilistic Sharpe Ratio 36.502% Loss Rate 15% Win Rate 85% Profit-Loss Ratio 0.30 Alpha 0.006 Beta 0.003 Annual Standard Deviation 0.007 Annual Variance 0 Information Ratio -0.719 Tracking Error 0.173 Treynor Ratio 2.141 Total Fees $945.00 Estimated Strategy Capacity $0 Lowest Capacity Asset MSFT 31S6IM58E6LZA|MSFT R735QTJ8XC9X |
from arch import arch_model
import numpy as np
import pandas as pd
from sklearn.model_selection import KFold
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
from sklearn.metrics import mean_squared_error
import pickle
class KNNVolModel(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2018, 1, 1) # Set Start Date
#self.SetEndDate(2018, 3, 1) # Set Start Date
self.SetCash(1000000) # Set Strategy Cash
# serializedModel = bytes( self.ObjectStore.ReadBytes("VolModel") )
# self.volModel = pickle.loads(serializedModel)
self.symbolDataBySymbol = {}
self.tickers = ["MSFT", "WFC", "HD"]
self.spy = self.AddEquity("SPY", Resolution.Minute).Symbol
for ticker in self.tickers:
self.AddEquity(ticker, Resolution.Minute)
self.Schedule.On(self.DateRules.EveryDay("SPY"),
self.TimeRules.AfterMarketOpen(self.spy, 2),
self.EveryDayAfterMarketOpen)
self.AutomaticIndicatorWarmup = True
self.slice = None
self.lastYear = self.Time.year
def CheckYear(self):
if self.lastYear == self.Time.year:
return
self.lastYear = self.Time.year
for symbol, symbolData in self.symbolDataBySymbol.items():
symbolData.TrainModel()
def OnData(self, slice):
self.slice = slice
self.CheckYear()
for symbol, symbolData in self.symbolDataBySymbol.items():
symbolData.CheckExercise()
def EveryDayAfterMarketOpen(self):
for symbol, symbolData in self.symbolDataBySymbol.items():
symbolData.EveryDay()
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
if security.Symbol.SecurityType != SecurityType.Equity: continue
symbol = security.Symbol
if symbol == self.spy: continue
option = self.AddOption(symbol)
option.SetFilter(self.UniverseFunc)
atr = self.ATR(symbol, 14, MovingAverageType.Simple, Resolution.Daily)
history = self.History(symbol, 100, Resolution.Daily)
history = history.loc[symbol]
symbolData = SymbolData(symbol, option, atr, history, self)
self.Debug(f"Created symbolData for {symbol.Value}")
self.symbolDataBySymbol[symbol] = symbolData
def UniverseFunc(self, universe):
return universe.IncludeWeeklys().Strikes(-15, 15).Expiration(timedelta(10), timedelta(30))
class SymbolData:
def __init__(self, symbol, option, atr, history, algo):
self.Symbol = symbol
self.Option = option
self.Atr = atr
self.history_ = history
self.algo = algo
self.previousPred = -1
self.investedOptions = False
self.volModel = None
self.TrainModel()
def volpred(self, returns,h):
am = arch_model(returns, p=1,o=1, q=1, vol='Garch', dist='skewt')
res = am.fit(disp='off')
forecasts=res.forecast(horizon=h, method='simulation')
return forecasts.variance.iloc[-1]
def EveryDay(self):
df = self.RetrieveVolatilityMetrics()
if self.CheckDataIntegrity(df) == True:
self.algo.Debug(f"Integrity failed for {self.Symbol.Value}")
return
prediction = self.volModel.predict(df)
self.EnterPositions(prediction, df)
self.previousPred = prediction
def CheckExercise(self):
if self.algo.Portfolio[self.Symbol].Quantity != 0:
#self.algo.Debug(f"Liquidating {self.Symbol.Value}")
self.algo.Liquidate(self.Symbol)
self.investedOptions = False
def CheckDataIntegrity(self, df):
if pd.isnull(df["GKHV"][0]) == True or pd.isnull(df["GARCH"][0]) == True or pd.isnull(df["ATR"][0]) == True:
#self.algo.Debug(f"NaN value detected {self.Symbol.Value}")
self.history_ = self.algo.History(self.Symbol, 100, Resolution.Daily)
self.history_ = self.history_.loc[self.Symbol]
return True
return False
def RetrieveVolatilityMetrics(self):
yesterday = self.algo.History(self.Symbol, 1, Resolution.Daily)
yesterday = yesterday.loc[self.Symbol]
self.history_.append(yesterday)
self.history_ = self.history_.iloc[1: , :]
returns = 100 * self.history_["close"].pct_change().dropna()
gkhv = np.sqrt(252/10 * pd.DataFrame.rolling(0.5 * np.log(self.history_.loc[:, 'high'] / self.history_.loc[:, 'low']) ** 2 -
(2 * np.log(2) - 1) *
np.log(self.history_.loc[:, 'close'] / self.history_.loc[:, 'open']) ** 2, window=10).sum())
self.gkhv = gkhv.pct_change(3).iloc[-1]
df = pd.DataFrame()
df["GKHV"] = [abs(self.gkhv)]
df["GARCH"] = [self.volpred(returns, 10).mean()]
df["ATR"] = [self.Atr.Current.Value]
return df
def CheckOptionPositions(self):
option_invested = [x.Key for x in self.algo.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]
for option in option_invested:
if self.Symbol == option.ID.Underlying.Symbol:
self.investedOptions = True
return
self.investedOptions = False
def EnterPositions(self, prediction, df):
self.CheckOptionPositions()
if prediction == 1 and self.previousPred == 0:
self.algo.Liquidate(self.Symbol)
self.investedOptions = False
if prediction == 1 and self.investedOptions == False:
#self.algo.Debug(df)
self.LongStraddle(self.algo.slice)
#self.algo.Debug(f"Long {prediction} for {self.Symbol.Value}")
elif prediction == 0 and self.investedOptions == False:
#self.algo.Debug(df)
self.ShortStraddle(self.algo.slice)
#self.algo.Debug(f"Short {prediction} for {self.Symbol.Value}")
# def CheckProfit(self):
# if self.Portfolio.TotalUnrealizedProfit >= (self.Portfolio.TotalUnrealizedProfit):
def LongStraddle(self, slice):
# If there is undelying assets in portfolio at expiration, liquidate the stocks in order to roll into new contracts
self.CheckExercise()
if self.algo.Time.hour != 0 and self.algo.Time.minute != 0:
for i in slice.OptionChains:
if self.Symbol != i.Value.Underlying.Symbol: continue
chain = i.Value
contract_list = [x for x in chain]
# if there is no optionchain or no contracts in this optionchain, pass the instance
if (slice.OptionChains.Count == 0) or (len(contract_list) == 0):
return
# sorted the optionchain by expiration date and choose the furthest date
expiry = sorted(chain,key = lambda x: x.Expiry)[0].Expiry
# filter the call and put options from the contracts
call = [i for i in chain if i.Expiry == expiry and i.Right == 0]
put = [i for i in chain if i.Expiry == expiry and i.Right == 1]
# sorted the contracts according to their strike prices
call_contracts = sorted(call,key = lambda x: x.Strike)
put_contracts = sorted(put,key = lambda x: x.Strike)
if len(call_contracts) == 0 or len(put_contracts) == 0 : return
atm_call = sorted(call_contracts,key = lambda x: abs(chain.Underlying.Price - x.Strike))[0]
atm_put = sorted(put_contracts,key = lambda x: abs(chain.Underlying.Price - x.Strike))[0]
self.algo.Buy(atm_call.Symbol, 2)
self.algo.Buy(atm_put.Symbol, 2)
self.investedOptions = True
return
self.algo.Debug(f"Failed Long for {self.Symbol.Value}")
def ShortStraddle(self, slice):
# If there is undelying assets in portfolio at expiration, liquidate the stocks in order to roll into new contracts
self.CheckExercise()
if self.algo.Time.hour != 0 and self.algo.Time.minute != 0:
for i in slice.OptionChains:
if self.Symbol != i.Value.Underlying.Symbol: continue
chain = i.Value
contract_list = [x for x in chain]
# if there is no optionchain or no contracts in this optionchain, pass the instance
if (slice.OptionChains.Count == 0) or (len(contract_list) == 0):
return
# sorted the optionchain by expiration date and choose the furthest date
expiry = sorted(chain,key = lambda x: x.Expiry)[0].Expiry
# filter the call and put options from the contracts
call = [i for i in chain if i.Expiry == expiry and i.Right == 0]
put = [i for i in chain if i.Expiry == expiry and i.Right == 1]
# sorted the contracts according to their strike prices
call_contracts = sorted(call,key = lambda x: x.Strike)
put_contracts = sorted(put,key = lambda x: x.Strike)
if len(call_contracts) == 0 or len(put_contracts) == 0 : return
if len(call_contracts) < 14:
return
atm_call = sorted(call_contracts,key = lambda x: x.Strike)[len(call_contracts)-1]
atm_put = sorted(put_contracts,key = lambda x: x.Strike)[0]
self.algo.Sell(atm_call.Symbol, 4)
self.algo.Sell(atm_put.Symbol, 4)
#self.algo.Debug(f"{atm_call.Strike} {atm_put.Strike} {self.algo.Securities[self.Symbol].Price}")
self.investedOptions = True
return
self.algo.Debug(f"Failed Short for {self.Symbol.Value}")
def TrainModel(self):
history = self.algo.History(self.Symbol, 1000, Resolution.Daily)
history = history.loc[self.Symbol]
atr = AverageTrueRange(14, MovingAverageType.Simple)
atr_arr = []
for tuple in history.itertuples():
bar = TradeBar(tuple.Index, self.Symbol, tuple.open, tuple.high, tuple.low, tuple.close, tuple.volume)
atr.Update(bar)
if not atr.IsReady:
atr_arr.append(np.nan)
continue
atr_arr.append(atr.Current.Value)
history["ATR"] = atr_arr
history["pct_change"] = abs(100 * history["close"].pct_change(10).dropna())
Garch = [np.nan for x in range(0, 100)]
for index in range(100, len(history.index)):
returns = 100 * history["close"].iloc[index-100:index].pct_change().dropna()
garchPred = self.volpred(returns, 10)
Garch.append(garchPred.mean())
history["Garch_Pred"] = Garch
gkhv = np.sqrt(252/10 * pd.DataFrame.rolling(0.5 * np.log(history.loc[:, 'high'] / history.loc[:, 'low']) ** 2 -
(2 * np.log(2) - 1) *
np.log(history.loc[:, 'close'] / history.loc[:, 'open']) ** 2, window=10).sum())
history["GKHV"] = abs(gkhv.pct_change(3))# gkhv.iloc[-1]#
history = history.dropna()
history["pct_delayed"] = history["pct_change"].shift(-10).dropna()
history.loc[abs(history['pct_delayed']) > (history["pct_change"].mean()*1.5), 'Strong_Change'] = 1
history.loc[abs(history['pct_delayed']) <= (history["pct_change"].mean()*1.5), 'Strong_Change'] = 0
history = history.dropna()
y = np.array(history["Strong_Change"])
X = history[["GKHV", "Garch_Pred", "ATR"]]
# split_percentage = 0.8
# split = int(split_percentage*len(history.index))
# X_train = X[:split]
# y_train = y[:split]
# X_test = X[split:]
# y_test = y[split:]
cls = KNeighborsClassifier(n_neighbors=10)
cls = cls.fit(X, y)
self.volModel = cls