| 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