| Overall Statistics |
|
Total Trades 1693 Average Win 0.53% Average Loss -0.33% Compounding Annual Return 10.573% Drawdown 16.300% Expectancy 0.450 Net Profit 238.641% Sharpe Ratio 0.797 Loss Rate 45% Win Rate 55% Profit-Loss Ratio 1.62 Alpha 0.08 Beta 0.006 Annual Standard Deviation 0.101 Annual Variance 0.01 Information Ratio 0.069 Tracking Error 0.191 Treynor Ratio 12.795 Total Fees $2581.74 |
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Common")
from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data.UniverseSelection import *
import numpy as np
import itertools
from Manage_Modular import *
from Utility_Functions import *
from AlphaGenerator_SLSQP import *
from AlphaGenerator_ZScore import *
from AlphaGenerator_WVF_opt import *
from AlphaGenerator_ACR import *
from AlphaGenerator_VAA import *
class Modexvet(QCAlgorithm):
def Initialize(self):
"""
Algo Settings
"""
self.Minimum_order_value = 1000 #This reduces fees by trading only when a large reblance is needed
"""
QC Trading Settings
"""
self.AddEquity("SPY", Resolution.Minute)
StartDate = [int(x) for x in self.GetParameter("StartDate").split(',')]
EndDate = [int(x) for x in self.GetParameter("EndDate").split(',')]
self.SetCash(int(self.GetParameter("StartingCash")))
self.SetStartDate(StartDate[0],StartDate[1],StartDate[2])
self.SetEndDate(EndDate[0],EndDate[1],EndDate[2])
self.SetBenchmark("SPY")
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
self.SetWarmup(252) #252
"""
Initialization of all components of the algorithm
"""
self.Util = Utilities(self) #import Utilities functions
#Start the Portfolio Manager - Defines the allocation between the different component alpha signals
self.portfolio_manager = PortfolioManager_EquiWeight(self) #Simple even weighting of alpha signals
#Start the Execution Handler - Performs trades
self.exec_handler = ExecutionHandler_Market(self) #Simple Market orders
#Build list of alpha signals
self.alpha_slsqp = AlphaGenerator_SLSQP(self)
self.alpha_zscore = AlphaGenerator_ZScore(self)
self.alpha_WVF_opt = AlphaGenerator_WVF_opt(self)
self.alpha_ACR = AlphaGenerator_ACR(self)
self.alpha_VAA = AlphaGenerator_VAA(self)
self.portfolio_manager.create_alpha_list()
"""
Define The Fixed Universe for modular
"""
#Create a unique universe out of our combined asset lists
self.securities = ["SPY"]
self.securities += list(itertools.chain.from_iterable([alpha.stocks for alpha in self.portfolio_manager.list_alpha]))
self.securities = self.Util.Unique(self.securities)
#self.Debug(str(self.securities))
self.consolidator = TradeBarConsolidator(1)
self.consolidator.DataConsolidated += self.OnDataConsolidated
self.SubscriptionManager.AddConsolidator("SPY", self.consolidator)
for stock in self.securities:
if not stock == "SPY":
self.AddSecurity(SecurityType.Equity, stock, Resolution.Daily)
#self.AddEquity(stock, Resolution.Daily)
"""
Register Indicators
"""
for alpha in self.portfolio_manager.list_alpha:
alpha.register_indicators()
"""
Rebalance Schedule settings
"""
self.Schedule.On(self.DateRules.EveryDay("SPY"),
self.TimeRules.Every(TimeSpan.FromMinutes(60)),
Action(self.Util.my_record_vars))
self.Schedule.On(self.DateRules.EveryDay("SPY"),
self.TimeRules.AfterMarketOpen("SPY", -15),
Action(self.my_before_trading))
if self.GetParameter("WVF") == "True":
self.Schedule.On(self.DateRules.Every([DayOfWeek.Friday]),
self.TimeRules.BeforeMarketClose("SPY", 5),
Action(self.alpha_WVF_opt.WVF_opt_reset))
if self.GetParameter("VAA") == "True":
self.Schedule.On(self.DateRules.Every([DayOfWeek.Friday]),
self.TimeRules.BeforeMarketClose("SPY", 5),
Action(self.alpha_VAA.VAA_reset))
"""
Define risk management 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)
"""
Flags - Do not Change these!
"""
self.modular_rebal_needed = True
self.calculated_alphas = False
self.computed_portfolio = False
self.before_trading_success = False
"""
Temp Variables - Do not Change these!
"""
self.modular_leverage = 1.0
"""
Setup Charting
"""
stockPlot = Chart('Data Graph')
stockPlot.AddSeries(Series('Long', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Short', SeriesType.Line, 0))
stockPlot2 = Chart('Leverage')
stockPlot2.AddSeries(Series('Leverage', SeriesType.Line, 0))
self.AddChart(stockPlot)
def my_before_trading(self):
self.Util.cancel_open_orders()
#self.Log("before trading")
portfolio = self.Portfolio
portfolio_val_total = float(portfolio.TotalPortfolioValue)
#Reset Daily Flags
self.computed_portfolio = False
self.modular_rebal_needed = True
self.calculated_alphas = False
''' NOT USED
#Dynamic cash reserve
if portfolio_val_total >= 20000:
self.reserved = 11240 #Tell algo to reserve (won't trade with) this amount of cash
else:
self.reserved = 0 #Tell algo to reserve (won't trade with) this amount of cash
'''
#success
self.before_trading_success = True
"""
Timed Functions
"""
def OnDataConsolidated(self, sender, bar):
#if before_trading_start has not run due to error:
if not self.before_trading_success == True:
self.my_before_trading()
return
#Rebalance when needed:
if self.modular_rebal_needed:
self.Modular_Rebalance()
#Handles Modular Rebalance, first it calculates alphas once per minute, once all are calculated it assembles the target_portfolio, then it executes orders until all orders are satisfied.
def Modular_Rebalance(self):
if not self.calculated_alphas:
for alpha in np.setdiff1d(self.portfolio_manager.list_alpha,self.portfolio_manager.calculated):
alpha.calculate_alpha()
self.portfolio_manager.calculated.append(alpha)
return
self.calculated_alphas = True
self.portfolio_manager.calculated = []
if not self.computed_portfolio:
# compute new allocation each alpha would like
for alpha in self.portfolio_manager.list_alpha:
alpha.compute_allocation()
# compute new target portfolio
self.portfolio_manager.compute_target()
# compute order strategy
self.computed_portfolio = True
target = self.portfolio_manager.target_portfolio
self.exec_handler.execute_orders(target)"""
Misc/Utility Functions
"""
import numpy as np
class Utilities(object):
def __init__(self, data):
self.data = data
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.data.Debug("my_record_vars")
account_leverage = float(self.data.Portfolio.TotalHoldingsValue) / float(self.data.Portfolio.TotalPortfolioValue)
self.data.Plot('Leverage', 'Leverage', account_leverage)
portfolio = self.data.Portfolio
positions = portfolio.Keys
pos = 0
short = 0
for symbol in positions:
if not self.data.Securities.ContainsKey(symbol): continue
if portfolio[symbol].IsLong:
pos += 1
if portfolio[symbol].IsShort:
short += 1
self.data.Plot('Data Graph', 'Long', pos)
self.data.Plot('Data Graph', 'Short', short)
def cancel_open_orders(self):
#self.data.Debug("cancel_open_orders")
oo = [order.Symbol for order in self.data.Transactions.GetOpenOrders()]
if len(oo) == 0: return
oo = self.Unique(oo)
for symbol in oo:
if not self.data.Securities.ContainsKey(symbol): return
self.data.Transactions.CancelOpenOrders(symbol)
def cancel_open_order(self, symbol):
#self.data.Debug("cancel_open_order")
if not self.data.Securities.ContainsKey(symbol): return
oo = self.data.Transactions.GetOpenOrders(symbol)
if len(oo) == 0: return
self.data.Transactions.CancelOpenOrders(symbol)
def get_open_orders(self, symbol=None):
#self.data.Debug("get_open_orders")
orders = False
if symbol == None:
if len(self.data.Transactions.GetOpenOrders()) > 0:
orders = True
else:
if not self.data.Securities.ContainsKey(symbol):
return orders
if len(self.data.Transactions.GetOpenOrders(symbol)) > 0:
orders = True
return ordersfrom Utility_Functions import *
import numpy as np
"""
Set up Modular Framework Classes
"""
#Alpha generator module is supposed to find edge on the market and ask the portfolio manager for allocation.
class AlphaGenerator(object):
def __init__(self):
self.alloc = dict() # allocation wanted
self.stocks = [] # positions associated to the strategy
def register_indicators(self):
raise NotImplementedError()
def compute_allocation(self):
raise NotImplementedError()
def calculate_alpha(self):
raise NotImplementedError()
#Execution Handler module takes care of the strategy to reach allocation once the portfolio manager gives a target allocation.
class ExecutionHandler(object):
#make orders
def execute_orders(self, target_portfolio):
raise NotImplementedError()
#Portfolio manager module manages the portfolio on the chosen frequency.
class PortfolioManager(object):
def __init__(self):
self.target_portfolio = dict() # target_portfolio
self.list_alpha = [] # list of strategies
self.calculated = [] # Which strategies have been calculated
#Builds a list of alphas
def create_alpha_list(self):
raise NotImplementedError()
# computes the target portfolio
def compute_target(self):
raise NotImplementedError()
"""
Manage Modular Execution
"""
# PortfolioManagerEquiWeight gives equal dollar allocation to each alpha and builds a target portfolio
class PortfolioManager_EquiWeight(PortfolioManager):
def __init__(self, data):
PortfolioManager.__init__(self)
self.data = data
#Builds a list of alphas
def create_alpha_list(self):
# creation of alpha generator #1
if self.data.GetParameter("SLSQP") == "True":
self.data.Log("SLSQP ACTIVE")
self.list_alpha.append(self.data.alpha_slsqp)
# creation of alpha generator #2
if self.data.GetParameter("ZScore") == "True":
self.data.Log("ZScore ACTIVE")
self.list_alpha.append(self.data.alpha_zscore)
# creation of alpha generator #3
if self.data.GetParameter("WVF") == "True":
self.data.Log("WVF_OPT ACTIVE")
self.list_alpha.append(self.data.alpha_WVF_opt)
# creation of alpha generator #4
if self.data.GetParameter("ACR") == "True":
self.data.Log("ACR ACTIVE")
self.list_alpha.append(self.data.alpha_ACR)
# creation of alpha generator #5
if self.data.GetParameter("VAA") == "True":
self.data.Log("VAA ACTIVE")
self.list_alpha.append(self.data.alpha_VAA)
def compute_target(self):
#clear desired allocation
for stock in self.data.securities:
self.target_portfolio[stock] = 0
#update allocation for each alpha signal
for alpha in self.list_alpha:
for stock in alpha.alloc:
#Calculate the weight with simple equal weighting
nb_alpha = max(1,len(self.list_alpha)) #prevents divide by 0
alloc_alpha = alpha.alloc[stock] / nb_alpha
#update the total allocation for the stock
self.target_portfolio[stock] = self.target_portfolio[stock] + alloc_alpha
#option to normalize target_portfolio to fill all availible leverage
if self.data.normalize:
original_wt = sum(np.abs(self.target_portfolio.values()))
if original_wt > 0.0:
factor = 1.0/original_wt
else:
factor = 0.0
for stock in self.target_portfolio:
self.target_portfolio[stock] = self.target_portfolio[stock]*factor
# ExecutionHandler_Market makes market orders to reach allocation
class ExecutionHandler_Market(ExecutionHandler):
def __init__(self, data):
self.data = data
def execute_orders(self, target_portfolio):
#Gets curernt Portfolio values
portfolio = self.data.Portfolio
portfolio_val_total = float(portfolio.TotalPortfolioValue)
port_value_adjusted = (portfolio_val_total - self.data.reserved) * self.data.max_leverage
modular_leverage = (port_value_adjusted * self.data.modular_leverage)/portfolio_val_total
#Process Sells
sold = False
for stock in self.data.securities:
if self.data.Util.get_open_orders(stock):
self.data.Debug("Open Orders on: " + str(stock) + ", waiting")
self.data.modular_rebal_needed = True
return
#Calculate required rebalance amount for each allocation
current = float(self.data.Portfolio[stock].AbsoluteHoldingsValue)
goal = (port_value_adjusted*target_portfolio[stock])
goal_pct = (modular_leverage*target_portfolio[stock])
amount = (goal-current)
#Sell if needed and order exceeds minimum, always sells if allocation is 0%
if amount < 0.0 and (abs(amount) > self.data.Minimum_order_value or goal_pct == 0.0):
self.data.SetHoldings(stock, goal_pct)
sold = True
else:
continue
if sold: #wait until next minute for order execution if any stock was sold
self.data.modular_rebal_needed = True
return
#Process Buys
for stock in self.data.securities:
#Calculate required rebalance amount for each allocation
current = float(self.data.Portfolio[stock].AbsoluteHoldingsValue)
goal = (port_value_adjusted*target_portfolio[stock])
goal_pct = (modular_leverage*target_portfolio[stock])
amount = (goal-current)
#Buy if needed and order exceeds minimum
if amount > 0.0 and abs(amount) > self.data.Minimum_order_value:
self.data.SetHoldings(stock, goal_pct)
else:
continue
#Log final desired allocation
for stock in self.data.securities:
self.data.Log(str(stock) + ". Allocation = " + str(round(target_portfolio[stock]*100,1)) + "%")
self.data.modular_rebal_needed = False
self.data.Log("Modular Execution Finished")from Manage_Modular import *
from Utility_Functions import *
import numpy as np
import pandas as pd
from scipy import optimize
class AlphaGenerator_SLSQP(AlphaGenerator):
def __init__(self, data):
AlphaGenerator.__init__(self)
self.data = data
self.Util = Utilities(self.data)
#SLSQP assets
self.stocks = [
"SPY",
"QQQ",
"TLT",
"TIP",
"BND",
]
self.allocation = {}
"""
SLSQP parameters
"""
self.sqslp_days = 42
self.x1 = np.asarray([1.0/len(self.stocks) for i in self.stocks])
self.eps = 0.01
self.tol = float(1.0e-6) #assume convergence is 10 time SLSQP ftol of 1e-6
def register_indicators(self):
pass
def compute_allocation(self):
#self.data.Log("compute_allocation")
for sid in self.allocation:
self.alloc[sid] = self.allocation[sid]
def calculate_alpha(self):
prices = self.data.History(self.stocks, self.sqslp_days, Resolution.Daily)['close'].unstack(level=0)
ret = prices.pct_change()[1:].values
ret_mean = prices.pct_change().mean()
ret_std = prices.pct_change().std()
ret_norm = ret_mean/ret_std
ret_norm = ret_norm.values
ret_norm_max = np.max(ret_norm)
eps_factor = 0.9 if ret_norm_max >0 else 1.0
self.eps = eps_factor*ret_norm_max
stocks_temp = ret_mean.keys()
bnds = []
limits = [0,1] #[0,1] for long only [-1,1] for long/short
for stock in stocks_temp:
bnds.append(limits)
self.x1 = np.asarray([1.0/len(stocks_temp) for i in stocks_temp])
bnds = tuple(tuple(x) for x in bnds)
cons = ({'type': 'eq', 'fun': lambda x: np.sum(x)-1.0},
{'type': 'ineq', 'fun': lambda x: np.dot(x,ret_norm)-self.eps})
res= optimize.minimize(self.Util.Variance, self.x1, args=ret,jac=self.Util.Jac_Variance, method='SLSQP',constraints=cons,bounds=bnds)
if res.success: # if SLSQP declares success
weighted_ret_norm = np.dot(res.x,ret_norm)
w_ret_constraint = weighted_ret_norm - self.eps + self.tol
if(w_ret_constraint > 0): # and constraint is actually met
allocation = res.x
allocation[allocation<0.0] = 0.0 #Remove to allow short
factor=sum(np.abs(allocation))
if factor > 0:
allocation = allocation/factor
for i,stock in enumerate(stocks_temp):
self.allocation[stock] = allocation[i]
self.data.WVF_opt_calculated = True
else:
self.data.Log("SLSQP: constraint fail, SLSQP status = {0}".format(res.status))
for i,stock in enumerate(self.stocks):
self.allocation[stock] = 0.0
else:
self.data.Log("SLSQP: SLSQP fail, SLSQP status = {0}".format(res.status))
for i,stock in enumerate(self.stocks):
self.allocation[stock] = 0.0from Manage_Modular import *
from Utility_Functions import *
import numpy as np
import pandas as pd
import math
class AlphaGenerator_ZScore(AlphaGenerator):
def __init__(self, data):
AlphaGenerator.__init__(self)
self.data = data
#zscore assets
self.stocks = [
"SPY",
"TLT",
"XLP",
"ZIV",
]
"""
zscore parameters
"""
self.fixed_wt_pct = 0.50
self.fixed_wt = {
"XLP": 0.50,
"TLT": 0.40,
"ZIV": 0.10,
}
self.vol_factor = 0.5 #TLT = .5, SPY = 1
self.ext_factor = 4.0 #move too extreme, leave asset
self.lookback = 150
self.allocation = {}
def register_indicators(self):
pass
def compute_allocation(self):
for sid in self.allocation:
self.alloc[sid] = self.allocation[sid]
def calculate_alpha(self):
'''
for sid in self.stocks:
if not self.data.data.ContainsKey(sid):
self.data.Log("ZScore No data")
return
'''
history = self.data.History(['TLT'], self.lookback, Resolution.Daily)
mean = history['close'].mean()
sigma = history['close'].std()
price = float(self.data.Securities['TLT'].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)
for sid in self.stocks:
self.allocation[sid] = 0.0
if sid in self.fixed_wt:
self.allocation[sid] += self.fixed_wt[sid] * self.fixed_wt_pct
self.allocation["TLT"] += tlt_target * (1.0 - self.fixed_wt_pct)
self.allocation["SPY"] += spy_target * (1.0 - self.fixed_wt_pct)from Manage_Modular import *
from Utility_Functions import *
import numpy as np
import pandas as pd
from scipy import optimize
class AlphaGenerator_WVF_opt(AlphaGenerator):
def __init__(self, data):
AlphaGenerator.__init__(self)
self.data = data
self.Util = Utilities(self.data)
"""
WVF parameters
"""
self.WFV_limit = 14
self.n = 28
self.WVF_opt_calculated = False
self.allocation = {}
#WVF_opt assets
self.bull = "TQQQ"
self.bear = "TMF"
self.stocks = [
self.bull,
self.bear,
"ZIV",
]
self.vxx = self.data.AddEquity("VXX")
"""
WVF_SLSQP parameters
"""
self.sqslp_days = 17
self.x1 = np.asarray([1.0/len(self.stocks) for i in self.stocks])
self.eps = 0.01
self.tol = float(1.0e-6) #assume convergence is 10 time SLSQP ftol of 1e-6
"""
SPY_FIX parameters
"""
self.period = 28 #"LookBack Period Standard Deviation High")
self.bbl = 22 # "Bolinger Band Length")
self.mult = 1.05 # "Bollinger Band Standard Devaition Up")
self.lb = 22 # "Look Back Period Percentile High")
self.ph = .90 # "Highest Percentile - 0.90=90%, 0.95=95%, 0.99=99%")
# Criteria for Down Trend Definition for Filtered Pivots and Aggressive Filtered Pivots
self.ltLB = 40 # Long-Term Look Back Current Bar Has To Close Below This Value OR Medium Term--Default=40")
self.mtLB = 14 # Medium-Term Look Back Current Bar Has To Close Below This Value OR Long Term--Default=14")
self.Str = 3 # Entry Price Action Strength--Close > X Bars Back---Default=3")
def register_indicators(self):
pass
def compute_allocation(self):
#self.data.Log("compute_allocation")
for sid in self.allocation:
self.alloc[sid] = self.allocation[sid]
def calculate_alpha(self):
#self.data.Log("calculate_alpha")
"""
VOL_calculate
"""
history = self.data.History(["VXX"], int(self.n + 2), Resolution.Daily)
vxx_prices = history.loc["VXX"]["close"][:-1]
vxx_lows = history.loc["VXX"]["low"][:-1]
vxx_highest = vxx_prices.rolling(window = self.n, center=False).max()
# William's VIX Fix indicator a.k.a. the Synthetic VIX
WVF = ((vxx_highest - vxx_lows)/(vxx_highest)) * 100
# Sell position when WVF crosses under 14
if(WVF[-2] > self.WFV_limit and WVF[-1] <= self.WFV_limit):
self.allocation["ZIV"] = 0.0
#self.data.Log("sell XIV")
"""
TMF_calculate
"""
history = self.data.History([self.bear], 200, Resolution.Daily)
ma_tmf = history.loc[self.bear]["close"].mean()
cp_tmf = float(self.data.Securities[self.bear].Price)
if cp_tmf < ma_tmf:
self.allocation[self.bear] = 0.0
"""
Allocate stocks
"""
if not self.WVF_opt_calculated:
self.WVF_opt_allocate()
"""
SPY_FIX calculate
"""
bullish_size = self.allocation[self.bull]
bearish_size = self.allocation[self.bear]
history = self.data.History([self.bull], int((2*self.period) + 2), Resolution.Daily)
spy_close = history.loc[self.bull]["close"]
spy_lows = history.loc[self.bull]["close"]
spy_highest = spy_close.rolling(window = self.period).max()
# Williams Vix Fix Formula
WVF_s = ((spy_highest - spy_lows)/(spy_highest)) * 100
sDev = self.mult * np.std(WVF_s[-int(self.bbl):])
midLine = np.mean(WVF_s[-int(self.bbl):])
upperBand = midLine + sDev
rangeHigh = (max(WVF_s[-int(self.lb):])) * self.ph
spy_higher_then_Xdays_back = spy_close[-1] > spy_close[-int(self.Str)]
spy_lower_then_longterm = spy_close[-1] < spy_close[-int(self.ltLB)]
spy_lower_then_midterm = spy_close[-1] < spy_close[-int(self.mtLB)]
# Alerts Criteria
alert2 = not (WVF_s[-1] >= upperBand and WVF_s[-1] >= rangeHigh) and (WVF_s[-2] >= upperBand and WVF_s[-2] >= rangeHigh)
spy_change = (alert2 or spy_higher_then_Xdays_back) and (spy_lower_then_longterm or spy_lower_then_midterm)
if (spy_change and bearish_size > bullish_size) or (not spy_change and bullish_size > bearish_size):
self.allocation[self.bear] = bullish_size
self.allocation[self.bull] = bearish_size
def WVF_opt_reset(self):
#self.data.Log("WVF_opt_reset")
self.WVF_opt_calculated = False
def WVF_opt_allocate(self):
#self.data.Log("WVF_opt_allocate")
prices = self.data.History(self.stocks, self.sqslp_days, Resolution.Daily)['close'].unstack(level=0)
ret = prices.pct_change()[1:].values
ret_mean = prices.pct_change().mean()
ret_std = prices.pct_change().std()
ret_norm = ret_mean/ret_std
ret_norm = ret_norm.values
ret_norm_max = np.max(ret_norm)
eps_factor = 0.9 if ret_norm_max >0 else 1.0
self.eps = eps_factor*ret_norm_max
bnds = []
limits = [0,1] #[0,1] for long only [-1,1] for long/short
for stock in self.stocks:
bnds.append(limits)
bnds = tuple(tuple(x) for x in bnds)
cons = ({'type': 'eq', 'fun': lambda x: np.sum(x)-1.0},
{'type': 'ineq', 'fun': lambda x: np.dot(x,ret_norm)-self.eps})
res= optimize.minimize(self.Util.Variance, self.x1, args=ret,jac=self.Util.Jac_Variance, method='SLSQP',constraints=cons,bounds=bnds)
if res.success: # if SLSQP declares success
weighted_ret_norm = np.dot(res.x,ret_norm)
w_ret_constraint = weighted_ret_norm - self.eps + self.tol
if(w_ret_constraint > 0): # and constraint is actually met
allocation = res.x
allocation[allocation<0] = 0 #Remove to allow short
factor=sum(np.abs(allocation))
if factor > 0:
allocation = allocation/factor
for i,stock in enumerate(self.stocks):
self.allocation[stock] = allocation[i]
self.data.WVF_opt_calculated = True
else:
self.data.Log("WVF: constraint fail, SLSQP status = {0}".format(res.status))
for i,stock in enumerate(self.stocks):
self.allocation[stock] = 0.0
else:
self.data.Log("WVF: SLSQP fail, SLSQP status = {0}".format(res.status))
for i,stock in enumerate(self.stocks):
self.allocation[stock] = 0.0from Manage_Modular import *
from Utility_Functions import *
import numpy as np
import pandas as pd
import itertools
class AlphaGenerator_ACR(AlphaGenerator):
def __init__(self, data):
AlphaGenerator.__init__(self)
self.data = data
self.Util = Utilities(self.data)
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",]
for stock in self.ACR_sectors:
self.data.AddEquity(stock)
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
]
def register_indicators(self):
pass
def compute_allocation(self):
#self.data.Log("compute_allocation")
for sid in self.allocation:
self.alloc[sid] = self.allocation[sid]
def calculate_alpha(self):
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'] = self.data.History(self.ACR_sectors, 20, Resolution.Daily)["close"].unstack(level=0).mean()
ACR_sectors_data.loc[:, '200Day'] = self.data.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'] = self.data.History(self.ACR_bonds, 20, Resolution.Daily)["close"].unstack(level=0).mean()
ACR_bonds_data.loc[:, '60Day'] = self.data.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 = self.data.History(self.ACR_assets, 126, Resolution.Daily)["close"].unstack(level=0).dropna().pct_change().dropna() + 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 xrange(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]
risk_weight = 0
for x in range(n):
self.allocation[self.ACR_assets[x]] = ACR_assets_weight[x]#*.95
risk_weight += ACR_assets_weight[x]
#self.allocation[self.Hedge[0]] = risk_weight * .05
for stock in self.ACR_bonds:
self.allocation[stock] = ACR_bonds_data.loc[stock, 'Weight']
for x in range(len(self.ACR_fixed)):
self.allocation[self.ACR_fixed[x]] = self.ACR_fixed_weight[x]
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''' Exvet_buy is ordering stocks that we already hold positions in, need to figure out why that isn't working and what other implications it might have (positions not registering) '''
from Manage_Modular import *
from Utility_Functions import *
class AlphaGenerator_VAA(AlphaGenerator):
def __init__(self, data):
AlphaGenerator.__init__(self)
self.data = data
self.Util = Utilities(self.data)
# these are the growth symbols we'll rotate through
self.GrowthSymbols =["SPY",
"VEA",
"AGG",
"VTI",
]
# these are the safety symbols we go to when things are looking bad for growth
self.SafetySymbols = ["LQD",
"IEF",
"SHY",
"TBF",
"DBC",
"EEM",
]
#Hedge
self.Hedge = ["VXX",]
#VAA assets
self.stocks = self.GrowthSymbols + self.SafetySymbols + self.Hedge
self.allocation = {}
self.SymbolData = []
self.SafetyData = []
self.VAA_calculated = False
"""
VAA parameters
"""
self.weight1 = 12
self.weight2 = 4
self.weight3 = 2
self.weight4 = 1
def register_indicators(self):
"""
VAA Indicators
"""
# I split the indicators into two different sets to make it easier for illustrative purposes below.
# Storing all risky asset data into SymbolData object
for symbol in list(self.GrowthSymbols):
self.oneMonthPerformance = self.data.MOMP(symbol, 21, Resolution.Daily)
self.threeMonthPerformance = self.data.MOMP(symbol, 63, Resolution.Daily)
self.sixMonthPerformance = self.data.MOMP(symbol, 126, Resolution.Daily)
self.twelveMonthPerformance = self.data.MOMP(symbol, 252, Resolution.Daily)
self.SymbolData.append([symbol, self.oneMonthPerformance, self.threeMonthPerformance, self.sixMonthPerformance, self.twelveMonthPerformance])
# Storing all risk-free data into SafetyData object
for symbol in list(self.SafetySymbols):
self.oneMonthPerformance = self.data.MOMP(symbol, 21, Resolution.Daily)
self.threeMonthPerformance = self.data.MOMP(symbol, 63, Resolution.Daily)
self.sixMonthPerformance = self.data.MOMP(symbol, 126, Resolution.Daily)
self.twelveMonthPerformance = self.data.MOMP(symbol, 252, Resolution.Daily)
self.SafetyData.append([symbol, self.oneMonthPerformance, self.threeMonthPerformance, self.sixMonthPerformance, self.twelveMonthPerformance])
def compute_allocation(self):
#self.data.Log("compute_allocation")
for sid in self.allocation:
self.alloc[sid] = self.allocation[sid]
def calculate_alpha(self):
if self.VAA_calculated:
return
for sid in self.stocks:
self.allocation[sid] = 0.0
##Using the Score class at the bottom, compute the score for each risky asset.
##This approach overweights the front month momentum value and progressively underweights older momentum values
orderedObjScores = sorted(self.SymbolData, key=lambda x: self.ObjectiveScore(x[1].Current.Value,x[2].Current.Value,x[3].Current.Value,x[4].Current.Value), reverse=True)
##Using the Score class at the bottom, compute the score for each risk-free asset.
orderedSafeScores = sorted(self.SafetyData, key=lambda x: self.ObjectiveScore(x[1].Current.Value,x[2].Current.Value,x[3].Current.Value,x[4].Current.Value), reverse=True)
##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
N = 0
for x in orderedObjScores:
if self.ObjectiveScore(x[1].Current.Value,x[2].Current.Value,x[3].Current.Value,x[4].Current.Value) < 0:
N += 1
if N >= 1:
## If any of the four risky assets exhibit negative momentum scores, select the risk-free asset (LQD, IEF or SHY) with the highest score
## (regardless of whether the score is > 0) and allocate 100% of the portfolio to that asset at the close.
self.allocation[orderedSafeScores[0][0]] = 1.0
#self.allocation[self.Hedge[0]] = 0.0
else:
## If none of the risky assets come back with negative momentum scores, allocation 100% to the best scoring risky asset and hold until month end
self.allocation[orderedObjScores[0][0]] = 1.0 #.95
#self.allocation[self.Hedge[0]] = .05
self.VAA_calculated = True
def ObjectiveScore(self,oneMonthPerformanceValue,threeMonthPerformanceValue,sixMonthPerformanceValue,twelveMonthPerformanceValue):
return ((self.weight1 * oneMonthPerformanceValue)
+ (self.weight2 * threeMonthPerformanceValue)
+ (self.weight3 * sixMonthPerformanceValue)
+ (self.weight4 * twelveMonthPerformanceValue))
def VAA_reset(self):
#self.data.Log("VAA_reset")
self.VAA_calculated = False