Overall Statistics |
Total Trades 841 Average Win 0.53% Average Loss -0.30% Compounding Annual Return 0.891% Drawdown 5.900% Expectancy 0.078 Net Profit 9.281% Sharpe Ratio 0.231 Probabilistic Sharpe Ratio 0.057% Loss Rate 61% Win Rate 39% Profit-Loss Ratio 1.74 Alpha 0 Beta 0 Annual Standard Deviation 0.028 Annual Variance 0.001 Information Ratio 0.231 Tracking Error 0.028 Treynor Ratio 0 Total Fees $902.85 Estimated Strategy Capacity $3800000.00 Lowest Capacity Asset JNJ R735QTJ8XC9X |
#region imports from AlgorithmImports import * from statsmodels.tsa.stattools import coint import statsmodels.api as sm import statsmodels.tsa.stattools as ts #endregion def GetCointegrationFromHistory(symbols, history, pvaluefunc): pvalue_list = [] if not history.empty: close_history = history.close.unstack(level=0) close_history = close_history.dropna(axis=1, how="any") n = close_history.shape[1] keys = close_history.columns for i in range(n): for ii in range(i+1, n): stock1 = close_history[keys[i]] stock2 = close_history[keys[ii]] #Get the name of the stock 1 and 2 asset1 = keys[i] asset2 = keys[ii] #If there is nans in the frames, we continue (broken data) if stock1.hasnans or stock2.hasnans: self.Debug(f'WARNING! {asset1} and {asset2} has Nans. Did not perform coint') continue # pvalue = cointPValue(stock1, stock2) pvalue = pvaluefunc(stock1, stock2) sym1 = [item for item in symbols if (str(item.ID) == keys[i])][0] sym2 = [item for item in symbols if (str(item.ID) == keys[ii])][0] pvalue_list.append((sym1, sym2, pvalue)) pvalue_list = sorted(pvalue_list, key=lambda x: x[2]) return pvalue_list def cointPValue(stock1, stock2): result = coint(stock1, stock2) return result[1] def cadfPValue(stock1, stock2): res = sm.OLS(endog=stock2, exog=stock1).fit() beta_hr=res.params[0] residuals = stock2 - beta_hr*stock1 cadf = ts.adfuller(residuals) return cadf[1]
#region imports from AlgorithmImports import * #endregion import numpy as np from math import floor class KalmanFilter: def __init__(self): self.delta = 1e-4 self.wt = self.delta / (1 - self.delta) * np.eye(2) self.vt = 1e-3 self.theta = np.zeros(2) self.P = np.zeros((2, 2)) self.R = None self.qty = 2000 def update(self, price_one, price_two): # Create the observation matrix of the latest prices # of TLT and the intercept value (1.0) F = np.asarray([price_one, 1.0]).reshape((1, 2)) y = price_two # The prior value of the states \theta_t is # distributed as a multivariate Gaussian with # mean a_t and variance-covariance R_t if self.R is not None: self.R = self.C + self.wt else: self.R = np.zeros((2, 2)) # Calculate the Kalman Filter update # ---------------------------------- # Calculate prediction of new observation # as well as forecast error of that prediction yhat = F.dot(self.theta) et = y - yhat # Q_t is the variance of the prediction of # observations and hence \sqrt{Q_t} is the # standard deviation of the predictions Qt = F.dot(self.R).dot(F.T) + self.vt sqrt_Qt = np.sqrt(Qt) # The posterior value of the states \theta_t is # distributed as a multivariate Gaussian with mean # m_t and variance-covariance C_t At = self.R.dot(F.T) / Qt self.theta = self.theta + At.flatten() * et self.C = self.R - At * F.dot(self.R) hedge_quantity = int(floor(self.qty*self.theta[0])) return et, sqrt_Qt, hedge_quantity
# 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 AlgorithmImports import * from Alphas.BasePairsTradingAlphaModel import BasePairsTradingAlphaModel from scipy.stats import pearsonr import statsmodels.api as sm import statsmodels.tsa.stattools as ts import numpy as np from math import floor from KalmanFilter import KalmanFilter from Cointegration import GetCointegrationFromHistory, cointPValue, cadfPValue class KalmanMeanReversionTrading(BasePairsTradingAlphaModel): ''' This alpha model is designed to rank every pair combination by its cointegration tested via CADF and trade the pairs with the hightest cointegration This model generates alternating long ratio/short ratio insights emitted as a group''' def __init__(self, cointegrationLookback = 8, resolution = Resolution.Daily, maximumPValue = 0.01, tradeableUniverseSize = 1, pvaluefunc = cadfPValue): '''Initializes a new instance of the KalmanMeanReversionTrading class Args: resolution: analysis resolution maximumPValue: The minimum cointegration to consider a tradable pair tradeableUniverseSize: the nuber of pairs to be considered for creating insights ''' super().__init__(resolution = resolution) self.refreshCointegrationTime = datetime.min self.candidatesSelectionLookback = cointegrationLookback self.resolution = resolution self.maximumPValue = maximumPValue self.best_pair = () self.tradeableUniverseSize = tradeableUniverseSize self.pvaluefunc = pvaluefunc def Update(self, algorithm, data): ''' Updates this alpha model with the latest data from the algorithm. This is called each time the algorithm receives data for subscribed securities Args: algorithm: The algorithm instance data: The new data available Returns: The new insights generated''' insights = [] if not algorithm.IsWarmingUp: # goes through the list of pairs and creates insights for those for key, pair in self.pairs.items(): if data.Bars.ContainsKey(str(pair.asset1.ID)) and data.Bars.ContainsKey(str(pair.asset2.ID)): if data[str(pair.asset1.ID)].Price and data[str(pair.asset2.ID)].Price: insights.extend(pair.GetKalmanInsightGroup()) activePositions = [i.Symbol for i in algorithm.Portfolio.Values if i.Security.Holdings.Invested] if activePositions: algorithm.Debug(str(algorithm.Time)+" Active Positions "+str([i.Value for i in activePositions])) candidates = list(dict.fromkeys(list(sum(list(list(zip(*self.candidates))[:2]), ())))) outdatedPositions = [i for i in activePositions if i not in candidates] if outdatedPositions: algorithm.Debug(str(algorithm.Time)+" Outdated Assets - Positions will be closed: "+str([i.Value for i in outdatedPositions])) for i in outdatedPositions: if data.ContainsKey(str(i.ID)) and data[str(i.ID)]: if data[str(i.ID)].Price: insights.extend(Insight.Group(Insight.Price(i, timedelta(1), InsightDirection.Flat))) return insights def OnSecuritiesChanged(self, algorithm, changes): '''Event fired each time the we add/remove securities from the data feed. Args: algorithm: The algorithm instance that experienced the change in securities changes: The security additions and removals from the algorithm''' self.candidates = [] for security in changes.AddedSecurities: self.Securities.append(security) for security in changes.RemovedSecurities: if security in self.Securities: self.Securities.remove(security) symbols = [ x.Symbol for x in self.Securities ] history = algorithm.History(symbols, self.candidatesSelectionLookback, self.resolution) pvalue_list = GetCointegrationFromHistory(symbols, history, self.pvaluefunc) self.candidates = [x for x in pvalue_list if x[2]<self.maximumPValue][:self.tradeableUniverseSize] # remove "stealth" duplicates, i.e. symbols that resolve to the same ticker but with different IDs duplicates = [item for item in self.candidates if item[0].Value == item[1].Value] for x in duplicates: self.candidates.remove(x) # remove multile pairs containing the same symbol. This would otherwise confuse the trading logic, when insights # are being generated for an asset with an existing position. Keep only the pair with the lowest p-Value for item in self.candidates: l = [x for x in self.candidates if x[0]==item[0] or x[1]==item[0]] l.extend([x for x in self.candidates if x[0]==item[1] or x[1]==item[1]]) multiples = sorted([*set(l)], key=lambda x: x[2])[1:] for x in multiples: self.candidates.remove(x) universestring = "" for i in self.candidates: universestring = universestring + i[0].Value + "/" + i[1].Value + " " algorithm.Debug (str(algorithm.Time)+" Pairs found with p-Value below "+str(self.maximumPValue)+": "+str(len(self.candidates))+": "+universestring) self.UpdatePairs(algorithm) for security in changes.RemovedSecurities: keys = [k for k in self.pairs.keys() if security.Symbol in k] for key in keys: self.pairs.pop(key) def UpdatePairs(self, algorithm): # adds only the candidate pairs to the list of pairs (the ones that return HasPassedTest as True) symbols = sorted([x.Symbol for x in self.Securities], key=lambda x: str(x.ID)) for i in range(0, len(symbols)): asset_i = symbols[i] for j in range(1 + i, len(symbols)): asset_j = symbols[j] pair_symbol = (asset_i, asset_j) invert = (asset_j, asset_i) # if the pair is not in our list of candidates to trade (that's what HasPassedTest checks), # then remove the pair for the overall pairs list and skip the rest of this loop if not self.HasPassedTest(algorithm, asset_i, asset_j): # check if the pair is currently in our pairs list (i.e. it was a candidate pair before, but not anymore) # then remove the pair from our list, but only if we don't have a position open # (we want the algo to be able to create flat insights on it to close the pair!) if pair_symbol in self.pairs: if self.pairs[pair_symbol].state == 0: self.pairs.pop(pair_symbol) elif invert in self.pairs: if self.pairs[invert].state == 0: self.pairs.pop(invert) continue # to get here the pair must have already passed the test, i.e. is in our candidates list. # if it is already in our list of pairs, don't do anything further. if pair_symbol in self.pairs or invert in self.pairs: continue # this part is only reached for new pairs that have passed the candidates test. # so we create a new pair instance and add it to our list # using timedelta(1) as prediction interval seems to work well. This used to be the interval determeined in the base class BasePairsTradingAlphaModel pair = self.Pair(algorithm, asset_i, asset_j, timedelta(1), self.resolution) self.pairs[pair_symbol] = pair def HasPassedTest(self, algorithm, asset1, asset2): '''Check whether the assets pass a pairs trading test Args: algorithm: The algorithm instance that experienced the change in securities asset1: The first asset's symbol in the pair asset2: The second asset's symbol in the pair Returns: True if the pair is in the candidates list''' # all elements of self.candidates pass the test pairToTest = (asset1.Value, asset2.Value) inversePairToTest = (asset2.Value, asset1.Value) for candidate in self.candidates: if (candidate[0].Value, candidate[1].Value) == pairToTest or (candidate[0].Value, candidate[1].Value) == inversePairToTest: return True return False class Pair: class State(Enum): ShortRatio = -1 FlatRatio = 0 LongRatio = 1 def __init__(self, algorithm, asset1, asset2, predictionInterval, resolution): '''Create a new pair Args: algorithm: The algorithm instance that experienced the change in securities asset1: The first asset's symbol in the pair asset2: The second asset's symbol in the pair predictionInterval: Period over which this insight is expected to come to fruition resolution: analysis resolution ''' self.kf = KalmanFilter() self.algo = algorithm self.resolution = resolution self.state = self.State.FlatRatio self.asset1 = asset1 self.asset2 = asset2 self.asset1Price = algorithm.Identity(asset1) self.asset2Price = algorithm.Identity(asset2) self.predictionInterval = predictionInterval def GetKalmanInsightGroup(self): '''Gets the insights group for the pair Returns: Insights grouped by an unique group id''' forecast_error, prediction_std_dev, hedge_quantity = self.kf.update(self.asset1Price.Current.Price, self.asset2Price.Current.Price) # Close existing Long/Short positions if the Kalman Filter predicts reversion to the mean if (self.state is self.State.LongRatio and (forecast_error >= -prediction_std_dev)) \ or (self.state is self.State.ShortRatio and (forecast_error >= -prediction_std_dev)): self.state = self.State.FlatRatio flatAsset1 = Insight.Price(self.asset1, self.predictionInterval, InsightDirection.Flat) flatAsset2 = Insight.Price(self.asset2, self.predictionInterval, InsightDirection.Flat) return Insight.Group(flatAsset1, flatAsset2) if self.state is self.State.FlatRatio: # Open Long positions if the Kalman Filter predicts breakout from the mean to the downside if forecast_error < -prediction_std_dev: self.state = self.State.LongRatio shortAsset1 = Insight.Price(self.asset1, self.predictionInterval, InsightDirection.Down) longAsset2 = Insight.Price(self.asset2, self.predictionInterval, InsightDirection.Up) return Insight.Group(shortAsset1, longAsset2) # Open Short positions if the Kalman Filter predicts breakout from the mean to the upside elif forecast_error > prediction_std_dev: self.state = self.State.ShortRatio longAsset1 = Insight.Price(self.asset1, self.predictionInterval, InsightDirection.Up) shortAsset2 = Insight.Price(self.asset2, self.predictionInterval, InsightDirection.Down) return Insight.Group(longAsset1, shortAsset2) return []
#region imports from AlgorithmImports import * #endregion from QuantConnect import Resolution, Extensions from QuantConnect.Algorithm.Framework.Alphas import * from QuantConnect.Algorithm.Framework.Portfolio import * from itertools import groupby from datetime import datetime, timedelta from pytz import utc UTCMIN = datetime.min.replace(tzinfo=utc) #endregion class EqualWeightedPairsTradingPortfolio(PortfolioConstructionModel): def __init__(self, percentage = 1.0): self.insightCollection = InsightCollection() self.removedSymbols = [] self.percentage = percentage def CreateTargets(self, algorithm, insights): targets = [] #Get expiredInsights expiredInsights = self.insightCollection.RemoveExpiredInsights(algorithm.UtcTime) #rebalance logic if self.ShouldCreateTargets(insights, expiredInsights) == False: return targets # here we get the new insights and add them to our insight collection for insight in insights: self.insightCollection.Add(insight) # create flatten target for each security that was removed from the universe if len(self.removedSymbols) > 0: #check if the tickers is in invested, otherwise, do nothing universeDeselectionTargets = [PortfolioTarget(symbol, 0) for symbol in self.removedSymbols if algorithm.Portfolio[symbol].Invested] self.removedSymbols = [] pop_insights = [] #If we have something in the universeDeselectionTargets if universeDeselectionTargets: #loop over the targets for target in universeDeselectionTargets: #If the symbol is in our insightCollection then we have to remove that insight if self.insightCollection.ContainsKey(target.Symbol): #Get a list of the insights (there maybe more than 1) insights_list = self.insightCollection[target.Symbol] #loop over the insights for insight in insights_list: #loop over the insights in insightcollection for insightCollection in self.insightCollection: #if the insights have been send together (.GroupId), we liquidate both stocks and send the insights to a list, to remove those insights from the collection if insight.GroupId == insightCollection.GroupId: targets.extend([PortfolioTarget(insight.Symbol, 0)] + [PortfolioTarget(insightCollection.Symbol, 0)]) pop_insights.extend([insight] + [insightCollection]) for insight in pop_insights: self.insightCollection.Remove(insight) #loop over the insights. If the symbol does NOT have an active insight, we can liquidate this stock for symbol, f in groupby(expiredInsights, lambda x: x.Symbol): if not self.insightCollection.HasActiveInsights(symbol, algorithm.UtcTime): targets.append(PortfolioTarget(symbol, 0)) # get insight that have not expired of each symbol that is still in the universe activeInsights = self.insightCollection.GetActiveInsights(algorithm.UtcTime) #sort by the most recent insight generated, so it is only the first insights being generated that is being used lastActiveInsights = sorted(activeInsights, key= lambda x: x.GeneratedTimeUtc, reverse=True) #get the len of the active insights, and loop over the insights pairs = {} for i in range(len(lastActiveInsights)): for ii in range(i+1, len(lastActiveInsights)): #get the insights insight_i = lastActiveInsights[i] insight_ii = lastActiveInsights[ii] #get the pairs pairs_symbol = (insight_i.Symbol, insight_ii.Symbol) invert = (insight_ii.Symbol, insight_i.Symbol) #if the stocks is already in the pairs, continue if pairs_symbol in pairs or invert in pairs: continue #If the insights is of the same groupId, we know that these belong together, so we append to the pairs if insight_i.GroupId == insight_ii.GroupId: pairs[(pairs_symbol)] = [insight_i.Direction, insight_ii.Direction] #Here, we calculated the score of the insights calculatedTargets = {} for key, value in pairs.items(): for insight, direction in zip(key, value): if insight not in calculatedTargets: calculatedTargets[insight] = direction else: calculatedTargets[insight] += direction # determine target percent for the given insights # weightFactor = self.percentage weightFactor = 1.0 weightSums = sum(abs(direction) for symbol, direction in calculatedTargets.items()) if weightSums > 1: # CHECK THIS: weightFactor = 1 / weightSums #Send the portfolio targets out, with the correct allocation percent, and append to the targets for symbol, weight in calculatedTargets.items(): allocationPercent = weight * weightFactor * self.percentage target = PortfolioTarget.Percent(algorithm, symbol, allocationPercent) targets.append(target) # targetsymbols = [i.Symbol.Value for i in targets] # algorithm.Debug(str(algorithm.Time)+" Targets are "+str(targetsymbols)) # try: return targets # except: # algorithm.Debug(str(algorithm.Time)+" EXCEPTION WHEN RETURNING TARGETS") # return [] def OnSecuritiesChanged(self, algorithm, changes): #Get the removed symbols newRemovedSymbols = [x.Symbol for x in changes.RemovedSecurities if x.Symbol not in self.removedSymbols] # get removed symbol and invalidate them in the insight collection self.removedSymbols.extend(newRemovedSymbols) #remove insights that have not been invested in anymore not_invested_symbols = [symbol for symbol in self.removedSymbols if not algorithm.Portfolio[symbol].Invested] self.insightCollection.Clear(not_invested_symbols) def ShouldCreateTargets(self, insights, expiredInsights): if len(insights) == 0 and len(self.removedSymbols) == 0 and len(expiredInsights) == 0: return False else: return True class EqualWeightingPortfolioConstructionModelPercentage(PortfolioConstructionModel): '''Provides an implementation of IPortfolioConstructionModel that gives equal weighting to all securities. The target percent holdings of each security is 1/N where N is the number of securities. For insights of direction InsightDirection.Up, long targets are returned and for insights of direction InsightDirection.Down, short targets are returned.''' def __init__(self, rebalance = Resolution.Daily, portfolioBias = PortfolioBias.LongShort, percentage = 1.0): '''Initialize a new instance of EqualWeightingPortfolioConstructionModel Args: rebalance: Rebalancing parameter. If it is a timedelta, date rules or Resolution, it will be converted into a function. If None will be ignored. The function returns the next expected rebalance time for a given algorithm UTC DateTime. The function returns null if unknown, in which case the function will be called again in the next loop. Returning current time will trigger rebalance. portfolioBias: Specifies the bias of the portfolio (Short, Long/Short, Long)''' super().__init__() self.portfolioBias = portfolioBias self.percentage = percentage # If the argument is an instance of Resolution or Timedelta # Redefine rebalancingFunc rebalancingFunc = rebalance if isinstance(rebalance, int): rebalance = Extensions.ToTimeSpan(rebalance) if isinstance(rebalance, timedelta): rebalancingFunc = lambda dt: dt + rebalance if rebalancingFunc: self.SetRebalancingFunc(rebalancingFunc) def DetermineTargetPercent(self, activeInsights): '''Will determine the target percent for each insight Args: activeInsights: The active insights to generate a target for''' result = {} # give equal weighting to each security count = sum(x.Direction != InsightDirection.Flat and self.RespectPortfolioBias(x) for x in activeInsights) percent = 0 if count == 0 else self.percentage / count for insight in activeInsights: result[insight] = (insight.Direction if self.RespectPortfolioBias(insight) else InsightDirection.Flat) * percent return result def RespectPortfolioBias(self, insight): '''Method that will determine if a given insight respects the portfolio bias Args: insight: The insight to create a target for ''' return self.portfolioBias == PortfolioBias.LongShort or insight.Direction == self.portfolioBias
#region imports from AlgorithmImports import * #endregion from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel # bracket risk model class class BracketRiskModel(RiskManagementModel): '''Creates a trailing stop loss for the maximumDrawdownPercent value and a profit taker for the maximumUnrealizedProfitPercent value''' def __init__(self, maximumDrawdownPercent = 0.05, maximumUnrealizedProfitPercent = 0.05): self.maximumDrawdownPercent = -abs(maximumDrawdownPercent) self.trailingHighs = dict() self.maximumUnrealizedProfitPercent = abs(maximumUnrealizedProfitPercent) def ManageRisk(self, algorithm, targets): riskAdjustedTargets = list() for kvp in algorithm.Securities: symbol = kvp.Key security = kvp.Value # Remove if not invested if not security.Invested: self.trailingHighs.pop(symbol, None) continue pnl = security.Holdings.UnrealizedProfitPercent if pnl > self.maximumUnrealizedProfitPercent: # liquidate algorithm.Debug(f"Profit Taken: {security.Symbol}") algorithm.Log(f"Profit Taken: {security.Symbol}") riskAdjustedTargets.append(PortfolioTarget(security.Symbol, 0)) return riskAdjustedTargets # Add newly invested securities if symbol not in self.trailingHighs: self.trailingHighs[symbol] = security.Holdings.AveragePrice # Set to average holding cost continue # Check for new highs and update - set to tradebar high if self.trailingHighs[symbol] < security.High: self.trailingHighs[symbol] = security.High continue # Check for securities past the drawdown limit securityHigh = self.trailingHighs[symbol] drawdown = (security.Low / securityHigh) - 1 if drawdown < self.maximumDrawdownPercent: # liquidate algorithm.Debug(f"Losses Taken: {security.Symbol}") algorithm.Log(f"Losses Taken: {security.Symbol}") riskAdjustedTargets.append(PortfolioTarget(symbol, 0)) return riskAdjustedTargets
#region imports from AlgorithmImports import * from QuantConnect.Data.UniverseSelection import * from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel from statsmodels.tsa.stattools import coint import statistics import operator from Cointegration import GetCointegrationFromHistory, cointPValue, cadfPValue class StaticCategorySelection(FundamentalUniverseSelectionModel): class Category: Sectors = { "FinancialServices": MorningstarSectorCode.FinancialServices, "RealEstate": MorningstarSectorCode.RealEstate, "Healthcare": MorningstarSectorCode.Healthcare, "Utilities": MorningstarSectorCode.Utilities, "Technology": MorningstarSectorCode.Technology, "BasicMaterials": MorningstarSectorCode.BasicMaterials, "ConsumerCyclical": MorningstarSectorCode.ConsumerCyclical, "ConsumerDefensive": MorningstarSectorCode.ConsumerDefensive, "CommunicationServices": MorningstarSectorCode.CommunicationServices, "Energy": MorningstarSectorCode.Energy, "Industrials": MorningstarSectorCode.Industrials } Industries = { "Agriculture": MorningstarIndustryGroupCode.Agriculture, "BuildingMaterials": MorningstarIndustryGroupCode.BuildingMaterials, "Chemicals": MorningstarIndustryGroupCode.Chemicals, "ForestProducts": MorningstarIndustryGroupCode.ForestProducts, "MetalsAndMining": MorningstarIndustryGroupCode.MetalsAndMining, "Steel": MorningstarIndustryGroupCode.Steel, "VehiclesAndParts": MorningstarIndustryGroupCode.VehiclesAndParts, "Furnishings": MorningstarIndustryGroupCode.Furnishings, "FixturesAndAppliances": MorningstarIndustryGroupCode.FixturesAndAppliances, "HomebuildingAndConstruction": MorningstarIndustryGroupCode.HomebuildingAndConstruction, "ManufacturingApparelAndAccessories": MorningstarIndustryGroupCode.ManufacturingApparelAndAccessories, "PackagingAndContainers": MorningstarIndustryGroupCode.PackagingAndContainers, "PersonalServices": MorningstarIndustryGroupCode.PersonalServices, "Restaurants": MorningstarIndustryGroupCode.Restaurants, "RetailCyclical": MorningstarIndustryGroupCode.RetailCyclical, "TravelAndLeisure": MorningstarIndustryGroupCode.TravelAndLeisure, "AssetManagement": MorningstarIndustryGroupCode.AssetManagement, "Banks": MorningstarIndustryGroupCode.Banks, "CapitalMarkets": MorningstarIndustryGroupCode.CapitalMarkets, "Insurance": MorningstarIndustryGroupCode.Insurance, "DiversifiedFinancialServices": MorningstarIndustryGroupCode.DiversifiedFinancialServices, "CreditServices": MorningstarIndustryGroupCode.CreditServices, "RealEstateIndustry": MorningstarIndustryGroupCode.RealEstate, "REITs": MorningstarIndustryGroupCode.REITs, "BeveragesAlcoholic": MorningstarIndustryGroupCode.BeveragesAlcoholic, "BeveragesNonAlcoholic": MorningstarIndustryGroupCode.BeveragesNonAlcoholic, "ConsumerPackagedGoods": MorningstarIndustryGroupCode.ConsumerPackagedGoods, "Education": MorningstarIndustryGroupCode.Education, "RetailDefensive": MorningstarIndustryGroupCode.RetailDefensive, "TobaccoProducts": MorningstarIndustryGroupCode.TobaccoProducts, "Biotechnology": MorningstarIndustryGroupCode.Biotechnology, "DrugManufacturers": MorningstarIndustryGroupCode.DrugManufacturers, "HealthcarePlans": MorningstarIndustryGroupCode.HealthcarePlans, "HealthcareProvidersAndServices": MorningstarIndustryGroupCode.HealthcareProvidersAndServices, "MedicalDevicesAndInstruments": MorningstarIndustryGroupCode.MedicalDevicesAndInstruments, "MedicalDiagnosticsAndResearch": MorningstarIndustryGroupCode.MedicalDiagnosticsAndResearch, "MedicalDistribution": MorningstarIndustryGroupCode.MedicalDistribution, "UtilitiesIndependentPowerProducers": MorningstarIndustryGroupCode.UtilitiesIndependentPowerProducers, "UtilitiesRegulated": MorningstarIndustryGroupCode.UtilitiesRegulated, "TelecommunicationServices": MorningstarIndustryGroupCode.TelecommunicationServices, "MediaDiversified": MorningstarIndustryGroupCode.MediaDiversified, "InteractiveMedia": MorningstarIndustryGroupCode.InteractiveMedia, "OilAndGas": MorningstarIndustryGroupCode.OilAndGas, "OtherEnergySources": MorningstarIndustryGroupCode.OtherEnergySources, "AerospaceAndDefense": MorningstarIndustryGroupCode.AerospaceAndDefense, "BusinessServices": MorningstarIndustryGroupCode.BusinessServices, "Conglomerates": MorningstarIndustryGroupCode.Conglomerates, "Construction": MorningstarIndustryGroupCode.Construction, "FarmAndHeavyConstructionMachinery": MorningstarIndustryGroupCode.FarmAndHeavyConstructionMachinery, "IndustrialDistribution": MorningstarIndustryGroupCode.IndustrialDistribution, "IndustrialProducts": MorningstarIndustryGroupCode.IndustrialProducts, "Transportation": MorningstarIndustryGroupCode.Transportation, "WasteManagement": MorningstarIndustryGroupCode.WasteManagement, "Software": MorningstarIndustryGroupCode.Software, "Hardware": MorningstarIndustryGroupCode.Hardware, "Semiconductors": MorningstarIndustryGroupCode.Semiconductors } def __init__(self, rebalanceFunc, numCoarse, minmarketcap, minPrice, minVolume, category): super().__init__(filterFineData = True, universeSettings = None) self.rebalanceFunc = rebalanceFunc self.numCoarse = numCoarse self.minmarketcap = minmarketcap self.minPrice = minPrice self.minVolume = minVolume self.category = category self.nextRebalance = None def SelectCoarse(self, algorithm, coarse): #rebalance function if self.nextRebalance is not None and algorithm.Time < self.nextRebalance: return Universe.Unchanged self.nextRebalance = self.rebalanceFunc(algorithm.Time) selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > self.minPrice and x.DollarVolume > self.minVolume], key=lambda x: x.DollarVolume, reverse=True) return [x.Symbol for x in selected[:self.numCoarse]] def SelectFine(self, algorithm, fine): sorted_market_cap = [x for x in fine if x.MarketCap > self.minmarketcap] if self.category != "all": # self.category is a string with a key for either self.sectors or self.industries # the keys in the two dicts are non overlapping (after renaming 'RealEstate' to 'RealEstateIndustry' in the latter) # so that we can just switch between these two classifications by checking which dict self.category belongs to if self.category in self.Category.Sectors: selectedSymbols = [x.Symbol for x in sorted_market_cap if x.AssetClassification.MorningstarSectorCode == self.Category.Sectors[self.category]] elif self.category in self.Category.Industries: selectedSymbols = [x.Symbol for x in sorted_market_cap if x.AssetClassification.MorningstarIndustryGroupCode == self.Category.Industries[self.category]] else: selectedSymbols = [x.Symbol for x in sorted_market_cap] # if for whaetver reason the universe only contains 1 symbol, return none as we can't form any pairs if len(selectedSymbols) < 2: selectedSymbols = [] algorithm.Debug(str(algorithm.Time)+" Rebalance Done | Static Category: "+self.category+" | Number of symbols: "+str(len(selectedSymbols))+" | Next Rebalance at "+str(self.nextRebalance.date())) return selectedSymbols class DynamicCategorySelection(StaticCategorySelection): def __init__(self, rebalanceFunc, numCoarse, lookback, resolution, minmarketcap, minPrice, minVolume, category, categories, pvaluefunc, percentile): super().__init__(rebalanceFunc, numCoarse, minmarketcap, minPrice, minVolume, category) self.lookback = lookback self.resolution = resolution self.categories = categories self.pvaluefunc = pvaluefunc self.pecentile = percentile self.nextRebalance = None def SelectFine(self, algorithm, fine): sorted_market_cap = [x for x in fine if x.MarketCap > self.minmarketcap] industry_pvalue = {} if not self.categories: return [x.Symbol for x in sorted_market_cap] for industry in list(self.categories.values()): if self.categories == self.Category.Sectors: industry_list_symbol = [x.Symbol for x in sorted_market_cap if x.AssetClassification.MorningstarSectorCode == industry] else: industry_list_symbol = [x.Symbol for x in sorted_market_cap if x.AssetClassification.MorningstarIndustryGroupCode == industry] history = algorithm.History(industry_list_symbol, self.lookback, self.resolution) pvalue_list = [x[2] for x in GetCointegrationFromHistory(industry_list_symbol, history, self.pvaluefunc)] if pvalue_list: mean = statistics.fmean(pvalue_list[:math.ceil(len(pvalue_list)*self.pecentile/100)]) industry_pvalue[industry] = mean if not industry_pvalue: return [] top = min(industry_pvalue, key=industry_pvalue.get) selectedSymbols = [x.Symbol for x in fine if x.AssetClassification.MorningstarIndustryGroupCode == top] # if for whaetver reason the universe only contains 1 symbol, return none as we can't form any pairs if len(selectedSymbols) < 2: selectedSymbols = [] industryname = str(list(self.categories.keys())[list(self.categories.values()).index(top)]) algorithm.Debug(str(algorithm.Time)+" Rebalance Done | Best Category: "+industryname+" | Industry p-Value: "+str(industry_pvalue[top])+" | Number of symbols: "+str(len(selectedSymbols))+" | Next Rebalance at "+str(self.nextRebalance.date())) return selectedSymbols
# 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 AlgorithmImports import * from Alphas.BasePairsTradingAlphaModel import BasePairsTradingAlphaModel from scipy.stats import pearsonr import statsmodels.api as sm import statsmodels.tsa.stattools as ts from KalmanMeanReversionAlphaModel import KalmanMeanReversionTrading import numpy as np from math import floor from KalmanFilter import KalmanFilter from Cointegration import GetCointegrationFromHistory, cointPValue, cadfPValue class ZScoreMeanReversionTrading(KalmanMeanReversionTrading): ''' This alpha model is designed to rank every pair combination by its cointegration tested via CADF and trade the pairs with the hightest cointegration This model generates alternating long ratio/short ratio insights emitted as a group''' def __init__(self, cointegrationLookback = 8, resolution = Resolution.Daily, maximumPValue = 0.01, tradeableUniverseSize = 1, pvaluefunc = cadfPValue, lookback = 475, threshold = 1, closingThreshold = 0.0, invalidationThreshold = 10): '''Initializes a new instance of the ZScoreMeanReversionTrading class Args: resolution: analysis resolution maximumPValue: The minimum cointegration to consider a tradable pair tradeableUniverseSize: the nuber of pairs to be considered for creating insights ''' super().__init__(resolution = resolution) self.refreshCointegrationTime = datetime.min self.candidatesSelectionLookback = cointegrationLookback self.resolution = resolution self.maximumPValue = maximumPValue self.best_pair = () self.tradeableUniverseSize = tradeableUniverseSize self.pvaluefunc = pvaluefunc self.lookback = lookback self.threshold = threshold self.closingThreshold = closingThreshold self.invalidationThreshold = invalidationThreshold def Update(self, algorithm, data): ''' Updates this alpha model with the latest data from the algorithm. This is called each time the algorithm receives data for subscribed securities Args: algorithm: The algorithm instance data: The new data available Returns: The new insights generated''' insights = [] if not algorithm.IsWarmingUp: # goes through the list of pairs and creates insights for those for key, pair in self.pairs.items(): if data.Bars.ContainsKey(str(pair.asset1.ID)) and data.Bars.ContainsKey(str(pair.asset2.ID)): if data[str(pair.asset1.ID)].Price and data[str(pair.asset2.ID)].Price: insights.extend(pair.GetZScoreInsightGroup()) activePositions = [i.Symbol for i in algorithm.Portfolio.Values if i.Security.Holdings.Invested] if activePositions: algorithm.Debug(str(algorithm.Time)+" Active Positions "+str([i.Value for i in activePositions])) candidates = list(dict.fromkeys(list(sum(list(list(zip(*self.candidates))[:2]), ())))) outdatedPositions = [i for i in activePositions if i not in candidates] if outdatedPositions: algorithm.Debug(str(algorithm.Time)+" Outdated Assets - Positions will be closed: "+str([i.Value for i in outdatedPositions])) for i in outdatedPositions: if data.ContainsKey(str(i.ID)) and data[str(i.ID)]: if data[str(i.ID)].Price: insights.extend(Insight.Group(Insight.Price(i, timedelta(1), InsightDirection.Flat))) return insights def OnSecuritiesChanged(self, algorithm, changes): super().OnSecuritiesChanged(algorithm, changes) def UpdatePairs(self, algorithm): # adds only the candidate pairs to the list of pairs (the ones that return HasPassedTest as True) symbols = sorted([x.Symbol for x in self.Securities], key=lambda x: str(x.ID)) for i in range(0, len(symbols)): asset_i = symbols[i] for j in range(1 + i, len(symbols)): asset_j = symbols[j] pair_symbol = (asset_i, asset_j) invert = (asset_j, asset_i) # if the pair is not in our list of candidates to trade (that's what HasPassedTest checks), # then remove the pair for the overall pairs list and skip the rest of this loop if not self.HasPassedTest(algorithm, asset_i, asset_j): # check if the pair is currently in our pairs list (i.e. it was a candidate pair before, but not anymore) # then remove the pair from opur list, but only if we don't have a position open # (we want the algo to be able to create flat insights on it to close the pair!) if pair_symbol in self.pairs: if self.pairs[pair_symbol].state == 0: self.pairs.pop(pair_symbol) elif invert in self.pairs: if self.pairs[invert].state == 0: self.pairs.pop(invert) continue # to get here the pair must have already passed the test, i.e. is in our candidates list. # if it is already in our list of pairs, don't do anything further. if pair_symbol in self.pairs or invert in self.pairs: continue # this part is only reached for new pairs that have passed the candidates test. # so we create a new pair instance and add it to our list # using timedelta(1) as prediction interval seems to work well. This used to be the interval determeined in the base class BasePairsTradingAlphaModel pair = self.Pair(algorithm, asset_i, asset_j, timedelta(1), self.resolution, self.lookback, self.threshold, self.closingThreshold, self.invalidationThreshold) self.pairs[pair_symbol] = pair class Pair: class State(Enum): ShortRatio = -1 FlatRatio = 0 LongRatio = 1 def __init__(self, algorithm, asset1, asset2, predictionInterval, resolution, lookback, threshold, closingThreshold, invalidationThreshold): '''Create a new pair Args: algorithm: The algorithm instance that experienced the change in securities asset1: The first asset's symbol in the pair asset2: The second asset's symbol in the pair predictionInterval: Period over which this insight is expected to come to fruition resolution: analysis resolution ''' self.algo = algorithm self.resolution = resolution self.lookback = lookback self.threshold = threshold self.closingThreshold = closingThreshold self.invalidationThreshold = invalidationThreshold self.state = self.State.FlatRatio self.asset1 = asset1 self.asset2 = asset2 self.asset1Price = algorithm.Identity(asset1) self.asset2Price = algorithm.Identity(asset2) # Indicators needed for z-Score: self.ratio = IndicatorExtensions.Over(self.asset2Price, self.asset1Price) self.mean = IndicatorExtensions.Of(SimpleMovingAverage(self.lookback), self.ratio) self.std = IndicatorExtensions.Of(StandardDeviation(self.lookback), self.ratio) self.residual = IndicatorExtensions.Minus(self.ratio, self.mean) self.zscore = IndicatorExtensions.Over(self.residual,self.std) self.predictionInterval = predictionInterval def GetZScoreInsightGroup(self): '''Gets the insights group for the pair Returns: Insights grouped by an unique group id''' # close positions if ratio reverts to mean or exceeds safe area if (self.state is self.State.LongRatio and (self.zscore.Current.Value <= self.closingThreshold or self.zscore.Current.Value >= self.invalidationThreshold)) \ or (self.state is self.State.ShortRatio and (self.zscore.Current.Value >= -self.closingThreshold or self.zscore.Current.Value <= -self.invalidationThreshold)): self.state = self.State.FlatRatio flatAsset1 = Insight.Price(self.asset1, self.predictionInterval, InsightDirection.Flat) flatAsset2 = Insight.Price(self.asset2, self.predictionInterval, InsightDirection.Flat) # creates a group id and set the GroupId property on each insight object return Insight.Group(flatAsset1, flatAsset2) # don't re-emit the same direction if self.state is not self.State.LongRatio and self.zscore.Current.Value > self.threshold: self.state = self.State.LongRatio # asset1/asset2 is more than 1 std away from mean, short asset1, long asset2 shortAsset1 = Insight.Price(self.asset1, self.predictionInterval, InsightDirection.Down) longAsset2 = Insight.Price(self.asset2, self.predictionInterval, InsightDirection.Up) # creates a group id and set the GroupId property on each insight object return Insight.Group(shortAsset1, longAsset2) # don't re-emit the same direction if self.state is not self.State.ShortRatio and self.zscore.Current.Value < -self.threshold: self.state = self.State.ShortRatio # asset1/asset2 is less than 1 std away from mean, long asset1, short asset2 longAsset1 = Insight.Price(self.asset1, self.predictionInterval, InsightDirection.Up) shortAsset2 = Insight.Price(self.asset2, self.predictionInterval, InsightDirection.Down) # creates a group id and set the GroupId property on each insight object return Insight.Group(longAsset1, shortAsset2) return []
# 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 AlgorithmImports import * from KalmanMeanReversionAlphaModel import KalmanMeanReversionTrading from ZScoreMeanReversionAlphaModel import ZScoreMeanReversionTrading from PortfolioConstruction import EqualWeightedPairsTradingPortfolio from Cointegration import GetCointegrationFromHistory, cointPValue, cadfPValue from UniverseSelection import StaticCategorySelection, DynamicCategorySelection from statsmodels.tsa.stattools import coint import statistics import operator import numpy as np import math ### <summary> ### Framework algorithm that uses MeanReversionTrading. ### This model extendes BasePairsTradingAlphaModel and uses Cointegration ### to rank the pairs trading candidates and use the n best candidates to trade. ### </summary> class CointegrationPairsTradingAlphaModelFrameworkAlgorithm(QCAlgorithm): class Timeframe(Enum): Minute = 0 Hour = 1 Day = 2 Week = 3 Month = 4 Quarter = 5 class Categorisation(Enum): Sectors = 0 Industries = 1 class AlgoType: Pearson = 0 ZScore = 1 Kalman = 2 class UniverseSelectionType: Static = 0 Dynamic = 1 def Initialize(self): self.candidates = [] self.SetStartDate(2010, 1, 1) self.SetEndDate(2020, 1, 1) self.SetCash(10000) # used to iterate through all possible sectors or industry groups in an optimiser # self.categoryIndex = self.GetParameter("categoryIndex", 10) # self.category = list(self.sectors)[self.categoryIndex] # self.category = list(self.industries)[self.categoryIndex] # all other parameters as external parameters - uncomment selectively # (and comment the corresponding declaration further below) for optimiser runs # Overall parameters: # self.resolution = self.GetResolution(self.GetParameter("resolution", self.GetResolution(self.Timeframe.Hour))) # self.universeResolution = self.GetResolution(self.GetParameter("universeResolution", self.GetResolution(self.Timeframe.Hour))) # self.refreshCycle = self.GetParameter("refreshCycle", self.Timeframe.Week) # self.algoType = self.GetParameter("algoType", self.AlgoType.ZScore) # self.selectionType = self.GetParameter("selectionType", self.UniverseSelectionType.Dynamic) # self.capitalAllocation = self.GetParameter("capitalAllocation", 0.95) # Fundamental Data selection parameters: # self.minmarketcap = self.GetParameter("minmarketcap", 500000000) # self.numCoarse = self.GetParameter("numCoarse", 100) # self.minPrice = self.GetParameter("minPrice", 5) # self.minVolume = self.GetParameter("minVolume", 0) # used for Kalman and z-Score AlphaModule AND in Dynamic Universe Selection: # pvaluefunc = self.GetParameter("pvaluefunc") or "cointPValue" # if pvaluefunc == "cadfPValue": # self.pvaluefunc = cadfPValue # else: # self.pvaluefunc = cointPValue # used only for Static Universe Selection # self.category = self.GetParameter("category") or "MedicalDiagnosticsAndResearch" # used only for Dynamic Universe Selection # self.categorisation = self.GetParameter("categorisation", self.Categorisation.Industries) # self.categorySelectionLookback = self.GetParameter("categorySelectionLookback", 500) # self.percentile = self.GetParameter("percentile", 5) # Pearson Correlation only: # self.minimumCorrelation = self.GetParameter("minimumCorrelation", 0.5) # z-Score trading logic only: # self.closingThreshold = self.GetParameter("closingThreshold", 0.0) # self.invalidationThreshold = self.GetParameter("invalidationThreshold", 10) # Pearson and z-Score trading logic: # self.threshold = self.GetParameter("threshold", 1.2) # self.TradingLogicLookback = self.GetParameter("TradingLogicLookback", 520) # Kalman and z-Score trading logic: # self.candidatesSelectionLookback = self.GetParameter("candidatesSelectionLookback", 1200) # self.tradeableUniverseSize = self.GetParameter("tradeableUniverseSize", 1) # self.maximumPValue = self.GetParameter("maximumPValue", 0.01) # BEGIN ---------------------- all parameters here (comment here and uncomment external parameter declarations for optimisers) # Overall parameters: self.resolution = self.GetResolution(self.Timeframe.Hour) # resolution of bars for trading and lookbacks self.universeResolution = self.GetResolution(self.Timeframe.Hour) # Universe resolution can be set differently from the resolution used in the AlphaModule self.refreshCycle = self.Timeframe.Week # the cycle for refreshing the universe and determining which industry/sector to trade in self.algoType = self.AlgoType.ZScore # AlphaModule to use: Pearson, ZScore or Kalman self.selectionType = self.UniverseSelectionType.Dynamic # Universe Selection Static or Dynamic (refreshed periodically) self.capitalAllocation = 0.95 # percentage of available cash to be invested # Fundamental Data selection parameters: self.minmarketcap = 500000000 # the minimum market cap for a symbol to be included in the universe self.numCoarse = 100 # the top x symbols by USD Vollum to include in the universe self.minPrice = 5 # minimim stock price (to eliminate penny stocks) self.minVolume = 0 # minimum trading volume to eliminate illiquid markets # used for Kalman and z-Score AlphaModule AND in Dynamic Universe Selection: self.pvaluefunc = cadfPValue # the calculation method to determine the degree of cointegrated-ness cointPValue or cadfPValue # used only for Static Universe Selection self.category = "MedicalDiagnosticsAndResearch" # Morning Star Category (either a sector or an indusyty group), only applicable for static universe selection # used only for Dynamic Universe Selection self.categorisation = self.Categorisation.Industries # use Morning Star Sectors or Industry Groups as categorisation for the universe self.categorySelectionLookback = 500 # lookback (in bars) used in the cointegration test to determine the industry/sector self.percentile = 5 # the top x% of pairs ordered by p-Value to take the mean of in determining how cointegrated an industry/sector is # Pearson Correlation only: self.minimumCorrelation = 0.5 # the minimum correaltion for the Pearson Correlation algo to consider a pair for trading # z-Score trading logic only: self.closingThreshold = 0.0 # threshold for closing an open poition (+/- around zero) self.invalidationThreshold = 10 # how many standard deviations away from the mean until we have to assume that one stock just runs away # Pearson and z-Score trading logic: self.threshold = 1.2 # threshold to trigger a long/short position (standard deviations breakout from the ratio's mean) self.TradingLogicLookback = 1200 # lookback for ratio within the trading logic # Kalman and z-Score trading logic: self.candidatesSelectionLookback = 1200 # lookback (in bars) used in the cointegration test to determine the actual pairs to consider for trading self.tradeableUniverseSize = 1 # maximum number of pairs to consider for trading at any one time self.maximumPValue = 0.01 # maximi=um p-Value in the cointegration test for a pair to be considered a candidate for trading # END ---------------------- all parameters # BEGIN Framework options ---------------------------------------------------- # Universe selection methods: Dynamic or Static if self.selectionType == self.UniverseSelectionType.Dynamic: if self.categorisation == self.Categorisation.Sectors: categories = DynamicCategorySelection.Category.Sectors elif self.categorisation == self.Categorisation.Industries: categories = DynamicCategorySelection.Category.Industries self.AddUniverseSelection(DynamicCategorySelection(self.GetRebalanceFunc(self.refreshCycle), self.numCoarse, self.categorySelectionLookback, self.resolution, self.minmarketcap, self.minPrice, self.minVolume, self.category, categories, self.pvaluefunc, self.percentile)) else: self.AddUniverseSelection(StaticCategorySelection(self.GetRebalanceFunc(self.refreshCycle), self.numCoarse, self.minmarketcap, self.minPrice, self.minVolume, self.category)) # ---------------------------------------------------------------------------- # Alpha Models: Pearson, Kalman Filter or Z-Score based trading logic if self.algoType == self.AlgoType.Kalman: self.SetAlpha(KalmanMeanReversionTrading(self.candidatesSelectionLookback, self.resolution, self.maximumPValue, self.tradeableUniverseSize, self.pvaluefunc)) elif self.algoType == self.AlgoType.ZScore: self.SetAlpha(ZScoreMeanReversionTrading(self.candidatesSelectionLookback, self.resolution, self.maximumPValue, self.tradeableUniverseSize, self.pvaluefunc, self.TradingLogicLookback, self.threshold, self.closingThreshold, self.invalidationThreshold)) else: self.SetAlpha(PearsonCorrelationPairsTradingAlphaModel(self.TradingLogicLookback, self.resolution, self.threshold, self.minimumCorrelation)) # ---------------------------------------------------------------------------- # PCM self.SetPortfolioConstruction(EqualWeightedPairsTradingPortfolio(self.capitalAllocation/self.tradeableUniverseSize)) # ---------------------------------------------------------------------------- # Risk Model self.SetRiskManagement(NullRiskManagementModel()) # END Framework options ---------------------------------------------------- self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) self.UniverseSettings.Resolution = self.universeResolution self.refreshUniverseTime = datetime.min self.SetExecution(ImmediateExecutionModel()) self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x))) def GetResolution(self, resolution): if resolution == self.Timeframe.Minute: return Resolution.Minute elif resolution == self.Timeframe.Hour: return Resolution.Hour else: return Resolution.Daily def GetRebalanceFunc(self, refreshCycle): if refreshCycle == self.Timeframe.Week: return Expiry.EndOfWeek elif refreshCycle == self.Timeframe.Month: return Expiry.EndOfMonth elif refreshCycle == self.Timeframe.Quarter: return Expiry.EndOfQuarter else: return Expiry.EndOfDay