Overall Statistics |
Total Trades 116 Average Win 24.72% Average Loss -3.70% Compounding Annual Return 59.166% Drawdown 38.200% Expectancy 1.785 Net Profit 1676.792% Sharpe Ratio 1.273 Loss Rate 64% Win Rate 36% Profit-Loss Ratio 6.69 Alpha 0.449 Beta 0.251 Annual Standard Deviation 0.444 Annual Variance 0.197 Information Ratio 0.153 Tracking Error 0.671 Treynor Ratio 2.254 Total Fees $1399.54 |
from clr import AddReference AddReference("System") AddReference("QuantConnect.Algorithm") AddReference("QuantConnect.Indicators") AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Data import * from QuantConnect.Algorithm import * from QuantConnect.Indicators import * from QuantConnect.Python import PythonQuandl # from QuantConnect.Data.Custom import Quandl from datetime import timedelta import pandas as pd class GaboVolatilityZscoreCubedWithTimer(QCAlgorithm): def Initialize(self): # Backtest initialization self.SetStartDate(2012, 1, 1) self.SetEndDate(2018, 3, 11) self.SetCash(15000) # Constant definitions self.ASSET_UNDERLYING = 'SPY' self.ASSET_TRADE_LONG = 'SVXY' self.ASSET_TRADE_SHORT = 'TZA' self.BENCHMARK = 'SVXY' self.INITIAL_TIME = 20 self.MIN_TIME = 0 self.MAX_TIME = 40 self.TIME_INCREASE = 1 self.TIME_DECREASE = .5 self.DAYS_DMA_LOW = 50 self.DAYS_DMA_HIGH = 200 self.DAYS_DMA_SHORT_TERM = 3 self.PCT_INVEST_SHORT = .99 self.PCT_INVEST_LONG = .99 self.OPEN_TRADNG_HOURS = [30, 60, 180, 210, 360] self.CLOSE_TRADING_HOURS = [15] self.ZSCORE_ADJ_SELL_LONG = 0 self.ZSCORE_ADJ_SELL_SHORT = -0.05 self.ZSCORE_ADJ_BUY_LONG = -.01 self.ZSCORE_EXP_LONG_EXP = 3 self.ZSCORE_EXP_LONG_MULT = 1.2 self.ZSCORE_EXP_LONG_TIMER_MULT = -1/60 self.ZSCORE_EXP_LONG_STDDEV_MULT = 1/20 self.ZSCORE_EXP_SHORT_EXP = 3 self.ZSCORE_EXP_SHORT_MULT = .9 self.ZSCORE_EXP_SHORT_TIMER_MULT = -1/45 self.ZSCORE_EXP_SHORT_STDDEV_MULT = -1/20 self._daily_history = {} # Initialization process # Add the equities to universe self._asset_underlying = self.AddEquity(self.ASSET_UNDERLYING, Resolution.Minute) self._asset_underlying.SetDataNormalizationMode(DataNormalizationMode.SplitAdjusted) self._asset_trade_long = self.AddEquity(self.ASSET_TRADE_LONG, Resolution.Minute) self._asset_trade_short = self.AddEquity(self.ASSET_TRADE_SHORT, Resolution.Minute) # Work with split adjustment (not dividend adjustment) # self._asset_underlying.SetDataNormalizationMode(DataNormalizationMode.SplitAdjusted) # self._asset_trade_long.SetDataNormalizationMode(DataNormalizationMode.SplitAdjusted) # self._asset_trade_short.SetDataNormalizationMode(DataNormalizationMode.SplitAdjusted) # variable initialization self._time = self.INITIAL_TIME self.SetBenchmark(self.BENCHMARK) oneDayConsolidator = self.ResolveConsolidator(self.ASSET_UNDERLYING, Resolution.Daily) oneDayConsolidator.DataConsolidated += self.daily_history_consolidator self.SubscriptionManager.AddConsolidator(self.ASSET_UNDERLYING, oneDayConsolidator) # Store the underlying history prices in memory, to avoid waiting the # warmup period underlying_id = str(self._asset_underlying.Symbol.ID) self._daily_history[underlying_id] = self.History( [self.ASSET_UNDERLYING], self.DAYS_DMA_HIGH, Resolution.Daily ) # Set trading events for minutes in self.OPEN_TRADNG_HOURS: self.Schedule.On( self.DateRules.EveryDay(self.ASSET_UNDERLYING), self.TimeRules.AfterMarketOpen(self.ASSET_UNDERLYING, minutes), Action(self._trade) ) for minutes in self.CLOSE_TRADING_HOURS: self.Schedule.On( self.DateRules.EveryDay(self.ASSET_UNDERLYING), self.TimeRules.BeforeMarketClose(self.ASSET_UNDERLYING, minutes), Action(self._trade) ) # Timer adjustment self.Schedule.On( self.DateRules.EveryDay(self.ASSET_UNDERLYING), self.TimeRules.BeforeMarketClose(self.ASSET_UNDERLYING, 1), Action(self._timer_management) ) def daily_history_consolidator(self, sender, bar): # ticker = bar.Symbol.Value ticker_id = str(bar.Symbol.ID) current_quotes_df = pd.DataFrame( { 'open': [float(bar.Open)], 'high': [float(bar.High)], 'low': [float(bar.Low)], 'close': [float(bar.Close)], 'volume': [int(bar.Volume)] }, index=[pd.to_datetime(self.Time)] ) if ticker_id in self._daily_history: self._daily_history[ticker_id] = self._daily_history[ticker_id] \ .append(current_quotes_df) else: self._daily_history[ticker_id] = current_quotes_df def _trade(self): underlying_id = str(self._asset_underlying.Symbol.ID) if underlying_id not in self._daily_history: # No history enough return history_size = self._daily_history[underlying_id].shape[0] if history_size < self.DAYS_DMA_HIGH: # No history enough return # Get all historic quotes underlying_price = float(self.Securities[self.ASSET_UNDERLYING].Close) history_low = self._daily_history[underlying_id].iloc[-self.DAYS_DMA_LOW:] history_high = self._daily_history[underlying_id].iloc[-self.DAYS_DMA_HIGH:] history_short_term = self._daily_history[underlying_id].iloc[-self.DAYS_DMA_SHORT_TERM:] # Compute all the z-scores sma_low = history_low['close'].mean() sma_high = history_high['close'].mean() sma_short_term = history_short_term['close'].mean() std_low = history_low['close'].std() std_high = history_high['close'].std() z_score = (sma_low - sma_high) / std_high z_score_current = (underlying_price - sma_high) / std_high z_score_short_term = (sma_short_term - sma_high) / std_high z_score_exp_long = ( z_score ** self.ZSCORE_EXP_LONG_EXP * self.ZSCORE_EXP_LONG_MULT + self._time * self.ZSCORE_EXP_LONG_TIMER_MULT + std_low * self.ZSCORE_EXP_LONG_STDDEV_MULT ) z_score_exp_short = ( z_score ** self.ZSCORE_EXP_SHORT_EXP * self.ZSCORE_EXP_SHORT_MULT + self._time * self.ZSCORE_EXP_SHORT_TIMER_MULT + std_low * self.ZSCORE_EXP_SHORT_STDDEV_MULT ) # self.Log('date: %s - zscore: %.02f - current zscore: %.2f' % ( # str(self.Time), # z_score, # z_score_current # ) # ) # self.Log('underlying price: %.04f' % underlying_price) if not self.Transactions.GetOpenOrders(): cash_pct = self.Portfolio.Cash / self.Portfolio.TotalPortfolioValue if z_score_current + self.ZSCORE_ADJ_SELL_LONG <= z_score_exp_long: self.SetHoldings(self.ASSET_TRADE_LONG, 0) if z_score_current + self.ZSCORE_ADJ_SELL_SHORT > z_score_exp_short: self.SetHoldings(self.ASSET_TRADE_SHORT, 0) if z_score_current < z_score_exp_short and z_score_current < z_score_exp_long: if cash_pct > 0.05 * self.PCT_INVEST_SHORT: self.SetHoldings(self.ASSET_TRADE_SHORT, self.PCT_INVEST_SHORT) # if z_score_current < z_score_short_term - 0.01 and z_score_current > z_score_exp_long: if (z_score_current < z_score_short_term + self.ZSCORE_ADJ_BUY_LONG and z_score_current > z_score_exp_long): if cash_pct > 0.05 * self.PCT_INVEST_LONG: self.SetHoldings(self.ASSET_TRADE_LONG, self.PCT_INVEST_LONG) # self.Log('zs current: %.04f - zs short: %0.4f - zse long: %.04f' % ( # z_score_current, z_score_current, z_score_exp_long)) def _timer_management(self): cash_pct = self.Portfolio.Cash / self.Portfolio.TotalPortfolioValue if cash_pct > 0.3 or ( self.Portfolio[self.ASSET_TRADE_SHORT].Quantity > 0 and self._time < self.MAX_TIME): self._time += self.TIME_INCREASE if cash_pct < 0.1: self._time -= self.TIME_DECREASE if self._time < self.MIN_TIME: self._time = self.MIN_TIME