Overall Statistics
Total Orders
25
Average Win
6.52%
Average Loss
-4.40%
Compounding Annual Return
16.908%
Drawdown
27.200%
Expectancy
0.654
Start Equity
10000000
End Equity
16617281.76
Net Profit
66.173%
Sharpe Ratio
0.775
Sortino Ratio
0.721
Probabilistic Sharpe Ratio
44.624%
Loss Rate
33%
Win Rate
67%
Profit-Loss Ratio
1.48
Alpha
0.056
Beta
0.584
Annual Standard Deviation
0.124
Annual Variance
0.015
Information Ratio
0.248
Tracking Error
0.109
Treynor Ratio
0.164
Total Fees
$3925.01
Estimated Strategy Capacity
$12000000.00
Lowest Capacity Asset
VXXB WRBPJAJZ2Q91
Portfolio Turnover
0.61%
# region imports
from AlgorithmImports import *
from QuantConnect.Securities.Option import OptionPriceModels
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy import poly1d, signal
from datetime import datetime, timedelta
import pytz as pytz
from sklearn.metrics import r2_score, mean_absolute_error
from hmmlearn import hmm
from scipy.signal import stft, gaussian, butter, filtfilt, detrend, medfilt, savgol_filter
from scipy.fft import fft
from scipy.interpolate import interp1d
from numpy import quantile
from inspect import signature
from statsmodels.nonparametric.smoothers_lowess import lowess
# endregion

class BasicTemplateAlgorithm(QCAlgorithm):
 
    def Initialize(self):
        # Parameters to change
        self.SST_resolution = 'Hour'

        if self.SST_resolution == 'Hour':
            self.tick=1
        elif self.SST_resolution == 'Minute':
            self.tick=60
        elif self.SST_resolution == 'Second':
            self.tick=60*60

       
        self.SST_lookback = 6*self.tick*160 ## 6 month trading

        self.SetStartDate(2021,1, 1)
        self.SetEndDate(2024, 4,1)

        #4. Set Initial Cash and Warmup Data
        self.SetCash(10000000)
        self.SetWarmup(300)

        #5. Assign Symbols for SST
        self.sst_symbols =[]
        if self.SST_resolution == 'Hour':
            self.AddEquity('SPY', Resolution.Hour)
        elif self.SST_resolution == 'Minute':
            self.AddEquity('SPY', Resolution.Minute)
        elif self.SST_resolution == 'Second':
            self.AddEquity('SPY', Resolution.Second)
        self.sst_symbols.append(self.Symbol('SPY'))

        if self.SST_resolution == 'Hour':
            self.AddEquity('VXX', Resolution.Hour)
        elif self.SST_resolution == 'Minute':
            self.AddEquity('VXX', Resolution.Minute)
        elif self.SST_resolution == 'Second':
            self.AddEquity('VXX', Resolution.Second)


        #self.sst_symbols.append(self.Symbol('VXX'))
        self.sym = self.sst_symbols[0]
        self.lookback = self.SST_lookback
        self.invested_bool = 0

        # If V.X.X. then
        self.s = "SHORT "
        self.l = "LONG "


    def OnData(self, slice: Slice):
        #1. Return if warming up
        if self.IsWarmingUp:
            return

        if True:
            self.df = self.History(self.sst_symbols, self.lookback)
            self.dg = self.df["close"].unstack(level=0)
            ## realized volatility
            ## 160 trading hours per week.
            RV = self.dg.pct_change().rolling(self.tick*160).std() * (252**0.5)
            RV = RV.to_numpy()
            RV = RV[~np.isnan(RV)]
            RV = RV.reshape((-1,1))
            ## 6 hour minute medium
            short_medium= np.quantile(RV[-self.tick*6:], 0.5)
            ## 1 week medium
            Q_20=np.quantile(RV[-self.tick*160:],0.2)
            Q_30=np.quantile(RV[-self.tick*160:],0.3)
            Q_40=np.quantile(RV[-self.tick*160:],0.4)
            Q_50=np.quantile(RV[-self.tick*160:],0.5)
            Q_60=np.quantile(RV[-self.tick*160:],0.6)
            Q_70=np.quantile(RV[-self.tick*160:],0.7)
            Q_80=np.quantile(RV[-self.tick*160:],0.8)

            self.Plot('Quantiles', '30', Q_30)
            self.Plot('Quantiles', '40', Q_40)
            self.Plot('Quantiles', '50', Q_50)
            self.Plot('Quantiles', '60', Q_60)
            self.Plot('Quantiles', '70', Q_70)
            self.Plot('Quantiles', 'Short Median', short_medium)

            # Trend following
            if not self.invested_bool:
                if short_medium < Q_30:
                    # Trend following
                    self.SetHoldings("VXX", -0.3)
                    self.invested_bool = 1
                    self.temp = 0
                    self.priceVXX = self.Portfolio['VXX'].Price
                    self.quant = Q_40
                    self.Debug(self.s + 'VXX at ' + str(self.Time) + " (SST: short avg low FOLLOW TREND)")
                elif short_medium > Q_70:
                    self.SetHoldings("VXX", -0.3)
                    self.invested_bool = 1
                    self.temp = 2
                    self.priceVXX = self.Portfolio['VXX'].Price
                    self.quant = Q_60
                    self.Debug(self.l + 'VXX at ' + str(self.Time) + " (SST: short avg high FOLLOW TREND)")   
            else:
                if self.temp == 0 and short_medium > self.quant:
                    self.Liquidate()
                    self.invested_bool = 0
                    self.Debug('Liquidate ' + 'VXX at ' + str(self.Time))
                elif self.temp == 2 and short_medium < self.quant:
                    self.Liquidate()
                    self.invested_bool = 0
                    self.Debug('Liquidate ' + 'VXX at ' + str(self.Time))       


        #2. Check stop loss criterion every hour of every day
        # if self.invested_bool:
        #     if (self.temp == 0 or self.temp == 3) and (self.Portfolio['VXX'].Price < self.priceVXX*0.9):
        #         self.Liquidate()
        #         self.invested_bool = 0
        #         self.Debug ('Stop Loss ' + self.l+ 'at ' + str(self.Time))
        #     elif (self.temp == 1 or self.temp == 2) and (self.Portfolio['VXX'].Price > self.priceVXX*1.1):
        #         self.Liquidate()
        #         self.invested_bool = 0
        #         self.Debug ('Stop Loss ' + self.s+ 'at ' + str(self.Time))