| Overall Statistics |
|
Total Trades 701 Average Win 0.88% Average Loss -0.53% Compounding Annual Return 11.471% Drawdown 20.500% Expectancy 0.633 Net Profit 218.398% Sharpe Ratio 1.045 Loss Rate 39% Win Rate 61% Profit-Loss Ratio 1.66 Alpha 0.014 Beta 5.015 Annual Standard Deviation 0.11 Annual Variance 0.012 Information Ratio 0.863 Tracking Error 0.11 Treynor Ratio 0.023 Total Fees $3131.21 |
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")
from System import *
from QuantConnect import *
from QuantConnect.Orders import *
from QuantConnect.Algorithm.Framework.Execution import ExecutionModel
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTargetCollection
from datetime import datetime, timedelta
from pytz import utc
UTCMIN = datetime.min.replace(tzinfo=utc)
class Zero_Leverage_ExecutionModel(ExecutionModel):
def __init__(self, resolution = Resolution.Daily, min_order = 0):
self.targetsCollection = PortfolioTargetCollection()
self.finished = False
self.rebalancingTime = UTCMIN
self.rebalancingPeriod = Extensions.ToTimeSpan(resolution)
self.min_order_value = min_order
def Execute(self, algorithm, targets):
#Run until all orders are completed once per day
if self.finished and algorithm.UtcTime <= self.rebalancingTime:
return
elif algorithm.UtcTime > self.rebalancingTime:
self.finished = False
self.rebalancingTime = algorithm.UtcTime + self.rebalancingPeriod
self.targetsCollection.AddRange(targets)
sold = False
bought = False
for target in self.targetsCollection.OrderByMarginImpact(algorithm):
open_quantity = sum([x.Quantity for x in algorithm.Transactions.GetOpenOrders(target.Symbol)])
existing = algorithm.Securities[target.Symbol].Holdings.Quantity + open_quantity
qty_to_order = target.Quantity - existing
value = abs(qty_to_order) * algorithm.Securities[target.Symbol].Price
#Only sell if it exeeds min order value or if selling everything, to prevent being left with a tiny amount of stock unable to sell
if qty_to_order <= -1.0 and (value >= self.min_order_value or target.Quantity == 0):
algorithm.MarketOrder(target.Symbol, qty_to_order)
#algorithm.Debug("Sell = " + str(target.Symbol) + " > " + str(quantity))
sold = True
#Only buy if it exeeds min order value and no sells have also processed in this same instant to avoid leverage
elif qty_to_order >= 1.0 and value >= self.min_order_value and sold == False:
#Don't buy stocks if there are still open sell orders to prevent leverage
all_open_sells_value = 0
for order in algorithm.Transactions.GetOpenOrders():
if order.Quantity < 0:
all_open_sells_value += abs(order.Quantity) * algorithm.Securities[order.Symbol].Price
if all_open_sells_value == 0:
algorithm.MarketOrder(target.Symbol, qty_to_order)
#algorithm.Debug("Buy = " + str(target.Symbol) + " > " + str(quantity))
bought = True
else:
bought = True
if bought == False and sold == False:
self.finished = True
self.targetsCollection.ClearFulfilled(algorithm)from clr import AddReference
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm.Framework.Alphas import *
from Utility_Functions import *
from datetime import timedelta
import numpy as np
import pandas as pd
import math
import itertools
from datetime import datetime, timedelta
from pytz import utc
UTCMIN = datetime.min.replace(tzinfo=utc)
class Alpha_Composite:
def __init__(self, variables, *args, **kwargs):
self.ACR_assets = [
"VOE",
"VDC",
"XLP",
"IJR",
]
self.ACR_bonds = [
"TLT",
"TIP",
"DBC",
"SHY",
]
self.ACR_sectors = [
"XLB", #Materials
"XLY", #Consumer Cyclical
"XLF", #Financials
"IYR", #ISHARES Real Estate
"XLP", #Consumer Defensive
"XLV", #Healthcare
"XLU", #Utilities
"XLE", #Energy
"XLI", #Industrials
"XLK", #Tech
]
self.ACR_fixed = [
"SPY",
]
#self.Hedge = ["VXX",]
self.ACR_stocks = self.ACR_assets+self.ACR_bonds+self.ACR_fixed# + self.Hedge
"""
ACR (Asset Class Rotation) parameters
"""
self.ACR_sector_step = 13 #12% step change = all bonds if 9 of 11 sectors down
self.ACR_asset_step = 20 #20% step change
self.ACR_allocation = {}
self.ACR_fixed_weight = [
0.0, #SPY
]
self.ACR_stocks = self.ACR_assets+self.ACR_bonds+self.ACR_fixed# + self.Hedge
#Variable Definitions
self.resolution = Resolution.Daily
self.rebalancingTime = UTCMIN
self.days_predicted = 5
self.rebalancingPeriod = Extensions.ToTimeSpan(self.resolution)*self.days_predicted
self.var = variables
#for stock in self.ACR_sectors:
#self.var.AddEquity(stock)
self.symbolData = {}
for symbol in self.ACR_stocks:
self.ACR_allocation[symbol] = 0.0
"""
VAA parameters
"""
self.periods = [42,21,63,126,252]
self.weights = [0, 12, 6, 3, 1]
self.GrowthSymbols = [
"SPY", #SPDR S&P 500 Trust ETF
"VEA", #Vanguard FTSE Developed Markets ETF
"VTI", #Vanguard Total Stock Market ETF
"VT", #Vanguard Total World Stock Index Fund
"VWO", #Vanguard FTSE Emerging Markets ETF
"AGG", #iShares Barclays Aggregate Bond ETF
]
self.SafetySymbols = [
"HYD", #VanEck Vectors High-Yield Municipal ETF
"VMBS", #Vanguard Mortgage-Backed Securities ETF
"BKLN", #Invesco Senior Loan ETF
"JNK", #SPDR Barclays High Yield Bond ETF
"TLT", #iShares 20+ Year Treasury Bond ETF
]
#Variable Definitions
self.symbolDataBySymbol = {}
self.VAA_allocation = {}
self.VAA_stocks = self.GrowthSymbols + self.SafetySymbols
for symbol in self.VAA_stocks:
self.VAA_allocation[symbol] = 0.0
'''
Zscore
'''
#zscore assets
self.ZScore_stocks = [
"SPY",
"TLT",
"XLP",
#"ZIV",
]
"""
zscore parameters
"""
self.fixed_wt_pct = 0.0 #.50
self.fixed_wt = {
"XLP": 0.50,
"TLT": 0.40,
#"ZIV": 0.10,
}
self.vol_factor = 0.50 #TLT = .5, SPY = 1
self.ext_factor = 4.0 #move too extreme, leave asset
self.lookback = 150
self.ZScore_allocation = {}
self.stocks = self.ZScore_stocks + self.VAA_stocks + self.ACR_stocks
self.stocks = (list(set(self.stocks)))
for symbol in self.ZScore_stocks:
self.ZScore_allocation[symbol] = 0.0
def Update(self, algorithm, data):
#algorithm.Debug("Updating Alpha Model.")
insights = []
collect_insights = {}
for stock in self.stocks:
collect_insights[stock] = {}
if (algorithm.UtcTime <= self.rebalancingTime):
return insights
for sid in self.ACR_stocks:
if not sid in self.ACR_allocation:
self.ACR_allocation[sid] = 0.0
ACR_assets_weight = np.zeros(len(self.ACR_assets))
ACR_bonds_data = pd.DataFrame(0, columns=['Weight','Ratio','20Day','60Day'],index=self.ACR_bonds)
ACR_sectors_data = pd.DataFrame(0, columns=['Ratio','20Day','200Day'],index=self.ACR_sectors)
"""
Determine sector trends and calculate weight to assets/bonds
"""
ACR_sectors_data.loc[:,'20Day'] = algorithm.History(self.ACR_sectors, 20, Resolution.Daily)["close"].unstack(level=0).mean()
ACR_sectors_data.loc[:, '200Day'] = algorithm.History(self.ACR_sectors, 200, Resolution.Daily)["close"].unstack(level=0).mean()
ACR_sectors_data['Ratio'] = ACR_sectors_data['20Day']/ACR_sectors_data['200Day'] - 1
ACR_bonds_weight = len(ACR_sectors_data[ACR_sectors_data['Ratio'] < 0]) * self.ACR_sector_step/100.0
if ACR_bonds_weight > 1.0:
ACR_bonds_weight = 1.0
ACR_bonds_weight = ACR_bonds_weight * (1-sum(self.ACR_fixed_weight))
"""
Determine bond trends and which duration to be in
"""
if ACR_bonds_weight > 0.0:
ACR_bonds_data.loc[:,'20Day'] = algorithm.History(self.ACR_bonds, 20, Resolution.Daily)["close"].unstack(level=0).mean()
ACR_bonds_data.loc[:, '60Day'] = algorithm.History(self.ACR_bonds, 60, Resolution.Daily)["close"].unstack(level=0).mean()
ACR_bonds_data['Ratio'] = ACR_bonds_data['20Day']/ACR_bonds_data['60Day'] - 1
ACR_bonds_data['Weight'] = 0
ACR_bonds_data.loc[ACR_bonds_data['Ratio'].idxmax(), 'Weight'] = ACR_bonds_weight
#log.info(self.ACR_bonds_data)
returns = algorithm.History(self.ACR_assets, 126, Resolution.Daily)["close"].unstack(level=0).dropna().pct_change().dropna() + 1.0
expected = algorithm.History(self.ACR_stocks, 20, Resolution.Daily)["close"].unstack(level=0).dropna().pct_change().dropna().mean() + 1.0
"""
Create portfolio combinations
"""
n = len(self.ACR_assets)
steps = [x/100.0 for x in range(0,101,int(self.ACR_asset_step))]
a = [steps for x in range(n)]
b = list(itertools.product(*a))
x = [sum(i) for i in b]
port = pd.DataFrame(b)
port['Sum'] = x
port = port[port.Sum == 1]
del port['Sum']
"""
Score and Weight portoflio
"""
port_returns = pd.DataFrame(np.dot(returns, port.T), index=returns.index)
port_metrics = self.ACR_get_specs(port_returns)
port_metrics = self.ACR_score(port_metrics)
port_metrics['Z3'] = port_metrics.ZMean\
-port_metrics.ZSTD\
-port_metrics.ZDownSide\
+port_metrics.ZAnnualized\
-port_metrics.ZMax_Draw\
-port_metrics.ZSTD10\
+port_metrics.ZMean10\
+port_metrics.ZMinimum15
port_metrics = port_metrics.sort_values(by='Z3', ascending=False)
portfolios = port
portfolios.columns = list(returns.columns.values)
best = pd.concat([pd.DataFrame(portfolios.iloc[port_metrics['Z3'].idxmax()]).T])
#log.info(best.loc[:, (best != 0).any(axis=0)].T)
best = pd.DataFrame(portfolios.iloc[port_metrics['Z3'].idxmax()])
#log.info(best)
ACR_assets_weight = [i[0]*(1-ACR_bonds_weight-sum(self.ACR_fixed_weight)) for i in best.values]
predictionInterval = Time.Multiply(Extensions.ToTimeSpan(self.resolution), self.days_predicted)
allocation_temp = {}
magnitude = {}
direction = {}
risk_weight = 0
for x in range(n):
allocation_temp[self.ACR_assets[x]] = ACR_assets_weight[x]#*.95
#expected_temp[self.ACR_assets[x]] =
risk_weight += ACR_assets_weight[x]
for stock in self.ACR_stocks:
magnitude[stock] = ((expected[stock]**self.days_predicted)-1.0)/5.0
#magnitude[stock] = 0.001
#algorithm.Debug(str(stock) + " magnitude = " + str(magnitude[stock]))
if magnitude[stock] > -0.01:
direction[stock] = InsightDirection.Up
else:
direction[stock] = InsightDirection.Flat
magnitude[stock] = 0.001
#self.ACR_allocation[self.Hedge[0]] = risk_weight * .05
for stock in self.ACR_bonds:
allocation_temp[stock] = ACR_bonds_data.loc[stock, 'Weight']
for x in range(len(self.ACR_fixed)):
allocation_temp[self.ACR_fixed[x]] = self.ACR_fixed_weight[x]
for symbol in self.ACR_allocation:
if (self.ACR_allocation[symbol] != allocation_temp[symbol]) or (self.ACR_allocation[symbol] != 0.0) or (allocation_temp[symbol] != 0.0):
#if allocation_temp[symbol] > self.ACR_allocation[symbol]:
self.ACR_allocation[symbol] = allocation_temp[symbol]
#insights.append(Insight(symbol, predictionInterval, InsightType.Price, InsightDirection.Up, magnitude[symbol], self.ACR_allocation[symbol], sourceModel="Test"))
collect_insights[symbol]["ACR"] = [predictionInterval, InsightType.Price, InsightDirection.Up, magnitude[symbol], self.ACR_allocation[symbol]]
#algorithm.Debug(str(symbol) + " = " + str(self.ACR_allocation[symbol]))
'''
VAA
'''
##Using a weighted average, compute the score for each risky asset.
growthdata = []
for symbol in self.GrowthSymbols:
if not symbol in self.symbolDataBySymbol.keys(): continue
temp_values = []
for i, period in enumerate(self.periods):
if self.symbolDataBySymbol[symbol][i].CanEmit:
temp_values.append(self.symbolDataBySymbol[symbol][i].Return)
else:
temp_values.append(0.0)
score = sum([i*j for i,j in zip(temp_values,self.weights)])
growthdata.append([symbol, score, temp_values[0]])
orderedGrowthScores = sorted(growthdata, key=lambda x: x[1], reverse=True)
#algorithm.Debug(orderedGrowthScores)
##Using a weighted average, compute the score for each risk-free asset.
##This approach overweights the front month momentum value and progressively underweights older momentum values
safetydata = []
for symbol in self.SafetySymbols:
#self.var.Debug(symbol)
#self.var.Debug(self.symbolDataBySymbol.keys())
if not symbol in self.symbolDataBySymbol.keys(): continue
#self.var.Debug("2")
temp_values = []
for i, period in enumerate(self.periods):
#self.var.Debug("3")
if self.symbolDataBySymbol[symbol][i].CanEmit:
#self.var.Debug("4")
temp_values.append(self.symbolDataBySymbol[symbol][i].Return)
else:
temp_values.append(0.0)
#self.var.Debug("5")
#score = np.average(temp_values, weights = self.weights)
score = sum([i*j for i,j in zip(temp_values,self.weights)])
safetydata.append([symbol, score, temp_values[0]])
orderedSafeScores = sorted(safetydata, key=lambda x: x[1], reverse=True)
#algorithm.Debug(orderedSafeScores)
##Count the number of risky assets with negative momentum scores and store in N. If all four of the offensive assets exhibit positive momentum scores,
##select the offensive asset with the highest score and allocate 100% of the portfolio to that asset at the close
count = 0
negative_flag = False
for stock in orderedGrowthScores:
if stock[1] <= 0.0:
count += 1
if count > 0:
negative_flag = True
top_growth = orderedGrowthScores[0][0]
second_growth = orderedGrowthScores[1][0]
top_safe = orderedSafeScores[0][0]
second_safe = orderedSafeScores[1][0]
predictionInterval = Time.Multiply(Extensions.ToTimeSpan(Resolution.Daily), self.days_predicted)
stock_data = {}
for data in (orderedGrowthScores + orderedSafeScores):
#stock_data[stock] = [score, momentum, magnitude, direction]
score = data[1]
if score == 0.0:
#DATA ERROR
continue
momentum = data[2]
magnitude = (data[2]/100*(self.days_predicted/self.periods[0]))
if magnitude > -.005:
direction = InsightDirection.Up
#elif magnitude < -.005:
#direction = InsightDirection.Down
else:
direction = InsightDirection.Flat
stock_data[data[0]] = [score, momentum, magnitude, direction]
for symbol in stock_data:
weight = 0
if symbol == top_growth:
if negative_flag:
weight = 0.1
else:
weight = 0.5
elif symbol == second_growth:
if negative_flag:
weight = 0.1
else:
weight = 0.5
elif symbol == top_safe:
if negative_flag:
weight = 0.4
else:
weight = 0.0
elif symbol == second_safe:
if negative_flag:
weight = 0.4
else:
weight = 0.0
else:
weight = 0
if self.VAA_allocation[symbol] != weight:
#insights.append(Insight(symbol, predictionInterval, InsightType.Price, stock_data[symbol][3], stock_data[symbol][2], weight, sourceModel="Test"))
collect_insights[symbol]["VAA"] = [predictionInterval, InsightType.Price, stock_data[symbol][3], stock_data[symbol][2], weight]
self.VAA_allocation[symbol] = weight
'''
ZScore
'''
safestock = "TLT"
history = algorithm.History([safestock], self.lookback, Resolution.Daily)
mean = history['close'].mean()
sigma = history['close'].std()
price = float(algorithm.Securities[safestock].Price)
if sigma != 0.0:
z = (price - mean) / sigma**self.vol_factor
else:
z = 0.0
if -self.ext_factor <= z <= self.ext_factor:
tlt_target = 1.0/(1+math.exp(-1.2*z)) # Pure momentum adding 1 to the sin wave to prevent shorting
else:
tlt_target = 0.0
spy_target = (1.0-tlt_target)
allocation_temp = {}
magnitude = {}
direction = {}
for sid in self.ZScore_stocks:
allocation_temp[sid] = 0.0
if sid in self.fixed_wt:
allocation_temp[sid] = self.fixed_wt[sid] * self.fixed_wt_pct
allocation_temp[safestock] += tlt_target * (1.0 - self.fixed_wt_pct)
allocation_temp["SPY"] += spy_target * (1.0 - self.fixed_wt_pct)
predictionInterval = Time.Multiply(Extensions.ToTimeSpan(self.resolution), self.days_predicted)
expected = algorithm.History(self.ZScore_stocks, 5, Resolution.Daily)["close"].unstack(level=0).dropna().pct_change().dropna().mean() + 1.0
for stock in self.ZScore_stocks:
magnitude[stock] = ((expected[stock]**self.days_predicted)-1.0)/2.0
if magnitude[stock] > -0.005:
direction[stock] = InsightDirection.Up
else:
direction[stock] = InsightDirection.Flat
for symbol in self.ZScore_allocation:
if (self.ZScore_allocation[symbol] != allocation_temp[symbol]) or (self.ZScore_allocation[symbol] != 0.0) or (allocation_temp[symbol] != 0.0):
self.ZScore_allocation[symbol] = allocation_temp[symbol]
#insights.append(Insight(symbol, predictionInterval, InsightType.Price, direction[symbol], magnitude[symbol], self.ZScore_allocation[symbol], sourceModel="Test"))
collect_insights[symbol]["ZScore"] = [predictionInterval, InsightType.Price, direction[symbol], magnitude[symbol], self.ZScore_allocation[symbol]]
#algorithm.Debug(str(symbol) + " = " + str(self.ZScore_allocation[symbol]))
'''
combined
'''
for symbol in self.stocks:
num_alphas = 3.0
magnitude = 0.0
allocation = 0.0
direction = InsightDirection.Flat
if collect_insights[symbol] != {}:
for alpha in collect_insights[symbol].keys():
allocation += collect_insights[symbol][alpha][4]/num_alphas
magnitude += collect_insights[symbol][alpha][3]/num_alphas
if magnitude > -0.005:
direction = InsightDirection.Up
else:
direction = InsightDirection.Flat
insights.append(Insight(symbol, predictionInterval, InsightType.Price, direction, magnitude, allocation))
self.rebalancingTime = algorithm.UtcTime + self.rebalancingPeriod
return Insight.Group( insights )
def ACR_drawdown(self, returns):
mat = returns.cumprod().values
[n, m] = np.shape(mat)
maxes = np.maximum.accumulate(np.array(mat))
for i in range(0,n):
for j in range(m):
mat[i,j] = mat[i,j] / maxes[i,j]
df = pd.DataFrame(mat)
df[df > 1] = 1
return df
def ACR_moving_returns(self, returns, w):
mat = returns.values
[n, m] = np.shape(mat)
ret = np.zeros(shape = (n-w+1,m))
for i in range(w-1,n):
for j in range(m):
ret[i-w+1,j] = np.power(np.prod(mat[(i-w+1):i+1,j]),(1.0/w))- 1.0
return pd.DataFrame(ret)
def ACR_get_specs(self, returns):
metrics = pd.DataFrame((returns.mean()),columns=['Mean']) - 1.0
metrics['STD'] = pd.DataFrame((returns.std()))
metrics['Annualized'] = np.power(returns.cumprod().values.tolist()[-1],1.0/len(returns))- 1.0
downside = returns.copy(deep=True) - 1
downside[downside > 0] = 0
downside = downside ** 2
metrics['DownSide'] = pd.DataFrame(downside.mean() ** 0.5)
draw = self.ACR_drawdown(returns)
metrics['Max_Draw'] = 1.0 - draw.min().values
ret15 = self.ACR_moving_returns(returns,21)
metrics['Minimum15'] = ret15.min().values
ret10 = self.ACR_moving_returns(returns,21)
metrics['Mean10'] = ret10.mean().values
metrics['STD10'] = ret10.std().values
return metrics
def ACR_zscore(self, stocks, var, var_save):
stocks[var_save] = (stocks[var] - stocks[var].mean())/stocks[var].std(ddof=0)
return stocks
def ACR_score(self, metrics):
metrics = self.ACR_zscore(metrics, 'Mean', 'ZMean')
metrics = self.ACR_zscore(metrics, 'STD', 'ZSTD')
metrics = self.ACR_zscore(metrics, 'Annualized', 'ZAnnualized')
metrics = self.ACR_zscore(metrics, 'DownSide', 'ZDownSide')
metrics = self.ACR_zscore(metrics, 'Max_Draw', 'ZMax_Draw')
metrics = self.ACR_zscore(metrics, 'Minimum15', 'ZMinimum15')
metrics = self.ACR_zscore(metrics, 'STD10', 'ZSTD10')
metrics = self.ACR_zscore(metrics, 'Mean10', 'ZMean10')
return metrics
def OnSecuritiesChanged(self, algorithm, changes):
algorithm.Debug("Securities Changed. Added " + str(changes.AddedSecurities))
symbols = [ x.Symbol for x in changes.AddedSecurities if str(x.Symbol) in self.VAA_stocks ]
history = algorithm.History(symbols, (np.max(self.periods)), Resolution.Daily)
if history.empty:
algorithm.Debug("History Error")
return
for removed in changes.RemovedSecurities:
if removed.Symbol in self.VAA_stocks:
symbolData_temp = self.symbolDataBySymbol.pop(removed.Symbol, None)
if symbolData_temp is not None:
symbolData_temp.RemoveConsolidators(algorithm)
for stock in history.index.levels[0]:
symbol = SymbolCache.GetSymbol(stock)
algorithm.Debug("Getting Data for " + str(symbol))
if str(symbol) not in self.symbolDataBySymbol.keys():
algorithm.Debug("registering symboldata for " + str(symbol))
symbolData_temp = []
for period in self.periods:
#algorithm.Debug("C")
tempData = SymbolData(symbol, period)
tempData.RegisterIndicators(algorithm, Resolution.Daily)
tempData.WarmUpIndicators(history.loc[stock])
symbolData_temp.append(tempData)
self.symbolDataBySymbol[str(symbol)] = symbolData_temp
#algorithm.Debug(symbolData_temp)
class SymbolData:
def __init__(self, symbol, lookback):
self.Symbol = symbol
self.MOM = Momentum('{}.MOM({})'.format(symbol, lookback), lookback)
self.Consolidator = None
self.previous = 0
def RegisterIndicators(self, algorithm, resolution):
#algorithm.Debug("Register Indicators. Alpha")
self.Consolidator = algorithm.ResolveConsolidator(self.Symbol, resolution)
algorithm.RegisterIndicator(self.Symbol, self.MOM, self.Consolidator)
def RemoveConsolidators(self, algorithm):
if self.Consolidator is not None:
algorithm.SubscriptionManager.RemoveConsolidator(self.Symbol, self.Consolidator)
def WarmUpIndicators(self, history):
for tuple in history.itertuples():
self.MOM.Update(tuple.Index, tuple.close)
@property
def Return(self):
return float(self.MOM.Current.Value)
@property
def CanEmit(self):
if self.previous == self.MOM.Samples:
return False
self.previous = self.MOM.Samples
return self.MOM.IsReady
'''
def __str__(self, **kwargs):
return '{}: {:.2%}'.format(self.MOM.Name, (1 + self.Return)**252 - 1)
'''# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget
from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel
from QuantConnect.Algorithm.Framework.Alphas import InsightCollection
from datetime import datetime
from pytz import utc
UTCMIN = datetime.min.replace(tzinfo=utc)
class Drawdown_Risk_Model(RiskManagementModel):
def __init__(self, max_drawdown = 0.025, security_stop = .025, multi_day_slide_stop = .025, wait_days = 5):
self.insightCollection = InsightCollection()
self.rebalancingTime = UTCMIN
self.rebalancingPeriod = Extensions.ToTimeSpan(Resolution.Daily)
self.max_drawdown = -abs(max_drawdown)
self.sec_stop = -abs(security_stop)
self.multi_day_slide_stop = -abs(multi_day_slide_stop)
self.wait_days = int(wait_days)
self.blacklist = {}
self.ago_portfolio = .0001
self.yesterday_portfolio = .0001
self.today_portfolio = .0001
self.max_portfolio = .0001
self.waited_days = 0
self.port_liquidate = False
def ManageRisk(self, algorithm, targets):
targets = []
if algorithm.UtcTime > self.rebalancingTime:
self.ago_portfolio = self.yesterday_portfolio
self.yesterday_portfolio = self.today_portfolio
self.today_portfolio = float(algorithm.Portfolio.TotalPortfolioValue)
self.max_portfolio = max(self.max_portfolio, self.today_portfolio)
for symbol in self.blacklist:
self.blacklist[symbol] +=1
if self.port_liquidate:
algorithm.Debug("Portfolio drop stop! Wait " + str(self.wait_days) + ". " + str(algorithm.UtcTime))
self.waited_days += 1
if self.waited_days > self.wait_days:
self.port_liquidate = False
self.waited_days = 0
self.rebalancingTime = algorithm.UtcTime + self.rebalancingPeriod
current_portfolio = float(algorithm.Portfolio.TotalPortfolioValue)
if ((current_portfolio/self.max_portfolio)-1.0) < self.max_drawdown:
self.max_portfolio = current_portfolio
self.port_liquidate = True
algorithm.Debug("Max drop stop! Wait " + str(self.wait_days) + ". " + str(algorithm.UtcTime))
if (((current_portfolio/self.ago_portfolio)-1.0) < self.multi_day_slide_stop) or (((current_portfolio/self.yesterday_portfolio)-1.0) < self.multi_day_slide_stop) or self.port_liquidate:
self.port_liquidate = True
for kvp in algorithm.Securities:
security = kvp.Value
if security.Invested:
self.insightCollection.Clear([security.Symbol])
targets.append(PortfolioTarget(security.Symbol, 0))
for kvp in algorithm.Securities:
security = kvp.Value
liquidate = False
pnl = 0.0
if security.Invested:
pnl = security.Holdings.UnrealizedProfitPercent
if pnl < self.sec_stop:
liquidate = True
if security.Symbol in self.blacklist:
if self.blacklist[security.Symbol] > self.wait_days:
self.blacklist.pop(security.Symbol, None)
else:
liquidate = True
elif liquidate:
algorithm.Debug("Stop on " + str(security.Symbol) + "! Wait " + str(self.wait_days) + ". " + str(algorithm.UtcTime))
self.blacklist[security.Symbol] = 0
if liquidate:
self.insightCollection.Clear([security.Symbol])
targets.append(PortfolioTarget(security.Symbol, 0))
return targetsfrom clr import AddReference
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm.Framework.Alphas import *
from Utility_Functions import *
from datetime import timedelta
import numpy as np
import pandas as pd
import itertools
from datetime import datetime, timedelta
from pytz import utc
UTCMIN = datetime.min.replace(tzinfo=utc)
class Alpha_ACR:
def __init__(self, variables, *args, **kwargs):
"""
ACR (Asset Class Rotation) parameters
"""
self.ACR_sector_step = 13 #12% step change = all bonds if 9 of 11 sectors down
self.ACR_asset_step = 20 #20% step change
self.allocation = {}
self.ACR_fixed_weight = [
0.0, #SPY
]
self.ACR_assets = [
"VOE",
"VDC",
"XLP",
"IJR",
]
self.ACR_bonds = [
"TLT",
"TIP",
"DBC",
"SHY",
]
self.ACR_sectors = [
"XLB", #Materials
"XLY", #Consumer Cyclical
"XLF", #Financials
"IYR", #ISHARES Real Estate
"XLP", #Consumer Defensive
"XLV", #Healthcare
"XLU", #Utilities
"XLE", #Energy
"XLI", #Industrials
"XLK", #Tech
]
self.ACR_fixed = [
"SPY",
]
#self.Hedge = ["VXX",]
self.stocks = self.ACR_assets+self.ACR_bonds+self.ACR_fixed# + self.Hedge
#Variable Definitions
self.resolution = Resolution.Daily
self.rebalancingTime = UTCMIN
self.days_predicted = 5
self.rebalancingPeriod = Extensions.ToTimeSpan(self.resolution)*self.days_predicted
self.var = variables
#for stock in self.ACR_sectors:
#self.var.AddEquity(stock)
self.symbolData = {}
for symbol in self.stocks:
self.allocation[symbol] = 0.0
def Update(self, algorithm, data):
#algorithm.Debug("Updating Alpha Model.")
insights = []
if (algorithm.UtcTime <= self.rebalancingTime):
return insights
self.symbolData["sectors20"] = algorithm.History(self.ACR_sectors, 20, Resolution.Daily)["close"]
self.symbolData["sectors200"] = algorithm.History(self.ACR_sectors, 200, Resolution.Daily)["close"]
self.symbolData["bonds20"] = algorithm.History(self.ACR_bonds, 20, Resolution.Daily)["close"]
self.symbolData["bonds60"] = algorithm.History(self.ACR_bonds, 60, Resolution.Daily)["close"]
self.symbolData["assets126"] = algorithm.History(self.ACR_assets, 126, Resolution.Daily)["close"]
self.symbolData["return5"] = algorithm.History(self.stocks, 5, Resolution.Daily)["close"]
ACR_assets_weight = np.zeros(len(self.ACR_assets))
ACR_bonds_data = pd.DataFrame(0, columns=['Weight','Ratio','20Day','60Day'],index=self.ACR_bonds)
ACR_sectors_data = pd.DataFrame(0, columns=['Ratio','20Day','200Day'],index=self.ACR_sectors)
"""
Determine sector trends and calculate weight to assets/bonds
"""
ACR_sectors_data.loc[:,'20Day'] = self.symbolData["sectors20"].unstack(level=0).mean()
ACR_sectors_data.loc[:, '200Day'] = self.symbolData["sectors200"].unstack(level=0).mean()
ACR_sectors_data['Ratio'] = ACR_sectors_data['20Day']/ACR_sectors_data['200Day'] - 1
ACR_bonds_weight = len(ACR_sectors_data[ACR_sectors_data['Ratio'] < 0]) * self.ACR_sector_step/100.0
if ACR_bonds_weight > 1.0:
ACR_bonds_weight = 1.0
ACR_bonds_weight = ACR_bonds_weight * (1-sum(self.ACR_fixed_weight))
"""
Determine bond trends and which duration to be in
"""
if ACR_bonds_weight > 0.0:
ACR_bonds_data.loc[:,'20Day'] = self.symbolData["bonds20"].unstack(level=0).mean()
ACR_bonds_data.loc[:, '60Day'] = self.symbolData["bonds60"].unstack(level=0).mean()
ACR_bonds_data['Ratio'] = ACR_bonds_data['20Day']/ACR_bonds_data['60Day'] - 1
ACR_bonds_data['Weight'] = 0
ACR_bonds_data.loc[ACR_bonds_data['Ratio'].idxmax(), 'Weight'] = ACR_bonds_weight
#log.info(self.ACR_bonds_data)
returns = self.symbolData["assets126"].unstack(level=0).dropna().pct_change().dropna() + 1.0
expected = self.symbolData["return5"].unstack(level=0).dropna().pct_change().dropna().mean() + 1.0
#algorithm.Debug(expected)
"""
Create portfolio combinations
"""
n = len(self.ACR_assets)
steps = [x/100.0 for x in range(0,101,int(self.ACR_asset_step))]
a = [steps for x in range(n)]
b = list(itertools.product(*a))
x = [sum(i) for i in b]
port = pd.DataFrame(b)
port['Sum'] = x
port = port[port.Sum == 1]
del port['Sum']
"""
Score and Weight portoflio
"""
port_returns = pd.DataFrame(np.dot(returns, port.T), index=returns.index)
port_metrics = self.ACR_get_specs(port_returns)
port_metrics = self.ACR_score(port_metrics)
port_metrics['Z3'] = port_metrics.ZMean\
-port_metrics.ZSTD\
-port_metrics.ZDownSide\
+port_metrics.ZAnnualized\
-port_metrics.ZMax_Draw\
-port_metrics.ZSTD10\
+port_metrics.ZMean10\
+port_metrics.ZMinimum15
port_metrics = port_metrics.sort_values(by='Z3', ascending=False)
portfolios = port
portfolios.columns = list(returns.columns.values)
best = pd.concat([pd.DataFrame(portfolios.iloc[port_metrics['Z3'].idxmax()]).T])
#log.info(best.loc[:, (best != 0).any(axis=0)].T)
best = pd.DataFrame(portfolios.iloc[port_metrics['Z3'].idxmax()])
#log.info(best)
ACR_assets_weight = [i[0]*(1-ACR_bonds_weight-sum(self.ACR_fixed_weight)) for i in best.values]
allocation_temp = {}
magnitude = {}
direction = {}
risk_weight = 0
for x in range(n):
allocation_temp[self.ACR_assets[x]] = ACR_assets_weight[x]#*.95
#expected_temp[self.ACR_assets[x]] =
risk_weight += ACR_assets_weight[x]
predictionInterval = Time.Multiply(Extensions.ToTimeSpan(self.resolution), self.days_predicted)
for stock in self.stocks:
magnitude[stock] = ((expected[stock]**self.days_predicted)-1.0)
if magnitude[stock] > 0.0:
direction[stock] = InsightDirection.Up
elif magnitude[stock] < 0.0:
direction[stock] = InsightDirection.Down
else:
direction[stock] = InsightDirection.Flat
#self.allocation[self.Hedge[0]] = risk_weight * .05
for stock in self.ACR_bonds:
allocation_temp[stock] = ACR_bonds_data.loc[stock, 'Weight']
for x in range(len(self.ACR_fixed)):
allocation_temp[self.ACR_fixed[x]] = self.ACR_fixed_weight[x]
for symbol in self.allocation:
if (self.allocation[symbol] != allocation_temp[symbol]) or (self.allocation[symbol] != 0.0) or (allocation_temp[symbol] != 0.0):
self.allocation[symbol] = allocation_temp[symbol]
insights.append(Insight(symbol, predictionInterval, InsightType.Price, direction[symbol], magnitude[symbol], self.allocation[symbol], sourceModel="Test"))
#algorithm.Debug(str(symbol) + " = " + str(self.allocation[symbol]))
#insights = [] #Remove me
#return insights
self.rebalancingTime = algorithm.UtcTime + self.rebalancingPeriod
return Insight.Group( insights )
def ACR_drawdown(self, returns):
mat = returns.cumprod().values
[n, m] = np.shape(mat)
maxes = np.maximum.accumulate(np.array(mat))
for i in range(0,n):
for j in range(m):
mat[i,j] = mat[i,j] / maxes[i,j]
df = pd.DataFrame(mat)
df[df > 1] = 1
return df
def ACR_moving_returns(self, returns, w):
mat = returns.values
[n, m] = np.shape(mat)
ret = np.zeros(shape = (n-w+1,m))
for i in range(w-1,n):
for j in range(m):
ret[i-w+1,j] = np.power(np.prod(mat[(i-w+1):i+1,j]),(1.0/w))- 1.0
return pd.DataFrame(ret)
def ACR_get_specs(self, returns):
metrics = pd.DataFrame((returns.mean()),columns=['Mean']) - 1.0
metrics['STD'] = pd.DataFrame((returns.std()))
metrics['Annualized'] = np.power(returns.cumprod().values.tolist()[-1],1.0/len(returns))- 1.0
downside = returns.copy(deep=True) - 1
downside[downside > 0] = 0
downside = downside ** 2
metrics['DownSide'] = pd.DataFrame(downside.mean() ** 0.5)
draw = self.ACR_drawdown(returns)
metrics['Max_Draw'] = 1.0 - draw.min().values
ret15 = self.ACR_moving_returns(returns,21)#21
metrics['Minimum15'] = ret15.min().values
ret10 = self.ACR_moving_returns(returns,21)#21
metrics['Mean10'] = ret10.mean().values
metrics['STD10'] = ret10.std().values
return metrics
def ACR_zscore(self, stocks, var, var_save):
stocks[var_save] = (stocks[var] - stocks[var].mean())/stocks[var].std(ddof=0)
return stocks
def ACR_score(self, metrics):
metrics = self.ACR_zscore(metrics, 'Mean', 'ZMean')
metrics = self.ACR_zscore(metrics, 'STD', 'ZSTD')
metrics = self.ACR_zscore(metrics, 'Annualized', 'ZAnnualized')
metrics = self.ACR_zscore(metrics, 'DownSide', 'ZDownSide')
metrics = self.ACR_zscore(metrics, 'Max_Draw', 'ZMax_Draw')
metrics = self.ACR_zscore(metrics, 'Minimum15', 'ZMinimum15')
metrics = self.ACR_zscore(metrics, 'STD10', 'ZSTD10')
metrics = self.ACR_zscore(metrics, 'Mean10', 'ZMean10')
return metrics
def OnSecuritiesChanged(self, algorithm, changes):
passfrom System import *
from QuantConnect import *
from QuantConnect.Orders import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Execution import *
from QuantConnect.Algorithm.Framework.Risk import *
from QuantConnect.Algorithm.Framework.Selection import *
from QuantConnect.Algorithm.Framework.Alphas import *
from Alpha_VAA import Alpha_VAA
from Alpha_ACR_2 import Alpha_ACR
from Alpha_ZScore import Alpha_ZScore
from Alpha_Composite import Alpha_Composite
from Confidence_Weighted_Portfolio import Confidence_Weighted_Portfolio
from Zero_Leverage_ExecutionModel import Zero_Leverage_ExecutionModel
from Drawdown_Risk_Model import Drawdown_Risk_Model
from Utility_Functions import Utilities
import numpy as np
import pandas as pd
from scipy.optimize import minimize
class Modular_Framework(QCAlgorithmFramework):
'''Mean Variance Optimization Algorithm.'''
def Initialize(self):
"""
General Algorithm Parameters
"""
self.UniverseSettings.Resolution = Resolution.Minute #Set Data Resolution
self.SetStartDate(2008, 1, 1) #Set Start Date
self.SetEndDate(2018, 8, 28) #Set End Date
self.SetCash(100000) #Set Strategy Cash
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) #Set Brokerage Model
self.SetBenchmark("SPY")
self.SetWarmup(252) #252
"""
Portfolio Construction Parameters
"""
self.max_leverage = 1.0 #highest combined leverage
self.reserved = 0.0 #Tell algo to reserve (won't trade with) this amount of cash
#self.normalize = True #noramalize leverage (attempt to stay at max leverage always)
"""
Portfolio Execution Parameters
"""
self.Minimum_order_value = 2500 #This reduces fees by trading only when a large reblance is needed
"""
Portfolio Risk Management Parameters
"""
self.max_drawdown = .04
self.security_stop = .04
self.multi_day_slide_stop = .04
self.wait_days = 5
"""
Universe
"""
self.assets_VAA = [
"SPY", #SPDR S&P 500 Trust ETF
"VEA", #Vanguard FTSE Developed Markets ETF
"VTI", #Vanguard Total Stock Market ETF
"AGG", #iShares Barclays Aggregate Bond Fund
"HYD", #VanEck Vectors High-Yield Municipal ETF
"VMBS", #Vanguard Mortgage-Backed Securities ETF
"BKLN", #Invesco Senior Loan ETF
"JNK", #SPDR Barclays High Yield Bond ETF
"VT", ##Vanguard Total World Stock Index Fund
"VWO", #Vanguard FTSE Emerging Markets ETF
"TLT", #iShares 20+ Year Treasury Bond ETF
]
self.assets_ACR = [
"VOE",
"VDC",
"XLP",
"IJR",
"TLT",
"TIP",
"DBC",
"SHY",
"XLB", #Materials
"XLY", #Consumer Cyclical
"XLF", #Financials
"IYR", #ISHARES Real Estate
"XLP", #Consumer Defensive
"XLV", #Healthcare
"XLU", #Utilities
"XLE", #Energy
"XLI", #Industrials
"XLK", #Tech
"SPY",
]
self.assets_ZScore = [
"SPY",
"TLT",
]
self.universe = self.assets_VAA + self.assets_ACR + self.assets_ZScore
self.universe = (list(set(self.universe)))
universe = [ Symbol.Create(symbol, SecurityType.Equity, Market.USA) for symbol in self.universe ]
"""
Framework Parameters
"""
#SET UNIVERSE MODEL
self.SetUniverseSelection( ManualUniverseSelectionModel(universe) )
#SET ALPHA MODELS
#self.SetAlpha( Alpha_VAA(self) )
#self.SetAlpha( Alpha_ACR(self) )
#self.SetAlpha( Alpha_ZScore(self) )
#self.SetAlpha( Alpha_Composite(self) )
alphas = [
Alpha_VAA(self),
#Alpha_ACR(self),
#Alpha_ZScore(self),
]
self.num_alphas = len(alphas)
self.SetAlpha( CompositeAlphaModel(*alphas))
#SET PORTFOLIO CONSTRUCTION MODEL
self.SetPortfolioConstruction( Confidence_Weighted_Portfolio(Resolution.Daily, self.reserved, self.max_leverage, self.num_alphas, False) )
#SET EXECUTION MODEL
#self.SetExecution( NullExecutionModel() )
self.SetExecution( Zero_Leverage_ExecutionModel(Resolution.Daily, self.Minimum_order_value) )
#SET RISK MANAGEMENT MODEL
self.SetRiskManagement( NullRiskManagementModel() )
#self.SetRiskManagement( Drawdown_Risk_Model(self.max_drawdown, self.security_stop, self.multi_day_slide_stop, self.wait_days) )
"""
Scheduled Functions
"""
#self.Schedule.On(self.DateRules.Every([DayOfWeek.Monday]),
self.Schedule.On(self.DateRules.EveryDay(),
self.TimeRules.At(9, 31),
Action(self.VAA_Rebal))
"""
Flags - Do not Change these!
"""
self.VAA_calculated = False
def VAA_Rebal(self):
pass
#self.VAA_calculated = False"""
Misc/Utility Functions
"""
import numpy as np
#class Utilities(object):
class Utilities:
def __init__(self, variables):
self.var = variables
def Variance(self,x,*args):
#Variance Function for SLSQP
p = np.squeeze(np.asarray(args))
Acov = np.cov(p.T)
return np.dot(x,np.dot(Acov,x))
def Jac_Variance(self,x,*args):
#jac_variance Function for SLSQP
p = np.squeeze(np.asarray(args))
Acov = np.cov(p.T)
return 2*np.dot(Acov,x)
def Unique(self,seq,idfun=None):
# order preserving
if idfun is None:
def idfun(x): return x
seen = {}
result = []
for item in seq:
marker = idfun(item)
if marker in seen:
continue
seen[marker] = 1
result.append(item)
return result
def my_record_vars(self):
#self.var.Debug("my_record_vars")
account_leverage = float(self.var.Portfolio.TotalHoldingsValue) / float(self.var.Portfolio.TotalPortfolioValue)
self.var.Plot('Leverage', 'Leverage', account_leverage)
portfolio = self.var.Portfolio
positions = portfolio.Keys
pos = 0
short = 0
for symbol in positions:
if not self.var.Securities.ContainsKey(symbol): continue
if portfolio[symbol].IsLong:
pos += 1
if portfolio[symbol].IsShort:
short += 1
self.var.Plot('Data Graph', 'Long', pos)
self.var.Plot('Data Graph', 'Short', short)
def cancel_open_orders(self):
#self.var.Debug("cancel_open_orders")
oo = [order.Symbol for order in self.var.Transactions.GetOpenOrders()]
if len(oo) == 0: return
oo = self.Unique(oo)
for symbol in oo:
if not self.var.Securities.ContainsKey(symbol): return
self.var.Transactions.CancelOpenOrders(symbol)
def cancel_open_order(self, symbol):
#self.var.Debug("cancel_open_order")
if not self.var.Securities.ContainsKey(symbol): return
oo = self.var.Transactions.GetOpenOrders(symbol)
if len(oo) == 0: return
self.var.Transactions.CancelOpenOrders(symbol)
def get_open_orders(self, symbol=None):
#self.var.Debug("get_open_orders")
orders = False
if symbol == None:
if len(self.var.Transactions.GetOpenOrders()) > 0:
orders = True
else:
if not self.var.Securities.ContainsKey(symbol):
return orders
if len(self.var.Transactions.GetOpenOrders(symbol)) > 0:
orders = True
return ordersfrom clr import AddReference
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm.Framework.Alphas import *
from datetime import datetime, timedelta
from pytz import utc
UTCMIN = datetime.min.replace(tzinfo=utc)
import numpy as np
class Alpha_VAA:
def __init__(self, variables, *args, **kwargs):
self.Name = "Alpha_VAA"
"""
VAA parameters
"""
self.periods = [42,21,63,126,252]
self.weights = [0, 12, 6, 3, 1]
self.resolution = Resolution.Daily
self.rebalancingTime = UTCMIN
self.days_predicted = 5
self.rebalancingPeriod = Extensions.ToTimeSpan(self.resolution)*self.days_predicted
self.GrowthSymbols = [
"SPY", #SPDR S&P 500 Trust ETF
"VEA", #Vanguard FTSE Developed Markets ETF
"VTI", #Vanguard Total Stock Market ETF
"VT", #Vanguard Total World Stock Index Fund
"VWO", #Vanguard FTSE Emerging Markets ETF
"AGG", #iShares Barclays Aggregate Bond ETF
]
self.SafetySymbols = [
"HYD", #VanEck Vectors High-Yield Municipal ETF
"VMBS", #Vanguard Mortgage-Backed Securities ETF
"BKLN", #Invesco Senior Loan ETF
"JNK", #SPDR Barclays High Yield Bond ETF
"TLT", #iShares 20+ Year Treasury Bond ETF
]
#Variable Definitions
self.symbolDataBySymbol = {}
self.var = variables
self.VAA_calculated = False
self.allocation = {}
self.stocks = self.GrowthSymbols + self.SafetySymbols
for symbol in self.stocks:
self.allocation[symbol] = 0.0
def Update(self, algorithm, data):
insights = []
if (algorithm.UtcTime <= self.rebalancingTime):
return insights
##Using a weighted average, compute the score for each risky asset.
growthdata = []
for symbol in self.GrowthSymbols:
if not symbol in self.symbolDataBySymbol.keys(): continue
temp_values = []
for i, period in enumerate(self.periods):
if self.symbolDataBySymbol[symbol][i].CanEmit:
temp_values.append(self.symbolDataBySymbol[symbol][i].Return)
else:
temp_values.append(0.0)
score = sum([i*j for i,j in zip(temp_values,self.weights)])
growthdata.append([symbol, score, temp_values[0]])
orderedGrowthScores = sorted(growthdata, key=lambda x: x[1], reverse=True)
#algorithm.Debug(orderedGrowthScores)
##Using a weighted average, compute the score for each risk-free asset.
##This approach overweights the front month momentum value and progressively underweights older momentum values
safetydata = []
for symbol in self.SafetySymbols:
#self.var.Debug(symbol)
#self.var.Debug(self.symbolDataBySymbol.keys())
if not symbol in self.symbolDataBySymbol.keys(): continue
#self.var.Debug("2")
temp_values = []
for i, period in enumerate(self.periods):
#self.var.Debug("3")
if self.symbolDataBySymbol[symbol][i].CanEmit:
#self.var.Debug("4")
temp_values.append(self.symbolDataBySymbol[symbol][i].Return)
else:
temp_values.append(0.0)
#self.var.Debug("5")
#score = np.average(temp_values, weights = self.weights)
score = sum([i*j for i,j in zip(temp_values,self.weights)])
safetydata.append([symbol, score, temp_values[0]])
orderedSafeScores = sorted(safetydata, key=lambda x: x[1], reverse=True)
#algorithm.Debug(orderedSafeScores)
##Count the number of risky assets with negative momentum scores and store in N. If all four of the offensive assets exhibit positive momentum scores,
##select the offensive asset with the highest score and allocate 100% of the portfolio to that asset at the close
count = 0
negative_flag = False
for stock in orderedGrowthScores:
if stock[1] <= 0.0:
count += 1
if count > 0:
negative_flag = True
top_growth = orderedGrowthScores[0][0]
second_growth = orderedGrowthScores[1][0]
top_safe = orderedSafeScores[0][0]
second_safe = orderedSafeScores[1][0]
predictionInterval = Time.Multiply(Extensions.ToTimeSpan(Resolution.Daily), self.days_predicted)
stock_data = {}
for data in (orderedGrowthScores + orderedSafeScores):
#stock_data[stock] = [score, momentum, magnitude, direction]
score = data[1]
if score == 0.0:
#DATA ERROR
continue
momentum = data[2]
magnitude = (data[2]/100*(self.days_predicted/self.periods[0]))
if magnitude > -.005:
direction = InsightDirection.Up
#elif magnitude < -.005:
#direction = InsightDirection.Down
else:
direction = InsightDirection.Flat
stock_data[data[0]] = [score, momentum, magnitude, direction]
for symbol in stock_data:
weight = 0
if symbol == top_growth:
if negative_flag:
weight = 0.1
else:
weight = 0.5
elif symbol == second_growth:
if negative_flag:
weight = 0.1
else:
weight = 0.5
elif symbol == top_safe:
if negative_flag:
weight = 0.4
else:
weight = 0.0
elif symbol == second_safe:
if negative_flag:
weight = 0.4
else:
weight = 0.0
else:
weight = 0
if self.allocation[symbol] != weight:
insights.append(Insight(symbol, predictionInterval, InsightType.Price, stock_data[symbol][3], stock_data[symbol][2], weight, sourceModel="Test"))
self.allocation[symbol] = weight
self.rebalancingTime = algorithm.UtcTime + self.rebalancingPeriod
self.var.VAA_calculated = True
#return insights
return Insight.Group( insights )
def OnSecuritiesChanged(self, algorithm, changes):
algorithm.Debug("Securities Changed. Added " + str(changes.AddedSecurities))
symbols = [ x.Symbol for x in changes.AddedSecurities if str(x.Symbol) in self.stocks ]
history = algorithm.History(symbols, (np.max(self.periods)), Resolution.Daily)
if history.empty:
algorithm.Debug("History Error")
return
for removed in changes.RemovedSecurities:
if removed.Symbol in self.stocks:
symbolData_temp = self.symbolDataBySymbol.pop(removed.Symbol, None)
if symbolData_temp is not None:
symbolData_temp.RemoveConsolidators(algorithm)
for stock in history.index.levels[0]:
symbol = SymbolCache.GetSymbol(stock)
algorithm.Debug("Getting Data for " + str(symbol))
if str(symbol) not in self.symbolDataBySymbol.keys():
algorithm.Debug("registering symboldata for " + str(symbol))
symbolData_temp = []
for period in self.periods:
#algorithm.Debug("C")
tempData = SymbolData(symbol, period)
tempData.RegisterIndicators(algorithm, Resolution.Daily)
tempData.WarmUpIndicators(history.loc[stock])
symbolData_temp.append(tempData)
self.symbolDataBySymbol[str(symbol)] = symbolData_temp
#algorithm.Debug(symbolData_temp)
class SymbolData:
def __init__(self, symbol, lookback):
self.Symbol = symbol
self.MOM = Momentum('{}.MOM({})'.format(symbol, lookback), lookback)
self.Consolidator = None
self.previous = 0
def RegisterIndicators(self, algorithm, resolution):
#algorithm.Debug("Register Indicators. Alpha")
self.Consolidator = algorithm.ResolveConsolidator(self.Symbol, resolution)
algorithm.RegisterIndicator(self.Symbol, self.MOM, self.Consolidator)
def RemoveConsolidators(self, algorithm):
if self.Consolidator is not None:
algorithm.SubscriptionManager.RemoveConsolidator(self.Symbol, self.Consolidator)
def WarmUpIndicators(self, history):
for tuple in history.itertuples():
self.MOM.Update(tuple.Index, tuple.close)
@property
def Return(self):
return float(self.MOM.Current.Value)
@property
def CanEmit(self):
if self.previous == self.MOM.Samples:
return False
self.previous = self.MOM.Samples
return self.MOM.IsReady
'''
def __str__(self, **kwargs):
return '{}: {:.2%}'.format(self.MOM.Name, (1 + self.Return)**252 - 1)
'''from clr import AddReference
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")
import numpy as np
import pandas as pd
import math
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm.Framework.Alphas import *
from datetime import datetime, timedelta
from pytz import utc
UTCMIN = datetime.min.replace(tzinfo=utc)
class Alpha_ZScore:
def __init__(self, variables, *args, **kwargs):
self.Name = "Alpha_ZScore"
self.var = variables
#zscore assets
self.stocks = [
"SPY",
"TLT",
]
"""
zscore parameters
"""
self.fixed_wt_pct = 0.0 #.50
self.fixed_wt = {
}
self.vol_factor = 0.50 #TLT = .5, SPY = 1
self.ext_factor = 4.0 #move too extreme, leave asset
self.lookback = 252
self.allocation = {}
#Variable Definitions
self.resolution = Resolution.Daily
self.rebalancingTime = UTCMIN
self.days_predicted = 1
self.rebalancingPeriod = Extensions.ToTimeSpan(self.resolution)*self.days_predicted
for symbol in self.stocks:
self.allocation[symbol] = 0.0
def Update(self, algorithm, data):
insights = []
if (algorithm.UtcTime <= self.rebalancingTime):
return insights
safestock = "TLT"
history = algorithm.History([safestock], self.lookback, Resolution.Daily)
mean = history['close'].mean()
sigma = history['close'].std()
price = float(algorithm.Securities[safestock].Price)
if sigma != 0.0:
z = (price - mean) / sigma**self.vol_factor
else:
z = 0.0
if -self.ext_factor <= z <= self.ext_factor:
tlt_target = 1.0/(1+math.exp(-1.2*z))*.75 # Pure momentum adding 1 to the sin wave to prevent shorting
else:
tlt_target = 0.0
spy_target = (1.0-tlt_target)
allocation_temp = {}
magnitude = {}
direction = {}
for sid in self.stocks:
allocation_temp[sid] = 0.0
if sid in self.fixed_wt:
allocation_temp[sid] = self.fixed_wt[sid] * self.fixed_wt_pct
allocation_temp[safestock] += tlt_target * (1.0 - self.fixed_wt_pct)
allocation_temp["SPY"] += spy_target * (1.0 - self.fixed_wt_pct)
predictionInterval = Time.Multiply(Extensions.ToTimeSpan(self.resolution), self.days_predicted)
expected = algorithm.History(self.stocks, 5, Resolution.Daily)["close"].unstack(level=0).dropna().pct_change().dropna().mean() + 1.0
for stock in self.stocks:
if not expected[stock]: continue
magnitude[stock] = ((expected[stock]**self.days_predicted)-1.0)/2.0
magnitude[stock] = 0.0035
if magnitude[stock] > -0.005:
direction[stock] = InsightDirection.Up
else:
direction[stock] = InsightDirection.Flat
for symbol in self.allocation:
if (self.allocation[symbol] != allocation_temp[symbol]) or (self.allocation[symbol] != 0.0) or (allocation_temp[symbol] != 0.0):
self.allocation[symbol] = allocation_temp[symbol]
insights.append(Insight(symbol, predictionInterval, InsightType.Price, direction[symbol], magnitude[symbol], self.allocation[symbol], sourceModel="Test"))
#algorithm.Debug(str(symbol) + " = " + str(self.allocation[symbol]))
#insights = [] #Remove me
#return insights
self.rebalancingTime = algorithm.UtcTime + self.rebalancingPeriod
return Insight.Group( insights )
def OnSecuritiesChanged(self, algorithm, changes):
passfrom clr import AddReference
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm.Framework.Alphas import *
from Utility_Functions import *
from datetime import timedelta
import numpy as np
import pandas as pd
import itertools
from datetime import datetime, timedelta
from pytz import utc
UTCMIN = datetime.min.replace(tzinfo=utc)
class Alpha_ACR:
def __init__(self, variables, *args, **kwargs):
self.Name = "Alpha_ACR"
self.ACR_assets = [
"VOE",
"VDC",
"XLP",
"IJR",
]
self.ACR_bonds = [
"TLT",
"TIP",
"DBC",
"SHY",
]
self.ACR_sectors = [
"XLB", #Materials
"XLY", #Consumer Cyclical
"XLF", #Financials
"IYR", #ISHARES Real Estate
"XLP", #Consumer Defensive
"XLV", #Healthcare
"XLU", #Utilities
"XLE", #Energy
"XLI", #Industrials
"XLK", #Tech
]
self.ACR_fixed = [
"SPY",
]
#self.Hedge = ["VXX",]
self.stocks = self.ACR_assets+self.ACR_bonds+self.ACR_fixed# + self.Hedge
"""
ACR (Asset Class Rotation) parameters
"""
self.ACR_sector_step = 13 #12% step change = all bonds if 9 of 11 sectors down
self.ACR_asset_step = 20 #20% step change
self.allocation = {}
self.ACR_fixed_weight = [
0.0, #SPY
]
self.stocks = self.ACR_assets+self.ACR_bonds+self.ACR_fixed# + self.Hedge
#Variable Definitions
self.resolution = Resolution.Daily
self.rebalancingTime = UTCMIN
self.days_predicted = 5
self.rebalancingPeriod = Extensions.ToTimeSpan(self.resolution)*self.days_predicted
self.var = variables
#for stock in self.ACR_sectors:
#self.var.AddEquity(stock)
self.symbolData = {}
for symbol in self.stocks:
self.allocation[symbol] = 0.0
def Update(self, algorithm, data):
#algorithm.Debug("Updating Alpha Model.")
insights = []
if (algorithm.UtcTime <= self.rebalancingTime):
return insights
for sid in self.stocks:
if not sid in self.allocation:
self.allocation[sid] = 0.0
ACR_assets_weight = np.zeros(len(self.ACR_assets))
ACR_bonds_data = pd.DataFrame(0, columns=['Weight','Ratio','20Day','60Day'],index=self.ACR_bonds)
ACR_sectors_data = pd.DataFrame(0, columns=['Ratio','20Day','200Day'],index=self.ACR_sectors)
"""
Determine sector trends and calculate weight to assets/bonds
"""
ACR_sectors_data.loc[:,'20Day'] = algorithm.History(self.ACR_sectors, 20, Resolution.Daily)["close"].unstack(level=0).mean()
ACR_sectors_data.loc[:, '200Day'] = algorithm.History(self.ACR_sectors, 200, Resolution.Daily)["close"].unstack(level=0).mean()
ACR_sectors_data['Ratio'] = ACR_sectors_data['20Day']/ACR_sectors_data['200Day'] - 1
ACR_bonds_weight = len(ACR_sectors_data[ACR_sectors_data['Ratio'] < 0]) * self.ACR_sector_step/100.0
if ACR_bonds_weight > 1.0:
ACR_bonds_weight = 1.0
ACR_bonds_weight = ACR_bonds_weight * (1-sum(self.ACR_fixed_weight))
"""
Determine bond trends and which duration to be in
"""
if ACR_bonds_weight > 0.0:
ACR_bonds_data.loc[:,'20Day'] = algorithm.History(self.ACR_bonds, 20, Resolution.Daily)["close"].unstack(level=0).mean()
ACR_bonds_data.loc[:, '60Day'] = algorithm.History(self.ACR_bonds, 60, Resolution.Daily)["close"].unstack(level=0).mean()
ACR_bonds_data['Ratio'] = ACR_bonds_data['20Day']/ACR_bonds_data['60Day'] - 1
ACR_bonds_data['Weight'] = 0
ACR_bonds_data.loc[ACR_bonds_data['Ratio'].idxmax(), 'Weight'] = ACR_bonds_weight
#log.info(self.ACR_bonds_data)
returns = algorithm.History(self.ACR_assets, 126, Resolution.Daily)["close"].unstack(level=0).dropna().pct_change().dropna() + 1.0
expected = algorithm.History(self.stocks, 20, Resolution.Daily)["close"].unstack(level=0).dropna().pct_change().dropna().mean() + 1.0
"""
Create portfolio combinations
"""
n = len(self.ACR_assets)
steps = [x/100.0 for x in range(0,101,int(self.ACR_asset_step))]
a = [steps for x in range(n)]
b = list(itertools.product(*a))
x = [sum(i) for i in b]
port = pd.DataFrame(b)
port['Sum'] = x
port = port[port.Sum == 1]
del port['Sum']
"""
Score and Weight portoflio
"""
port_returns = pd.DataFrame(np.dot(returns, port.T), index=returns.index)
port_metrics = self.ACR_get_specs(port_returns)
port_metrics = self.ACR_score(port_metrics)
port_metrics['Z3'] = port_metrics.ZMean\
-port_metrics.ZSTD\
-port_metrics.ZDownSide\
+port_metrics.ZAnnualized\
-port_metrics.ZMax_Draw\
-port_metrics.ZSTD10\
+port_metrics.ZMean10\
+port_metrics.ZMinimum15
port_metrics = port_metrics.sort_values(by='Z3', ascending=False)
portfolios = port
portfolios.columns = list(returns.columns.values)
best = pd.concat([pd.DataFrame(portfolios.iloc[port_metrics['Z3'].idxmax()]).T])
#log.info(best.loc[:, (best != 0).any(axis=0)].T)
best = pd.DataFrame(portfolios.iloc[port_metrics['Z3'].idxmax()])
#log.info(best)
ACR_assets_weight = [i[0]*(1-ACR_bonds_weight-sum(self.ACR_fixed_weight)) for i in best.values]
predictionInterval = Time.Multiply(Extensions.ToTimeSpan(self.resolution), self.days_predicted)
allocation_temp = {}
magnitude = {}
direction = {}
risk_weight = 0
for x in range(n):
allocation_temp[self.ACR_assets[x]] = ACR_assets_weight[x]#*.95
#expected_temp[self.ACR_assets[x]] =
risk_weight += ACR_assets_weight[x]
for stock in self.stocks:
magnitude[stock] = ((expected[stock]**self.days_predicted)-1.0)/5.0
#magnitude[stock] = 0.001
#algorithm.Debug(str(stock) + " magnitude = " + str(magnitude[stock]))
if magnitude[stock] > -0.01:
direction[stock] = InsightDirection.Up
else:
direction[stock] = InsightDirection.Flat
magnitude[stock] = 0.001
#self.allocation[self.Hedge[0]] = risk_weight * .05
for stock in self.ACR_bonds:
allocation_temp[stock] = ACR_bonds_data.loc[stock, 'Weight']
for x in range(len(self.ACR_fixed)):
allocation_temp[self.ACR_fixed[x]] = self.ACR_fixed_weight[x]
for symbol in self.allocation:
if (self.allocation[symbol] != allocation_temp[symbol]) or (self.allocation[symbol] != 0.0) or (allocation_temp[symbol] != 0.0):
#if allocation_temp[symbol] > self.allocation[symbol]:
self.allocation[symbol] = allocation_temp[symbol]
insights.append(Insight(symbol, predictionInterval, InsightType.Price, InsightDirection.Up, magnitude[symbol], self.allocation[symbol], sourceModel="Test"))
#algorithm.Debug(str(symbol) + " = " + str(self.allocation[symbol]))
#insights = [] #Remove me
#return insights
self.rebalancingTime = algorithm.UtcTime + self.rebalancingPeriod
return Insight.Group( insights )
def ACR_drawdown(self, returns):
mat = returns.cumprod().values
[n, m] = np.shape(mat)
maxes = np.maximum.accumulate(np.array(mat))
for i in range(0,n):
for j in range(m):
mat[i,j] = mat[i,j] / maxes[i,j]
df = pd.DataFrame(mat)
df[df > 1] = 1
return df
def ACR_moving_returns(self, returns, w):
mat = returns.values
[n, m] = np.shape(mat)
ret = np.zeros(shape = (n-w+1,m))
for i in range(w-1,n):
for j in range(m):
ret[i-w+1,j] = np.power(np.prod(mat[(i-w+1):i+1,j]),(1.0/w))- 1.0
return pd.DataFrame(ret)
def ACR_get_specs(self, returns):
metrics = pd.DataFrame((returns.mean()),columns=['Mean']) - 1.0
metrics['STD'] = pd.DataFrame((returns.std()))
metrics['Annualized'] = np.power(returns.cumprod().values.tolist()[-1],1.0/len(returns))- 1.0
downside = returns.copy(deep=True) - 1
downside[downside > 0] = 0
downside = downside ** 2
metrics['DownSide'] = pd.DataFrame(downside.mean() ** 0.5)
draw = self.ACR_drawdown(returns)
metrics['Max_Draw'] = 1.0 - draw.min().values
ret15 = self.ACR_moving_returns(returns,21)
metrics['Minimum15'] = ret15.min().values
ret10 = self.ACR_moving_returns(returns,21)
metrics['Mean10'] = ret10.mean().values
metrics['STD10'] = ret10.std().values
return metrics
def ACR_zscore(self, stocks, var, var_save):
stocks[var_save] = (stocks[var] - stocks[var].mean())/stocks[var].std(ddof=0)
return stocks
def ACR_score(self, metrics):
metrics = self.ACR_zscore(metrics, 'Mean', 'ZMean')
metrics = self.ACR_zscore(metrics, 'STD', 'ZSTD')
metrics = self.ACR_zscore(metrics, 'Annualized', 'ZAnnualized')
metrics = self.ACR_zscore(metrics, 'DownSide', 'ZDownSide')
metrics = self.ACR_zscore(metrics, 'Max_Draw', 'ZMax_Draw')
metrics = self.ACR_zscore(metrics, 'Minimum15', 'ZMinimum15')
metrics = self.ACR_zscore(metrics, 'STD10', 'ZSTD10')
metrics = self.ACR_zscore(metrics, 'Mean10', 'ZMean10')
return metrics
def OnSecuritiesChanged(self, algorithm, changes):
passfrom clr import AddReference
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")
from QuantConnect import Resolution, Extensions
from QuantConnect.Algorithm.Framework.Alphas import InsightCollection, InsightDirection
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioConstructionModel, PortfolioTarget
from itertools import groupby
from datetime import datetime
from pytz import utc
UTCMIN = datetime.min.replace(tzinfo=utc)
import numpy as np
class Confidence_Weighted_Portfolio(PortfolioConstructionModel):
def __init__(self, resolution = Resolution.Daily, reserve = 0, leverage = 1.0, num_alphas = 1.0, normalize = False):
self.insightCollection = InsightCollection()
self.removedSymbols = []
self.rebalancingTime = UTCMIN
self.rebalancingPeriod = Extensions.ToTimeSpan(resolution)
self.allocation = {}
self.reserved = reserve
self.max_leverage = leverage
self.normalize = normalize
self.num_alphas = num_alphas
self.symbol_list = []
def CreateTargets(self, algorithm, insights):
targets = []
if (algorithm.UtcTime <= self.rebalancingTime and
len(insights) == 0 and
self.removedSymbols is None):
return targets
self.insightCollection.AddRange(insights)
# Create Sell target for each security that was removed from the universe
if self.removedSymbols is not None:
universeDeselectionTargets = [ PortfolioTarget(symbol, 0) for symbol in self.removedSymbols ]
targets.extend(universeDeselectionTargets)
self.removedSymbols = None
if self.addedSymbols is not None:
for symbol in self.addedSymbols:
self.allocation[symbol] = {}
self.addedSymbols = None
# Get insight that haven't expired of each symbol that is still in the universe
activeInsights = self.insightCollection.GetActiveInsights(algorithm.UtcTime)
# Get the last generated active insight for each symbol
lastActiveInsights = []
for symbol, g in groupby(activeInsights, lambda x: x.Symbol):
lastActiveInsights.append(sorted(g, key = lambda x: x.GeneratedTimeUtc)[-1])
#Gets Adjusted Portfolio value
port_value_adjusted = (float(algorithm.Portfolio.TotalPortfolioValue) - self.reserved) * self.max_leverage
for insight in lastActiveInsights:
symbol = insight.Symbol
allocation = insight.Confidence
alpha = insight.SourceModel
#Calculate required rebalance amount for each allocation
goal_value = (port_value_adjusted * allocation)
#Adjusted % allocation
self.allocation[symbol][alpha] = goal_value/float(algorithm.Portfolio.TotalPortfolioValue)/self.num_alphas
combined_weight = {}
for symbol in self.allocation.keys():
wt = sum(list(self.allocation[symbol].values()))
combined_weight[symbol] = wt
#option to normalize target_portfolio to fill all availible leverage
if self.normalize:
original_wt = sum(np.abs(list(combined_weight.values())))
if original_wt > 0.0:
factor = self.max_leverage/original_wt
else:
factor = 0.0
for symbol in combined_weight.keys():
combined_weight[symbol] = combined_weight[symbol]*factor
for symbol in combined_weight.keys():
target = PortfolioTarget.Percent(algorithm, symbol, combined_weight[symbol])
if target != None:
targets.append(target)
#Log final desired allocation
#algorithm.Debug(str(symbol) + ". Allocation = " + str(round(self.allocation[symbol]*100,1)) + "% > " + str(int(target.Quantity)))
else:
algorithm.Debug("Target Error on " + str(symbol) + ". Should be " + str(round(combined_weight[symbol]*100,1)) + "%")
self.rebalancingTime = algorithm.UtcTime + self.rebalancingPeriod
return targets
def OnSecuritiesChanged(self, algorithm, changes):
self.addedSymbols = [x.Symbol for x in changes.AddedSecurities]
# Get removed symbol and invalidate them in the insight collection
self.removedSymbols = [x.Symbol for x in changes.RemovedSecurities]
self.insightCollection.Clear(self.removedSymbols)