| Overall Statistics |
|
Total Trades 92 Average Win 0.85% Average Loss -0.22% Compounding Annual Return 7.708% Drawdown 6.900% Expectancy 1.461 Net Profit 16.003% Sharpe Ratio 1.176 Probabilistic Sharpe Ratio 59.153% Loss Rate 49% Win Rate 51% Profit-Loss Ratio 3.81 Alpha 0.037 Beta 0.287 Annual Standard Deviation 0.046 Annual Variance 0.002 Information Ratio -0.066 Tracking Error 0.084 Treynor Ratio 0.187 Total Fees $252.70 Estimated Strategy Capacity $45000.00 Lowest Capacity Asset STIP US4OVXDLWVHH |
#region imports
from AlgorithmImports import *
#endregion
class CPIData(PythonData):
# 12-month unadjusted CPI data
# Source: https://www.bls.gov/charts/consumer-price-index/consumer-price-index-by-category-line-chart.htm
# Release dates source: https://www.bls.gov/bls/news-release/cpi.htm
def GetSource(self,
config: SubscriptionDataConfig,
date: datetime,
isLive: bool) -> SubscriptionDataSource:
return SubscriptionDataSource("https://www.dropbox.com/s/f02a9htg6pyhf9p/CPI%20data%201.csv?dl=1", SubscriptionTransportMedium.RemoteFile)
def Reader(self,
config: SubscriptionDataConfig,
line: str,
date: datetime,
isLive: bool) -> BaseData:
if not (line.strip()):
return None
cpi = CPIData()
cpi.Symbol = config.Symbol
try:
def parse(pct):
return float(pct[:-1]) / 100
data = line.split(',')
cpi.EndTime = datetime.strptime(data[0], "%m%d%Y %H:%M %p")
cpi["month"] = data[1]
cpi['all-items'] = parse(data[2])
cpi['food'] = parse(data[3])
cpi['food-at-home'] = parse(data[4])
cpi['food-away-from-home'] = parse(data[5])
cpi['energy'] = parse(data[6])
cpi['gasoline'] = parse(data[7])
cpi['electricity'] = parse(data[8])
cpi['natural-gas'] = parse(data[9])
cpi['all-items-less-food-and-energy'] = parse(data[10])
cpi['commodities-less-food-and-energy-commodities'] = parse(data[11])
cpi['apparel'] = parse(data[12])
cpi['new-vehicles'] = parse(data[13])
cpi['medical-car-commodities'] = parse(data[14])
cpi['services-less-energy-services'] = parse(data[15])
cpi['shelter'] = parse(data[16])
cpi['medical-care-services'] = parse(data[17])
cpi['education-and-communication'] = parse(data[18])
cpi.Value = cpi['all-items']
except ValueError:
# Do nothing
return None
return cpi
# region imports
from AlgorithmImports import *
from data import CPIData
from symbol import SymbolData
# endregion
class Modified_60_40(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2017, 1, 1) # Set Start Date
self.SetEndDate(2019, 1, 1) # Set End Date
self.SetCash(100000) # Set Strategy Cash
self.AddEquity("SPY", Resolution.Minute)
self.AddEquity("BND", Resolution.Minute)
self.AddEquity("GLD", Resolution.Minute)
self.AddEquity("STIP", Resolution.Minute)
# requesting data for yield curve
self.yield_curve_symbol = self.AddData(USTreasuryYieldCurveRate, "USTYCR").Symbol
# historical data for yield curve
history = self.History(USTreasuryYieldCurveRate, self.yield_curve_symbol, 60, Resolution.Daily)
# requesting data for CPI
self.cpi_symbol = self.AddData(CPIData, "CPIData").Symbol
# Warm up CPI data history
self.cpi_lookback = timedelta(days=395)
self.cpi_history = self.History(self.cpi_symbol, self.StartDate - self.cpi_lookback, self.StartDate)
if not self.cpi_history.empty:
self.cpi_history = self.cpi_history.loc[self.cpi_symbol]['value']
self.RotationInterval = timedelta(days=30)
self.first = True
def OnData(self, data: Slice):
#look for first
if self.first:
self.first = False
self.LastRotationTime = self.Time
return
#invest in short term treasuries if not invested
if not self.Portfolio.Invested:
self.SetHoldings("STIP", 1)
#ensure data is available at given slice
if not data.ContainsKey(self.yield_curve_symbol) and not data.ContainsKey(self.cpi_symbol):
return
# update yield curve data
if data.ContainsKey(self.yield_curve_symbol):
rates = data[self.yield_curve_symbol]
# update CPI historical data
if data.ContainsKey(self.cpi_symbol):
self.rebalance = True
self.Debug("test")
self.cpi_history.loc[self.Time] = data[self.cpi_symbol].Value
self.cpi_history = self.cpi_history.loc[self.cpi_history.index >= self.Time]
#look for rebalance window
delta = self.Time - self.LastRotationTime
if delta > self.RotationInterval:
#reset rebalance window
self.LastRotationTime = self.Time
# determine if risk on (10Y-3M is >1) or off (10Y-3M is <1)
if (rates.TenYear - rates.ThreeMonth) > 1:
risk = 1
else:
risk = 0
# determine if inflationary (cpi is > X%) ir deflationary (cpi <X%)
if self.cpi_history[-1] > .03:
inflation = 1
else:
inflation = 0
if risk == 1 and inflation == 1:
#risk on, inflationary
self.Debug("risk on inflationary")
self.Liquidate()
self.SetHoldings("SPY", 0.6)
self.SetHoldings("GLD", 0.4)
elif risk == 1 and inflation == 0:
#risk on, deflationary
self.Debug("risk on deflationary")
self.Liquidate()
self.SetHoldings("SPY", 0.6)
self.SetHoldings("BND", 0.4)
elif risk == 0 and inflation == 1:
#risk off, inflationary
self.Debug("risk off inflationary")
self.Liquidate()
self.SetHoldings("STIP", 0.6)
self.SetHoldings("GLD", 0.4)
elif risk == 0 and inflation == 0:
#risk off, deflationary
self.Debug("risk off deflationary")
self.Liquidate()
self.SetHoldings("BND", 0.6)
self.SetHoldings("STIP", 0.4) #region imports
from AlgorithmImports import *
#endregion
class SymbolData:
def __init__(self, algorithm, security, lookback):
self.algorithm = algorithm
self.symbol = security.Symbol
self.security = security
# Set up consolidators to collect pricing data
self.consolidator = TradeBarConsolidator(1)
self.consolidator.DataConsolidated += self.consolidation_handler
algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator)
self.history = pd.Series()
self.lookback = lookback
# Get historical data
history = algorithm.History(self.symbol, self.lookback, Resolution.Daily)
if not history.empty:
self.history = history.loc[self.symbol].open
def consolidation_handler(self, sender: object, consolidated_bar: TradeBar) -> None:
self.history.loc[consolidated_bar.EndTime] = consolidated_bar.Open
self.history = self.history.loc[self.history.index > consolidated_bar.EndTime - self.lookback]
def dispose(self):
self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.consolidator)