| Overall Statistics |
|
Total Trades 953 Average Win 1.90% Average Loss -1.34% Compounding Annual Return 16.269% Drawdown 15.400% Expectancy 0.460 Net Profit 1634.269% Sharpe Ratio 1.231 Probabilistic Sharpe Ratio 80.269% Loss Rate 40% Win Rate 60% Profit-Loss Ratio 1.42 Alpha 0.101 Beta 0.178 Annual Standard Deviation 0.093 Annual Variance 0.009 Information Ratio 0.241 Tracking Error 0.155 Treynor Ratio 0.642 Total Fees $176705.63 Estimated Strategy Capacity $82000000.00 Lowest Capacity Asset TLT SGNKIKYGE9NP |
from AlgorithmImports import *
import numpy as np
class CryingVioletZebra(QCAlgorithm):
def Initialize(self):
# set up
self.SetStartDate(2003, 2, 1)
self.SetEndDate(2022, 1, 1)
self.SetCash(1000000)
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
# parameters
self.sd_threshold = 0.004 # standard deviation threshold after which we sell
self.predictions_sd_window = 3 # window size when calculating standard deviation of predictions
self.safe_asset = "TLT" # safe asset we buy, when we sell SPY. Chouces: (None, ticker: Inverse SPY: SH, SDS, SPXU, Bonds: TLT, VFSTX , money market FX: FXE)
# self.thresold = 0.00 # ne koristimo, prvotno zamisljeno za predikcije
# self.sma_width = 1 # ne koristimo
# self.close_window_length = 7 # ne koristimo
# optimization parameters
# self.sd_threshold = float(self.GetParameter("threshold-sd"))
# self.predictions_sd_window = int(self.GetParameter("preds-window"))
# data feeds
self.UniverseSettings.Leverage = 4
PRAData.set_algo(self)
if self.LiveMode:
self.spy = self.AddEquity("SPY", Resolution.Minute).Symbol
self.symbol = self.AddData(PRAData, "PR", Resolution.Minute, TimeZones.Utc).Symbol
else:
equity = self.AddEquity("SPY", Resolution.Daily)
equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
self.spy = equity.Symbol
self.SetBenchmark(lambda dt: self.Securities[self.spy].Price)
self.symbol = self.AddData(PRAData, "PR", Resolution.Daily).Symbol
if self.safe_asset is not None:
self.AddEquity(self.safe_asset, Resolution.Daily)
# self.pra_sma = self.SMA("PR", self.sma_width, Resolution.Daily)
# init
self.pred_window = RollingWindow[float](self.predictions_sd_window)
self.std_window = RollingWindow[float](2)
self.high_month = 0
# consolidator
self.Consolidate("SPY", Calendar.Monthly, self.MonthBarHandler)
# set and warmup rolling close
# self.close_radf = RollingWindow[float](self.close_window_length)
# self.volume = RollingWindow[float](8)
# history = self.History(["SPY"], self.close_window_length, Resolution.Hour)
# for index, ohlcv in history.loc["SPY"].iterrows():
# self.close_radf.Add(ohlcv["close"])
# self.volume.Add(ohlcv["volume"])
# define margins
# self.Securities["SPY"].SetLeverage(4)
# self.Securities[self.safe_asset].SetLeverage(4)
def MonthBarHandler(self, bar):
self.high_month = bar.High
def OnData(self, data):
# extract indicator
if self.LiveMode:
# get SPY data
spy = self.Securities[self.spy]
o, h, l, c = spy.Open, spy.High, spy.Low, spy.Close
if c == 0:
self.Log("SPY close price is 0.")
return
# check if custom data is ready
if not data.ContainsKey(self.symbol):
# self.Log(f'There is no indicator data for the bar.')
return
# define radf value and spy close
pr = data[self.symbol].Value
else:
# check if SPY and safe assets are ready
if not data.Bars.ContainsKey(self.spy) and not data.Bars.ContainsKey(self.spy) or data[self.spy] is None:
self.Log(f'SPY is not ready')
return
if self.safe_asset is not None:
if not data.Bars.ContainsKey(self.safe_asset) and not data.Bars.ContainsKey(self.safe_asset) or data[self.safe_asset] is None:
self.Log(f'TLT is not ready')
return
# check if custom data is ready
if not data.ContainsKey(self.symbol):
self.Log(f'There is no indicator data for the bar.')
return
pr = data[self.symbol].Value
############ SMA ############
# if not self.pra_sma.IsReady:
# self.Log(f'Radf agg SMA is not ready')
# return
# pr = self.pra_sma.Current.Value
############ SMA ############
# populate predictins window
self.pred_window.Add(pr)
if not self.pred_window.IsReady: return
# predictions standard deviation
preds_std = np.std(list(self.pred_window))
self.std_window.Add(preds_std)
if not self.pred_window.IsReady: return
if not self.std_window.IsReady: return
# growth rate of indicator
indicator_gw = list(self.std_window)
indicator_gw = indicator_gw[0] / indicator_gw[1] - 1
self.Debug(f"Indicator growth rate: {indicator_gw}")
# current return
# close_diff = self.close_radf[0] - self.close_radf[self.close_window_length - 1]
# plot indicator
# self.Plot("Indicators", "PR", pr)
# self.Plot("Indicators", "Threshold", self.thresold)
self.Plot("Indicators", "Predictoins sd", preds_std)
self.Plot("Indicators", "Threshold sd", self.sd_threshold)
self.Plot("Indicators GR", "SD", indicator_gw)
## buy or sell all
if not self.Portfolio["SPY"].Invested and preds_std <= self.sd_threshold: # pr > self.thresold
if self.safe_asset is not None:
if self.Portfolio[self.safe_asset].Invested:
self.Liquidate(self.safe_asset)
self.SetHoldings("SPY", 1)
elif self.Portfolio["SPY"].Invested and preds_std > self.sd_threshold:
self.Liquidate("SPY")
if self.safe_asset is not None:
self.SetHoldings(self.safe_asset, 1)
class PRAData(PythonData):
algo = None
@staticmethod
def set_algo(algo):
PRAData.algo = algo
def GetSource(self, config, date, isLive):
if isLive:
url = "https://contentiobatch.blob.core.windows.net/qc-live/pra.csv?sp=r&st=2021-09-29T14:42:22Z&se=2025-04-28T22:42:22Z&sv=2020-08-04&sr=b&sig=gJCmQj%2FU6%2FwL2pPEytx6nwtfbWAoKXDLYQ%2FmgeieGGM%3D"
return SubscriptionDataSource(url, SubscriptionTransportMedium.RemoteFile)
else:
url = "https://contentiobatch.blob.core.windows.net/qc-backtest/systemic_risk.csv"
return SubscriptionDataSource(url, SubscriptionTransportMedium.RemoteFile)
def Reader(self, config, line, date, isLive):
# If first character is not digit, pass
if not (line.strip() and line[0].isdigit()): return None
if isLive:
data = line.split(';')
index = PRAData()
index.Symbol = config.Symbol
index.EndTime = datetime.utcnow()
index.Time = datetime.utcnow()
index.Value = int(data[1])
else:
data = line.split(',')
index = PRAData()
index.Symbol = config.Symbol
index.Time = datetime.strptime(data[0], '%Y-%m-%d') #+ timedelta(hours=1) # Make sure we only get this data AFTER trading day - don't want forward bias.
index.Value = float(data[1]) # pr_below_dummy_176
return index