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 orders
from 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.0
from 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.0
from 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