| Overall Statistics |
|
Total Trades 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Net Profit 0% Sharpe Ratio 0 Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio 0 Tracking Error 0 Treynor Ratio 0 Total Fees $0.00 |
from System import *
from QuantConnect import *
from QuantConnect.Orders import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Execution import *
from QuantConnect.Algorithm.Framework.Risk import *
from QuantConnect.Algorithm.Framework.Selection import *
from HistoricalReturnsAlphaModel import HistoricalReturnsAlphaModel
from BlackLittermanPortfolioConstructionModel import BlackLittermanPortfolioConstructionModel
from datetime import timedelta
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from CustomFundamentalPortfolioSelectionModel import CustomFundamentalPortfolioSelectionModel
from MacdAlphaModel import MacdAlphaModel
class MeanVarianceOptimizationAlgorithm(QCAlgorithmFramework):
'''Mean Variance Optimization Algorithm.'''
def Initialize(self):
''' Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
# Set requested data resolution
self.UniverseSettings.Resolution = Resolution.Minute
# self.DebugMode = True
self.SetStartDate(2015, 10, 1) #Set Start Date
self.SetEndDate(2015, 10, 6) #Set End Date
self.SetCash(10000000) #Set Strategy Cash
# tickers = ['IWD', 'MTUM', 'IWN', 'IWM', 'EFA', 'EEM']#, 'IEF', 'SPY', 'LQD', 'TLT', 'DBC', 'GLD', 'VNQ']
# symbols = [ Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in tickers ]
self.UniverseSettings.Resolution = Resolution.Daily
self.minimum_weight = -1
self.maximum_weight = 1
# set algorithm framework models
# self.SetUniverseSelection( ManualUniverseSelectionModel(symbols) )
self.SetUniverseSelection(CustomFundamentalPortfolioSelectionModel())
# self.SetAlpha( HistoricalReturnsAlphaModel(resolution = Resolution.Daily) )
# self.SetAlpha(CompositeAlphaModel([ConstantAlphaModel(InsightType.Price, InsightDirection.Up, timedelta(minutes = 60), 0.025, None),
# HistoricalReturnsAlphaModel(resolution = Resolution.Daily),
# MacdAlphaModel()]))
self.SetAlpha(CompositeAlphaModel( [ConstantAlphaModel(InsightType.Price, InsightDirection.Up, timedelta(minutes = 60), 0.0025, None),
#ConstantAlphaModel(InsightType.Price, InsightDirection.Down, timedelta(minutes = 60), 0.001, None),]))
#MacdAlphaModel(10, 20, 6, MovingAverageType.Simple, Resolution.Daily),
HistoricalReturnsAlphaModel(resolution = Resolution.Daily),
MacdAlphaModel()]))
self.SetPortfolioConstruction( BlackLittermanPortfolioConstructionModel() )
self.SetExecution( ImmediateExecutionModel() )
self.SetRiskManagement( NullRiskManagementModel() )
#def OnOrderEvent(self, orderEvent):
# if orderEvent.Status == OrderStatus.Filled:
# self.Debug(orderEvent.ToString())
def maximum_sharpe_ratio(self, returns):
'''Maximum Sharpe Ratio optimization method'''
# Objective function
fun = lambda weights: -self.sharpe_ratio(returns, weights)
# Constraint #1: The weights can be negative, which means investors can short a security.
constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]
size = returns.columns.size
x0 = np.array(size * [1. / size])
bounds = tuple((self.minimum_weight, self.maximum_weight) for x in range(size))
opt = minimize(fun, # Objective function
x0, # Initial guess
method='SLSQP', # Optimization method: Sequential Least SQuares Programming
bounds = bounds, # Bounds for variables
constraints = constraints) # Constraints definition
weights = pd.Series(opt['x'], index = returns.columns)
return opt, weights
def sharpe_ratio(self, returns, weights):
annual_return = np.dot(np.matrix(returns.mean()), np.matrix(weights).T).item()
annual_volatility = np.sqrt(np.dot(weights.T, np.dot(returns.cov(), weights)))
return annual_return/annual_volatility# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Indicators")
from System import *
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget
from datetime import timedelta
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from itertools import groupby
from numpy import dot, transpose
from numpy.linalg import inv
### <summary>
### Provides an implementation of Mean-Variance portfolio optimization based on modern portfolio theory.
### The interval of weights in optimization method can be changed based on the long-short algorithm.
### The default model uses the last three months daily price to calculate the optimal weight
### with the weight range from -1 to 1 and minimize the portfolio variance with a target return of 2%
### </summary>
class BlackLittermanPortfolioConstructionModel:
def __init__(self, *args, **kwargs):
"""Initialize the model
Args:
lookback(int): Historical return lookback period
resolution: The resolution of the history price
minimum_weight(float): The lower bounds on portfolio weights
maximum_weight(float): The upper bounds on portfolio weights
risk_free_rate(float): The risk free rate
self.tau(float): A scalar number indicating the uncertainty of the CAPM prior """
self.lookback = kwargs['lookback'] if 'lookback' in kwargs else 63
# self.period = kwargs['period'] if 'period' in kwargs else 15
self.resolution = kwargs['resolution'] if 'resolution' in kwargs else Resolution.Daily
self.minimum_weight = kwargs['minimum_weight'] if 'minimum_weight' in kwargs else -1
self.maximum_weight = kwargs['maximum_weight'] if 'maximum_weight' in kwargs else 1
self.risk_free_rate = kwargs['risk_free_rate'] if 'risk_free_rate' in kwargs else 0
self.tau = kwargs['risk_free_rate'] if 'risk_free_rate' in kwargs else 0.025
self.symbolDataDict = {}
self.pendingRemoval = []
def CreateTargets(self, algorithm, insights):
"""
Create portfolio targets from the specified insights
Args:
algorithm: The algorithm instance
insights: The insights to create portoflio targets from
Returns:
An enumerable of portoflio targets to be sent to the execution model
"""
algorithm.Log("Begin Try Catch")
try:
algorithm.Lom("CHANGE:: Purposefully calling undefined method")
except:
algorithm.Error(str(sys.exc_info()[0]))
algorithm.Log("End Try Catch")
for symbol in self.pendingRemoval:
yield PortfolioTarget.Percent(algorithm, symbol, 0)
self.pendingRemoval.clear()
price = {}
for symbol, data in self.symbolDataDict.items():
price[str(symbol)] = data.PriceSeries()
df_price = pd.DataFrame(price)#.astype(float)
returns = df_price.pct_change().dropna()
symbols = list(returns.columns)
# market capitalization weight
W = np.array([1/len(symbols)]*len(symbols))
# annualized return
annual_return = np.sum(((1 + returns.mean())**252 - 1) * W)
algorithm.Log("annual_return "+ str(annual_return))
# annualized variance of return
annual_variance = dot(W.T, dot(returns.cov()*252, W))
# the risk aversion coefficient
risk_aversion = (annual_return - self.risk_free_rate ) / annual_variance
algorithm.Log("risk_aversion " + str(risk_aversion))
# the covariance matrix of excess returns (N x N matrix)
cov = returns.cov()*252
# the implied excess equilibrium return Vector (N x 1 column vector)
equilibrium_return = dot(dot(risk_aversion, cov), W)
algorithm.Log("equilibrium_return " + str(equilibrium_return))
# generate the link matrix of views P
view = {}
insights = sorted(insights, key = lambda x: x.SourceModel)
for model, group in groupby(insights, lambda x: x.SourceModel):
view[model] = {str(symbol): 0 for symbol in symbols}
for insight in group:
view[model][str(insight.Symbol)] = insight.Direction
view = pd.DataFrame(view).T
algorithm.Log("This is view "+str(view.values))
# normalize the view matrix by row
up_view = view[view>0].fillna(0)
down_view = view[view<0].fillna(0)
normalize_up_view = up_view.apply(lambda x: x/sum(x), axis=1).fillna(0)
normalize_down_view = down_view.apply(lambda x: -x/sum(x), axis=1).fillna(0)
# link matrix: a matrix that identifies the assets involved in the views (K x N matrix)
P = normalize_up_view + normalize_down_view
# drop the rows with all zero views (flat direction)
P = P[~(P == 0).all(axis=1)]
algorithm.Log("This is P "+str(P.values))
# the estimated return vector for every different view (K x 1 column vector)
Q = []
for model, group in groupby(insights, lambda x: x.SourceModel):
if model in P.index:
Q.append(sum([P.loc[model][str(insight.Symbol)]*insight.Magnitude for insight in group]))
P = np.array(P)
Q = np.array(Q)
algorithm.Log("This is Q "+str(Q))
# The diagonal covariance matrix of error terms from the expressed views
omega = dot(dot(dot(self.tau, P), cov), transpose(P))
algorithm.Log("This is omega "+str(omega))
if np.linalg.det(omega) == 0:
expected_return = equilibrium_return
algorithm.Log("expected return is equilibrium_return "+str(expected_return))
else:
A = inv(dot(self.tau, cov)) + dot(dot(np.transpose(P), inv(omega)), P)
B = dot(inv(dot(self.tau, cov)), equilibrium_return) + dot(dot(np.transpose(P), inv(omega)), Q)
# The new combined expected return vector
expected_return = dot(inv(A), B)
algorithm.Log("expected return "+str(expected_return))
# The optimization method processes the data frame
opt, weights = self.maximum_sharpe_ratio(expected_return, returns)
algorithm.Log("opt "+str(weights))
# Create portfolio targets from the specified insights
for insight in insights:
weight = weights[str(insight.Symbol)]
yield PortfolioTarget.Percent(algorithm, insight.Symbol, weight)
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'''
# clean up data for removed securities
for removed in changes.RemovedSecurities:
self.pendingRemoval.append(removed.Symbol)
symbolData = self.symbolDataDict.pop(removed.Symbol, None)
for symbol in self.symbolDataDict.keys():
self.symbolDataDict[symbol].Add(algorithm.Time, algorithm.Securities[symbol].Close)
# initialize data for newly added securities
symbols = [ x.Symbol for x in changes.AddedSecurities]
history = algorithm.History(symbols, self.lookback, self.resolution)
for symbol in symbols:
if symbol not in self.symbolDataDict.keys():
symbolData = SymbolData(symbol, self.lookback)
self.symbolDataDict[symbol] = symbolData
symbolData.WarmUpHisotryWindow(history.loc[str(symbol)])
def maximum_sharpe_ratio(self, expected_return, returns):
'''Maximum Sharpe Ratio optimization method'''
# Objective function
fun = lambda weights: -self.sharpe_ratio(expected_return, returns, weights)
# Constraint #1: The weights can be negative, which means investors can short a security.
constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]
size = returns.columns.size
x0 = np.array(size * [1. / size])
bounds = tuple((self.minimum_weight, self.maximum_weight) for x in range(size))
opt = minimize(fun, # Objective function
x0, # Initial guess
method='SLSQP', # Optimization method: Sequential Least SQuares Programming
bounds = bounds, # Bounds for variables
constraints = constraints) # Constraints definition
weights = pd.Series(opt['x'], index = returns.columns)
return opt, weights
def sharpe_ratio(self, expected_return, returns, weights):
annual_return = np.dot(np.matrix(expected_return), np.matrix(weights).T).item()
annual_volatility = np.sqrt(np.dot(weights.T, np.dot(returns.cov(), weights)))
return annual_return/annual_volatility
class SymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, symbol, lookback):
self.Symbol = symbol
self.Window = RollingWindow[IndicatorDataPoint](lookback)
def WarmUpHisotryWindow(self, history):
for tuple in history.itertuples():
item = IndicatorDataPoint(self.Symbol, tuple.Index, float(tuple.close))
self.Window.Add(item)
def Add(self, time, value):
item = IndicatorDataPoint(self.Symbol, time, value)
self.Window.Add(item)
def PriceSeries(self):
data = [float(x.Value) for x in self.Window]
index = [x.EndTime for x in self.Window]
return pd.Series(data, index=index)from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Indicators")
from System import *
from QuantConnect import *
from QuantConnect.Util import PythonUtil
from QuantConnect.Orders import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Execution import *
from QuantConnect.Algorithm.Framework.Portfolio import *
from QuantConnect.Algorithm.Framework.Risk import *
from QuantConnect.Algorithm.Framework.Selection import *
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Data.UniverseSelection import *
from datetime import timedelta
import numpy as np
class CustomFundamentalPortfolioSelectionModel:
def CreateUniverses(self, algorithm):
algorithm.AddUniverse(self.SelectCoarse, self.SelectFine)
return []
def SelectCoarse(self, coarse):
sortedByDollarVolume = sorted(coarse, key=lambda c: c.DollarVolume, reverse=True)
return [ x.Symbol for x in sortedByDollarVolume[:20] ]
def SelectFine(self, fine):
sortedByEarningYield = sorted(fine, key=lambda f: f.ValuationRatios.EarningYield, reverse=True)
# algorithm.Log("universe "+str([x.Symbol.Value for x in sortedByEarningYield[:5]))
return [ x.Symbol for x in sortedByEarningYield[:5] ]# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from clr import AddReference
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm.Framework.Alphas import *
from datetime import timedelta
class HistoricalReturnsAlphaModel:
'''Uses Historical returns to create insights.'''
def __init__(self, *args, **kwargs):
'''Initializes a new default instance of the HistoricalReturnsAlphaModel class.
Args:
lookback(int): Historical return lookback period
resolution: The resolution of historical data'''
self.lookback = kwargs['lookback'] if 'lookback' in kwargs else 1
self.resolution = kwargs['resolution'] if 'resolution' in kwargs else Resolution.Daily
self.predictionInterval = Time.Multiply(Extensions.ToTimeSpan(self.resolution), self.lookback)
self.symbolDataBySymbol = {}
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 = []
for symbol, symbolData in self.symbolDataBySymbol.items():
if symbolData.CanEmit:
direction = InsightDirection.Flat
magnitude = symbolData.Return
if magnitude > 0: direction = InsightDirection.Up
if magnitude < 0: direction = InsightDirection.Down
insights.append(Insight.Price(symbol, self.predictionInterval, direction, magnitude, None))
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'''
# clean up data for removed securities
for removed in changes.RemovedSecurities:
symbolData = self.symbolDataBySymbol.pop(removed.Symbol, None)
if symbolData is not None:
symbolData.RemoveConsolidators(algorithm)
# initialize data for added securities
symbols = [ x.Symbol for x in changes.AddedSecurities ]
history = algorithm.History(symbols, self.lookback, self.resolution)
if history.empty: return
tickers = history.index.levels[0]
for ticker in tickers:
symbol = SymbolCache.GetSymbol(ticker)
if symbol not in self.symbolDataBySymbol:
symbolData = SymbolData(symbol, self.lookback)
self.symbolDataBySymbol[symbol] = symbolData
symbolData.RegisterIndicators(algorithm, self.resolution)
symbolData.WarmUpIndicators(history.loc[ticker])
class SymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, symbol, lookback):
self.Symbol = symbol
self.ROC = RateOfChange('{}.ROC({})'.format(symbol, lookback), lookback)
self.Consolidator = None
self.previous = 0
def RegisterIndicators(self, algorithm, resolution):
self.Consolidator = algorithm.ResolveConsolidator(self.Symbol, resolution)
algorithm.RegisterIndicator(self.Symbol, self.ROC, self.Consolidator)
def RemoveConsolidators(self, algorithm):
if self.Consolidator is not None:
algorithm.SubscriptionManager.RemoveConsolidator(self.Symbol, self.Consolidator)
def WarmUpIndicators(self, history):
for tuple in history.itertuples():
self.ROC.Update(tuple.Index, tuple.close)
@property
def Return(self):
return float(self.ROC.Current.Value)
@property
def CanEmit(self):
if self.previous == self.ROC.Samples:
return False
self.previous = self.ROC.Samples
return self.ROC.IsReady
def __str__(self, **kwargs):
return '{}: {:.2%}'.format(self.ROC.Name, (1 + self.Return)**252 - 1)# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from clr import AddReference
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm.Framework.Alphas import *
class MacdAlphaModel:
'''Defines a custom alpha model that uses MACD crossovers. The MACD signal line
is used to generate up/down insights if it's stronger than the bounce threshold.
If the MACD signal is within the bounce threshold then a flat price insight is returned.'''
def __init__(self,
fastPeriod = 12,
slowPeriod = 26,
signalPeriod = 9,
movingAverageType = MovingAverageType.Exponential,
resolution = Resolution.Daily):
''' Initializes a new instance of the MacdAlphaModel class
Args:
fastPeriod: The MACD fast period
slowPeriod: The MACD slow period</param>
signalPeriod: The smoothing period for the MACD signal
movingAverageType: The type of moving average to use in the MACD'''
self.fastPeriod = fastPeriod
self.slowPeriod = slowPeriod
self.signalPeriod = signalPeriod
self.movingAverageType = movingAverageType
self.resolution = resolution
self.insightPeriod = Time.Multiply(Extensions.ToTimeSpan(resolution), fastPeriod)
self.bounceThresholdPercent = 0.01
self.symbolData = {};
resolutionString = Extensions.GetEnumString(resolution, Resolution)
movingAverageTypeString = Extensions.GetEnumString(movingAverageType, MovingAverageType)
self.Name = '{}({},{},{},{},{})'.format(self.__class__.__name__, fastPeriod, slowPeriod, signalPeriod, movingAverageTypeString, resolutionString)
def Update(self, algorithm, data):
''' Determines an insight for each security based on it's current MACD signal
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
for key, sd in self.symbolData.items():
if sd.Security.Price == 0:
continue
direction = InsightDirection.Flat
normalized_signal = sd.MACD.Signal.Current.Value / sd.Security.Price
# algorithm.Log(str(algorithm.Time) + ":" + str(normalized_signal) + ":" + str(sd.MACD.Signal.Current.Value))
if normalized_signal > self.bounceThresholdPercent:
direction = InsightDirection.Up
elif normalized_signal < -self.bounceThresholdPercent:
direction = InsightDirection.Down
# ignore signal for same direction as previous signal
if direction == sd.PreviousDirection:
continue
insight = Insight.Price(sd.Security.Symbol, self.insightPeriod, direction)
sd.PreviousDirection = insight.Direction
yield insight
def OnSecuritiesChanged(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed.
This initializes the MACD for each added security and cleans up the indicator for each removed security.
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
for added in changes.AddedSecurities:
self.symbolData[added.Symbol] = SymbolData(algorithm, added, self.fastPeriod, self.slowPeriod, self.signalPeriod, self.movingAverageType, self.resolution)
for removed in changes.RemovedSecurities:
data = self.symbolData.pop(removed.Symbol, None)
if data is not None:
# clean up our consolidator
algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator)
class SymbolData:
def __init__(self, algorithm, security, fastPeriod, slowPeriod, signalPeriod, movingAverageType, resolution):
self.Security = security
self.MACD = MovingAverageConvergenceDivergence(fastPeriod, slowPeriod, signalPeriod, movingAverageType)
self.Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
algorithm.RegisterIndicator(security.Symbol, self.MACD, self.Consolidator)
self.PreviousDirection = None