chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -0,0 +1,205 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Scheduling;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of <see cref="IPortfolioConstructionModel"/> that allocates percent of account
/// to each insight, defaulting to 3%.
/// For insights of direction <see cref="InsightDirection.Up"/>, long targets are returned and
/// for insights of direction <see cref="InsightDirection.Down"/>, short targets are returned.
/// By default, no rebalancing shall be done.
/// Rules:
/// 1. On active Up insight, increase position size by percent
/// 2. On active Down insight, decrease position size by percent
/// 3. On active Flat insight, move by percent towards 0
/// 4. On expired insight, and no other active insight, emits a 0 target'''
/// </summary>
public class AccumulativeInsightPortfolioConstructionModel : PortfolioConstructionModel
{
private readonly PortfolioBias _portfolioBias;
private readonly double _percent;
/// <summary>
/// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time
/// in UTC</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="percent">The percentage amount of the portfolio value to allocate
/// to a single insight. The value of percent should be in the range [0,1].
/// The default value is 0.03.</param>
public AccumulativeInsightPortfolioConstructionModel(IDateRule rebalancingDateRules,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
double percent = 0.03)
: this(rebalancingDateRules.ToFunc(), portfolioBias, percent)
{
}
/// <summary>
/// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="percent">The percentage amount of the portfolio value to allocate
/// to a single insight. The value of percent should be in the range [0,1].
/// The default value is 0.03.</param>
public AccumulativeInsightPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc = null,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
double percent = 0.03)
: base(rebalancingFunc)
{
_portfolioBias = portfolioBias;
_percent = Math.Abs(percent);
}
/// <summary>
/// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
/// Returning current time will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="percent">The percentage amount of the portfolio value to allocate
/// to a single insight. The value of percent should be in the range [0,1].
/// The default value is 0.03.</param>
public AccumulativeInsightPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
double percent = 0.03)
: this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null,
portfolioBias,
percent)
{
}
/// <summary>
/// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
/// For a given algorithm UTC DateTime the func returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
/// constructor for the date rules parameter.
/// For performance we prefer python algorithms using the C# implementation</remarks>
/// <param name="percent">The percentage amount of the portfolio value to allocate
/// to a single insight. The value of percent should be in the range [0,1].
/// The default value is 0.03.</param>
public AccumulativeInsightPortfolioConstructionModel(PyObject rebalance,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
double percent = 0.03)
: this((Func<DateTime, DateTime?>)null,
portfolioBias,
percent)
{
SetRebalancingFunc(rebalance);
}
/// <summary>
/// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="percent">The percentage amount of the portfolio value to allocate
/// to a single insight. The value of percent should be in the range [0,1].
/// The default value is 0.03.</param>
public AccumulativeInsightPortfolioConstructionModel(TimeSpan timeSpan,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
double percent = 0.03)
: this(dt => dt.Add(timeSpan), portfolioBias, percent)
{
}
/// <summary>
/// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
/// </summary>
/// <param name="resolution">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="percent">The percentage amount of the portfolio value to allocate
/// to a single insight. The value of percent should be in the range [0,1].
/// The default value is 0.03.</param>
public AccumulativeInsightPortfolioConstructionModel(Resolution resolution,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
double percent = 0.03)
: this(resolution.ToTimeSpan(), portfolioBias, percent)
{
}
/// <summary>
/// Gets the target insights to calculate a portfolio target percent for
/// </summary>
/// <returns>An enumerable of the target insights</returns>
protected override List<Insight> GetTargetInsights()
{
return Algorithm.Insights.GetActiveInsights(Algorithm.UtcTime).Where(ShouldCreateTargetForInsight)
.OrderBy(insight => insight.GeneratedTimeUtc)
.ToList();
}
/// <summary>
/// Determines the target percent for each insight
/// </summary>
/// <param name="activeInsights">The active insights to generate a target for</param>
/// <returns>A target percent for each insight</returns>
protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
{
var percentPerSymbol = new Dictionary<Symbol, double>();
foreach (var insight in activeInsights)
{
double targetPercent;
if (percentPerSymbol.TryGetValue(insight.Symbol, out targetPercent))
{
if (insight.Direction == InsightDirection.Flat)
{
// We received a Flat
// if adding or subtracting will push past 0, then make it 0
if (Math.Abs(targetPercent) < _percent)
{
targetPercent = 0;
}
else
{
// otherwise, we flatten by percent
targetPercent += (targetPercent > 0 ? -_percent : _percent);
}
}
}
targetPercent += _percent * (int)insight.Direction;
// adjust to respect portfolio bias
if (_portfolioBias != PortfolioBias.LongShort
&& Math.Sign(targetPercent) != (int)_portfolioBias)
{
targetPercent = 0;
}
percentPerSymbol[insight.Symbol] = targetPercent;
}
return activeInsights.DistinctBy(insight => insight.Symbol)
.ToDictionary(insight => insight, insight => percentPerSymbol[insight.Symbol]);
}
}
}
@@ -0,0 +1,82 @@
# 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 EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
class AccumulativeInsightPortfolioConstructionModel(EqualWeightingPortfolioConstructionModel):
'''Provides an implementation of IPortfolioConstructionModel that allocates percent of account
to each insight, defaulting to 3%.
For insights of direction InsightDirection.UP, long targets are returned and
for insights of direction InsightDirection.DOWN, short targets are returned.
By default, no rebalancing shall be done.
Rules:
1. On active Up insight, increase position size by percent
2. On active Down insight, decrease position size by percent
3. On active Flat insight, move by percent towards 0
4. On expired insight, and no other active insight, emits a 0 target'''
def __init__(self, rebalance = None, portfolio_bias = PortfolioBias.LONG_SHORT, percent = 0.03):
'''Initialize a new instance of AccumulativeInsightPortfolioConstructionModel
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.
portfolio_bias: Specifies the bias of the portfolio (Short, Long/Short, Long)
percent: percent of portfolio to allocate to each position'''
super().__init__(rebalance)
self.portfolio_bias = portfolio_bias
self.percent = abs(percent)
self.sign = lambda x: -1 if x < 0 else (1 if x > 0 else 0)
def determine_target_percent(self, active_insights):
'''Will determine the target percent for each insight
Args:
active_insights: The active insights to generate a target for'''
percent_per_symbol = {}
insights = sorted(self.algorithm.insights.get_active_insights(self.current_utc_time), key=lambda insight: insight.generated_time_utc)
for insight in insights:
target_percent = 0
if insight.symbol in percent_per_symbol:
target_percent = percent_per_symbol[insight.symbol]
if insight.direction == InsightDirection.FLAT:
# We received a Flat
# if adding or subtracting will push past 0, then make it 0
if abs(target_percent) < self.percent:
target_percent = 0
else:
# otherwise, we flatten by percent
target_percent += (-self.percent if target_percent > 0 else self.percent)
target_percent += self.percent * insight.direction
# adjust to respect portfolio bias
if self.portfolio_bias != PortfolioBias.LONG_SHORT and self.sign(target_percent) != self.portfolio_bias:
target_percent = 0
percent_per_symbol[insight.symbol] = target_percent
return dict((insight, percent_per_symbol[insight.symbol]) for insight in active_insights)
def create_targets(self, algorithm, insights):
'''Create portfolio targets from the specified insights
Args:
algorithm: The algorithm instance
insights: The insights to create portfolio targets from
Returns:
An enumerable of portfolio targets to be sent to the execution model'''
self.current_utc_time = algorithm.utc_time
return super().create_targets(algorithm, insights)
@@ -0,0 +1,58 @@
/*
* 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.
*/
using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Algorithm.Framework.Alphas;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Base alpha streams portfolio construction model
/// </summary>
public class AlphaStreamsPortfolioConstructionModel : IPortfolioConstructionModel
{
/// <summary>
/// Get's the weight for an alpha
/// </summary>
/// <param name="alphaId">The algorithm instance that experienced the change in securities</param>
/// <returns>The alphas weight</returns>
public virtual decimal GetAlphaWeight(string alphaId)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Event fired each time the we add/remove securities from the data feed
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="changes">The security additions and removals from the algorithm</param>
public virtual void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Create portfolio targets from the specified insights
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="insights">The insights to create portfolio targets from</param>
/// <returns>An enumerable of portfolio targets to be sent to the execution model</returns>
public virtual IEnumerable<IPortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
{
throw new System.NotImplementedException();
}
}
}
@@ -0,0 +1,455 @@
/*
* 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.
*/
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using System;
using System.Collections.Generic;
using System.Linq;
using Accord.Statistics;
using Accord.Math;
using Python.Runtime;
using QuantConnect.Scheduling;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of Black-Litterman portfolio optimization. The model adjusts equilibrium market
/// returns by incorporating views from multiple alpha models and therefore to get the optimal risky portfolio
/// reflecting those views. If insights of all alpha models have None magnitude or there are linearly dependent
/// vectors in link matrix of views, the expected return would be the implied excess equilibrium return.
/// The interval of weights in optimization method can be changed based on the long-short algorithm.
/// The default model uses the 0.0025 as weight-on-views scalar parameter tau. The optimization method
/// maximizes the Sharpe ratio with the weight range from -1 to 1.
/// </summary>
public class BlackLittermanOptimizationPortfolioConstructionModel : PortfolioConstructionModel
{
private readonly IPortfolioOptimizer _optimizer;
private readonly PortfolioBias _portfolioBias;
private readonly Resolution _resolution;
private readonly double _riskFreeRate;
private readonly double _delta;
private readonly int _lookback;
private readonly double _tau;
private readonly int _period;
private readonly Dictionary<Symbol, ReturnsSymbolData> _symbolDataDict;
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="riskFreeRate">The risk free rate</param>
/// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
/// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
/// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
public BlackLittermanOptimizationPortfolioConstructionModel(TimeSpan timeSpan,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double riskFreeRate = 0.0,
double delta = 2.5,
double tau = 0.05,
IPortfolioOptimizer optimizer = null)
: this(dt => dt.Add(timeSpan), portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalanceResolution">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="riskFreeRate">The risk free rate</param>
/// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
/// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
/// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
public BlackLittermanOptimizationPortfolioConstructionModel(Resolution rebalanceResolution = Resolution.Daily,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double riskFreeRate = 0.0,
double delta = 2.5,
double tau = 0.05,
IPortfolioOptimizer optimizer = null)
: this(rebalanceResolution.ToTimeSpan(), portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
/// Returning current time will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="riskFreeRate">The risk free rate</param>
/// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
/// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
/// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
public BlackLittermanOptimizationPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double riskFreeRate = 0.0,
double delta = 2.5,
double tau = 0.05,
IPortfolioOptimizer optimizer = null)
: this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null,
portfolioBias,
lookback,
period,
resolution,
riskFreeRate,
delta,
tau,
optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time
/// in UTC</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="riskFreeRate">The risk free rate</param>
/// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
/// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
/// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
public BlackLittermanOptimizationPortfolioConstructionModel(IDateRule rebalancingDateRules,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double riskFreeRate = 0.0,
double delta = 2.5,
double tau = 0.05,
IPortfolioOptimizer optimizer = null)
: this(rebalancingDateRules.ToFunc(), portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
/// For a given algorithm UTC DateTime the func returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="riskFreeRate">The risk free rate</param>
/// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
/// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
/// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
/// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
/// constructor for the date rules parameter.
/// For performance we prefer python algorithms using the C# implementation</remarks>
public BlackLittermanOptimizationPortfolioConstructionModel(PyObject rebalance,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double riskFreeRate = 0.0,
double delta = 2.5,
double tau = 0.05,
IPortfolioOptimizer optimizer = null)
: this((Func<DateTime, DateTime?>)null, portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
{
SetRebalancingFunc(rebalance);
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance.</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="riskFreeRate">The risk free rate</param>
/// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
/// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
/// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
public BlackLittermanOptimizationPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double riskFreeRate = 0.0,
double delta = 2.5,
double tau = 0.05,
IPortfolioOptimizer optimizer = null)
: base(rebalancingFunc)
{
_lookback = lookback;
_period = period;
_resolution = resolution;
_riskFreeRate = riskFreeRate;
_delta = delta;
_tau = tau;
var lower = portfolioBias == PortfolioBias.Long ? 0 : -1;
var upper = portfolioBias == PortfolioBias.Short ? 0 : 1;
_optimizer = optimizer ?? new MaximumSharpeRatioPortfolioOptimizer(lower, upper, riskFreeRate);
_portfolioBias = portfolioBias;
_symbolDataDict = new Dictionary<Symbol, ReturnsSymbolData>();
}
/// <summary>
/// Method that will determine if the portfolio construction model should create a
/// target for this insight
/// </summary>
/// <param name="insight">The insight to create a target for</param>
/// <returns>True if the portfolio should create a target for the insight</returns>
protected override bool ShouldCreateTargetForInsight(Insight insight)
{
return FilterInvalidInsightMagnitude(Algorithm, new []{ insight }).Length != 0;
}
/// <summary>
/// Will determine the target percent for each insight
/// </summary>
/// <param name="activeInsights">The active insights to generate a target for</param>
/// <returns>A target percent for each insight</returns>
protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
{
var targets = new Dictionary<Insight, double>();
if (TryGetViews(activeInsights, out var P, out var Q))
{
// Updates the ReturnsSymbolData with insights
foreach (var insight in activeInsights)
{
if (_symbolDataDict.TryGetValue(insight.Symbol, out var symbolData))
{
if (insight.Magnitude == null)
{
Algorithm.SetRunTimeError(new ArgumentNullException("BlackLittermanOptimizationPortfolioConstructionModel does not accept \'null\' as Insight.Magnitude. Please make sure your Alpha Model is generating Insights with the Magnitude property set."));
return targets;
}
symbolData.Add(insight.GeneratedTimeUtc, insight.Magnitude.Value.SafeDecimalCast());
}
}
// Get symbols' returns
var symbols = activeInsights.Select(x => x.Symbol).Distinct().ToList();
var returns = _symbolDataDict.FormReturnsMatrix(symbols);
// Calculate posterior estimate of the mean and uncertainty in the mean
var Π = GetEquilibriumReturns(returns, out var Σ);
ApplyBlackLittermanMasterFormula(ref Π, ref Σ, P, Q);
// Create portfolio targets from the specified insights
var W = _optimizer.Optimize(returns, Π, Σ);
var sidx = 0;
foreach (var symbol in symbols)
{
var weight = W[sidx];
// don't trust the optimizer
if (_portfolioBias != PortfolioBias.LongShort
&& Math.Sign(weight) != (int)_portfolioBias)
{
weight = 0;
}
targets[activeInsights.First(insight => insight.Symbol == symbol)] = weight;
sidx++;
}
}
return targets;
}
/// <summary>
/// Gets the target insights to calculate a portfolio target percent for
/// </summary>
/// <returns>An enumerable of the target insights</returns>
protected override List<Insight> GetTargetInsights()
{
// Get insight that haven't expired of each symbol that is still in the universe
var activeInsights = Algorithm.Insights.GetActiveInsights(Algorithm.UtcTime).Where(ShouldCreateTargetForInsight);
// Get the last generated active insight for each symbol
return (from insight in activeInsights
group insight by new { insight.Symbol, insight.SourceModel } into g
select g.OrderBy(x => x.GeneratedTimeUtc).Last())
.OrderBy(x => x.Symbol).ToList();
}
/// <summary>
/// Event fired each time the we add/remove securities from the data feed
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="changes">The security additions and removals from the algorithm</param>
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
base.OnSecuritiesChanged(algorithm, changes);
foreach (var symbol in changes.RemovedSecurities.Select(x => x.Symbol))
{
if (_symbolDataDict.ContainsKey(symbol))
{
_symbolDataDict[symbol].Reset();
_symbolDataDict.Remove(symbol);
}
}
// initialize data for added securities
var addedSymbols = changes.AddedSecurities.ToDictionary(x => x.Symbol, x => x.Exchange.TimeZone);
algorithm.History(addedSymbols.Keys, _lookback * _period, _resolution)
.PushThrough(bar =>
{
ReturnsSymbolData symbolData;
if (!_symbolDataDict.TryGetValue(bar.Symbol, out symbolData))
{
symbolData = new ReturnsSymbolData(bar.Symbol, _lookback, _period);
_symbolDataDict.Add(bar.Symbol, symbolData);
}
// Convert the data timestamp to UTC
var utcTime = bar.EndTime.ConvertToUtc(addedSymbols[bar.Symbol]);
symbolData.Update(utcTime, bar.Value);
});
}
/// <summary>
/// Calculate equilibrium returns and covariance
/// </summary>
/// <param name="returns">Matrix of returns where each column represents a security and each row returns for the given date/time (size: K x N)</param>
/// <param name="Σ">Multi-dimensional array of double with the portfolio covariance of returns (size: K x K).</param>
/// <returns>Array of double of equilibrium returns</returns>
public virtual double[] GetEquilibriumReturns(double[,] returns, out double[,] Σ)
{
// equal weighting scheme
var W = Vector.Create(returns.GetLength(1), 1.0 / returns.GetLength(1));
// annualized covariance
Σ = returns.Covariance().Multiply(252);
//annualized return
var annualReturn = W.Dot(Elementwise.Add(returns.Mean(0), 1.0).Pow(252.0).Subtract(1.0));
//annualized variance of return
var annualVariance = W.Dot(Σ.Dot(W));
// the risk aversion coefficient
var riskAversion = (annualReturn - _riskFreeRate) / annualVariance;
// the implied excess equilibrium return Vector (N x 1 column vector)
return Σ.Dot(W).Multiply(riskAversion);
}
/// <summary>
/// Generate views from multiple alpha models
/// </summary>
/// <param name="insights">Array of insight that represent the investors' views</param>
/// <param name="P">A matrix that identifies the assets involved in the views (size: K x N)</param>
/// <param name="Q">A view vector (size: K x 1)</param>
protected bool TryGetViews(ICollection<Insight> insights, out double[,] P, out double[] Q)
{
try
{
var symbols = insights.Select(insight => insight.Symbol).ToHashSet();
var tmpQ = insights.GroupBy(insight => insight.SourceModel)
.Select(values =>
{
var upInsightsSum = values.Where(i => i.Direction == InsightDirection.Up).Sum(i => Math.Abs(i.Magnitude.Value));
var dnInsightsSum = values.Where(i => i.Direction == InsightDirection.Down).Sum(i => Math.Abs(i.Magnitude.Value));
return new { View = values.Key, Q = upInsightsSum > dnInsightsSum ? upInsightsSum : dnInsightsSum };
})
.Where(x => x.Q != 0)
.ToDictionary(k => k.View, v => v.Q);
var tmpP = insights.GroupBy(insight => insight.SourceModel)
.Select(values =>
{
var q = tmpQ[values.Key];
var results = values.ToDictionary(x => x.Symbol, insight =>
{
var value = (int)insight.Direction * Math.Abs(insight.Magnitude.Value);
return value / q;
});
// Add zero for other symbols that are listed but active insight
foreach (var symbol in symbols)
{
if (!results.ContainsKey(symbol))
{
results.Add(symbol, 0d);
}
}
return new { View = values.Key, Results = results };
})
.Where(r => !r.Results.Select(v => Math.Abs(v.Value)).Sum().IsNaNOrZero())
.ToDictionary(k => k.View, v => v.Results);
P = Matrix.Create(tmpP.Select(d => d.Value.Values.ToArray()).ToArray());
Q = tmpQ.Values.ToArray();
}
catch
{
P = null;
Q = null;
return false;
}
return true;
}
/// <summary>
/// Apply Black-Litterman master formula
/// http://www.blacklitterman.org/cookbook.html
/// </summary>
/// <param name="Π">Prior/Posterior mean array</param>
/// <param name="Σ">Prior/Posterior covariance matrix</param>
/// <param name="P">A matrix that identifies the assets involved in the views (size: K x N)</param>
/// <param name="Q">A view vector (size: K x 1)</param>
private void ApplyBlackLittermanMasterFormula(ref double[] Π, ref double[,] Σ, double[,] P, double[] Q)
{
// Create the diagonal covariance matrix of error terms from the expressed views
var eye = Matrix.Diagonal(Q.GetLength(0), 1);
var Ω = Elementwise.Multiply(P.Dot(Σ).DotWithTransposed(P).Multiply(_tau), eye);
if (Ω.Determinant() != 0)
{
// Define matrices Στ and A to avoid recalculations
var Στ = Σ.Multiply(_tau);
var A = Στ.DotWithTransposed(P).Dot(P.Dot(Στ).DotWithTransposed(P).Add(Ω).Inverse());
// Compute posterior estimate of the mean: Black-Litterman "master equation"
Π = Π.Add(A.Dot(Q.Subtract(P.Dot(Π))));
// Compute posterior estimate of the uncertainty in the mean
var M = Στ.Subtract(A.Dot(P).Dot(Στ));
Σ = Σ.Add(M).Multiply(_delta);
}
}
}
}
@@ -0,0 +1,310 @@
# 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 Portfolio.MaximumSharpeRatioPortfolioOptimizer import MaximumSharpeRatioPortfolioOptimizer
from itertools import groupby
from numpy import dot, transpose
from numpy.linalg import inv
### <summary>
### Provides an implementation of Black-Litterman portfolio optimization. The model adjusts equilibrium market
### returns by incorporating views from multiple alpha models and therefore to get the optimal risky portfolio
### reflecting those views. If insights of all alpha models have None magnitude or there are linearly dependent
### vectors in link matrix of views, the expected return would be the implied excess equilibrium return.
### The interval of weights in optimization method can be changed based on the long-short algorithm.
### The default model uses the 0.0025 as weight-on-views scalar parameter tau and
### MaximumSharpeRatioPortfolioOptimizer that accepts a 63-row matrix of 1-day returns.
### </summary>
class BlackLittermanOptimizationPortfolioConstructionModel(PortfolioConstructionModel):
def __init__(self,
rebalance = Resolution.DAILY,
portfolio_bias = PortfolioBias.LONG_SHORT,
lookback = 1,
period = 63,
resolution = Resolution.DAILY,
risk_free_rate = 0,
delta = 2.5,
tau = 0.05,
optimizer = None):
"""Initialize the model
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.
portfolio_bias: Specifies the bias of the portfolio (Short, Long/Short, Long)
lookback(int): Historical return lookback period
period(int): The time interval of history price to calculate the weight
resolution: The resolution of the history price
risk_free_rate(float): The risk free rate
delta(float): The risk aversion coeffficient of the market portfolio
tau(float): The model parameter indicating the uncertainty of the CAPM prior"""
super().__init__()
self.lookback = lookback
self.period = period
self.resolution = resolution
self.risk_free_rate = risk_free_rate
self.delta = delta
self.tau = tau
self.portfolio_bias = portfolio_bias
lower = 0 if portfolio_bias == PortfolioBias.LONG else -1
upper = 0 if portfolio_bias == PortfolioBias.SHORT else 1
self.optimizer = MaximumSharpeRatioPortfolioOptimizer(lower, upper, risk_free_rate) if optimizer is None else optimizer
self.sign = lambda x: -1 if x < 0 else (1 if x > 0 else 0)
self.symbol_data_by_symbol = {}
# If the argument is an instance of Resolution or Timedelta
# Redefine rebalancing_func
rebalancing_func = rebalance
if isinstance(rebalance, Resolution):
rebalance = Extensions.to_time_span(rebalance)
if isinstance(rebalance, timedelta):
rebalancing_func = lambda dt: dt + rebalance
if rebalancing_func:
self.set_rebalancing_func(rebalancing_func)
def should_create_target_for_insight(self, insight):
return PortfolioConstructionModel.filter_invalid_insight_magnitude(self.algorithm, [ insight ])
def determine_target_percent(self, last_active_insights):
targets = {}
# Get view vectors
p, q = self.get_views(last_active_insights)
if p is not None:
returns = dict()
# Updates the BlackLittermanSymbolData with insights
# Create a dictionary keyed by the symbols in the insights with an pandas.Series as value to create a data frame
for insight in last_active_insights:
symbol = insight.symbol
symbol_data = self.symbol_data_by_symbol.get(symbol, self.BlackLittermanSymbolData(symbol, self.lookback, self.period))
if insight.magnitude is None:
self.algorithm.set_run_time_error(ArgumentNullException('BlackLittermanOptimizationPortfolioConstructionModel does not accept \'None\' as Insight.magnitude. Please make sure your Alpha Model is generating Insights with the Magnitude property set.'))
return targets
symbol_data.add(insight.generated_time_utc, insight.magnitude)
returns[symbol] = symbol_data.return_
returns = pd.DataFrame(returns)
# Calculate prior estimate of the mean and covariance
pi, sigma = self.get_equilibrium_return(returns)
# Calculate posterior estimate of the mean and covariance
pi, sigma = self.apply_blacklitterman_master_formula(pi, sigma, p, q)
# Create portfolio targets from the specified insights
weights = self.optimizer.optimize(returns, pi, sigma)
weights = pd.Series(weights, index = sigma.columns)
for symbol, weight in weights.items():
for insight in last_active_insights:
if str(insight.symbol) == str(symbol):
# don't trust the optimizer
if self.portfolio_bias != PortfolioBias.LONG_SHORT and self.sign(weight) != self.portfolio_bias:
weight = 0
targets[insight] = weight
break
return targets
def get_target_insights(self):
# Get insight that haven't expired of each symbol that is still in the universe
active_insights = filter(self.should_create_target_for_insight,
self.algorithm.insights.get_active_insights(self.algorithm.utc_time))
# Get the last generated active insight for each symbol
last_active_insights = []
for source_model, f in groupby(sorted(active_insights, key = lambda ff: ff.source_model), lambda fff: fff.source_model):
for symbol, g in groupby(sorted(list(f), key = lambda gg: gg.symbol), lambda ggg: ggg.symbol):
last_active_insights.append(sorted(g, key = lambda x: x.generated_time_utc)[-1])
return last_active_insights
def on_securities_changed(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'''
# Get removed symbol and invalidate them in the insight collection
super().on_securities_changed(algorithm, changes)
for security in changes.removed_securities:
symbol = security.symbol
symbol_data = self.symbol_data_by_symbol.pop(symbol, None)
if symbol_data is not None:
symbol_data.reset()
# initialize data for added securities
added_symbols = { x.symbol: x.exchange.time_zone for x in changes.added_securities }
history = algorithm.history(list(added_symbols.keys()), self.lookback * self.period, self.resolution)
if history.empty:
return
history = history.close.unstack(0)
symbols = history.columns
for symbol, timezone in added_symbols.items():
if str(symbol) not in symbols:
continue
symbol_data = self.symbol_data_by_symbol.get(symbol, self.BlackLittermanSymbolData(symbol, self.lookback, self.period))
for time, close in history[symbol].items():
utc_time = Extensions.convert_to_utc(time, timezone)
symbol_data.update(utc_time, close)
self.symbol_data_by_symbol[symbol] = symbol_data
def apply_blacklitterman_master_formula(self, Pi, Sigma, P, Q):
'''Apply Black-Litterman master formula
http://www.blacklitterman.org/cookbook.html
Args:
Pi: Prior/Posterior mean array
Sigma: Prior/Posterior covariance matrix
P: A matrix that identifies the assets involved in the views (size: K x N)
Q: A view vector (size: K x 1)'''
ts = self.tau * Sigma
# Create the diagonal Sigma matrix of error terms from the expressed views
omega = np.dot(np.dot(P, ts), P.T) * np.eye(Q.shape[0])
if np.linalg.det(omega) == 0:
return Pi, Sigma
A = np.dot(np.dot(ts, P.T), inv(np.dot(np.dot(P, ts), P.T) + omega))
Pi = np.squeeze(np.asarray((
np.expand_dims(Pi, axis=0).T +
np.dot(A, (Q - np.expand_dims(np.dot(P, Pi.T), axis=1))))
))
M = ts - np.dot(np.dot(A, P), ts)
Sigma = (Sigma + M) * self.delta
return Pi, Sigma
def get_equilibrium_return(self, returns):
'''Calculate equilibrium returns and covariance
Args:
returns: Matrix of returns where each column represents a security and each row returns for the given date/time (size: K x N)
Returns:
equilibrium_return: Array of double of equilibrium returns
cov: Multi-dimensional array of double with the portfolio covariance of returns (size: K x K)'''
size = len(returns.columns)
# equal weighting scheme
W = np.array([1/size]*size)
# the covariance matrix of excess returns (N x N matrix)
cov = returns.cov()*252
# annualized return
annual_return = np.sum(((1 + returns.mean())**252 -1) * W)
# annualized variance of return
annual_variance = dot(W.T, dot(cov, W))
# the risk aversion coefficient
risk_aversion = (annual_return - self.risk_free_rate ) / annual_variance
# the implied excess equilibrium return Vector (N x 1 column vector)
equilibrium_return = dot(dot(risk_aversion, cov), W)
return equilibrium_return, cov
def get_views(self, insights):
'''Generate views from multiple alpha models
Args
insights: Array of insight that represent the investors' views
Returns
P: A matrix that identifies the assets involved in the views (size: K x N)
Q: A view vector (size: K x 1)'''
try:
P = {}
Q = {}
symbols = set(insight.symbol for insight in insights)
for model, group in groupby(insights, lambda x: x.source_model):
group = list(group)
up_insights_sum = 0.0
dn_insights_sum = 0.0
for insight in group:
if insight.direction == InsightDirection.UP:
up_insights_sum = up_insights_sum + np.abs(insight.magnitude)
if insight.direction == InsightDirection.DOWN:
dn_insights_sum = dn_insights_sum + np.abs(insight.magnitude)
q = up_insights_sum if up_insights_sum > dn_insights_sum else dn_insights_sum
if q == 0:
continue
Q[model] = q
# generate the link matrix of views: P
P[model] = dict()
for insight in group:
value = insight.direction * np.abs(insight.magnitude)
P[model][insight.symbol] = value / q
# Add zero for other symbols that are listed but active insight
for symbol in symbols:
if symbol not in P[model]:
P[model][symbol] = 0
Q = np.array([[x] for x in Q.values()])
if len(Q) > 0:
P = np.array([list(x.values()) for x in P.values()])
return P, Q
except:
pass
return None, None
class BlackLittermanSymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, symbol, lookback, period):
self._symbol = symbol
self.roc = RateOfChange(f'{symbol}.roc({lookback})', lookback)
self.roc.updated += self.on_rate_of_change_updated
self.window = RollingWindow(period)
def reset(self):
self.roc.updated -= self.on_rate_of_change_updated
self.roc.reset()
self.window.reset()
def update(self, utc_time, close):
self.roc.update(utc_time, close)
def on_rate_of_change_updated(self, roc, value):
if roc.is_ready:
self.window.add(value)
def add(self, time, value):
if self.window.samples > 0 and self.window[0].end_time == time:
return
item = IndicatorDataPoint(self._symbol, time, value)
self.window.add(item)
@property
def return_(self):
return pd.Series(
data = [x.value for x in self.window],
index = [x.end_time for x in self.window])
@property
def is_ready(self):
return self.window.is_ready
def __str__(self, **kwargs):
return f'{self.roc.name}: {(1 + self.window[0])**252 - 1:.2%}'
@@ -0,0 +1,129 @@
/*
* 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.
*/
using System;
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Scheduling;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of <see cref="IPortfolioConstructionModel"/> that generates percent targets based on the
/// <see cref="Insight.Confidence"/>. The target percent holdings of each Symbol is given by the <see cref="Insight.Confidence"/>
/// from the last active <see cref="Insight"/> for that symbol.
/// For insights of direction <see cref="InsightDirection.Up"/>, long targets are returned and for insights of direction
/// <see cref="InsightDirection.Down"/>, short targets are returned.
/// If the sum of all the last active <see cref="Insight"/> per symbol is bigger than 1, it will factor down each target
/// percent holdings proportionally so the sum is 1.
/// It will ignore <see cref="Insight"/> that have no <see cref="Insight.Confidence"/> value.
/// </summary>
public class ConfidenceWeightedPortfolioConstructionModel : InsightWeightingPortfolioConstructionModel
{
/// <summary>
/// Initialize a new instance of <see cref="ConfidenceWeightedPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time
/// in UTC</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public ConfidenceWeightedPortfolioConstructionModel(IDateRule rebalancingDateRules,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(rebalancingDateRules, portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="ConfidenceWeightedPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
/// For a given algorithm UTC DateTime the func returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
/// constructor for the date rules parameter.
/// For performance we prefer python algorithms using the C# implementation</remarks>
public ConfidenceWeightedPortfolioConstructionModel(PyObject rebalance,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(rebalance, portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="ConfidenceWeightedPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public ConfidenceWeightedPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(rebalancingFunc, portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="ConfidenceWeightedPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
/// Returning current time will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public ConfidenceWeightedPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(rebalancingFunc, portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="ConfidenceWeightedPortfolioConstructionModel"/>
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public ConfidenceWeightedPortfolioConstructionModel(TimeSpan timeSpan,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(timeSpan, portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="ConfidenceWeightedPortfolioConstructionModel"/>
/// </summary>
/// <param name="resolution">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public ConfidenceWeightedPortfolioConstructionModel(Resolution resolution = Resolution.Daily,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(resolution, portfolioBias)
{
}
/// <summary>
/// Method that will determine if the portfolio construction model should create a
/// target for this insight
/// </summary>
/// <param name="insight">The insight to create a target for</param>
/// <returns>True if the portfolio should create a target for the insight</returns>
protected override bool ShouldCreateTargetForInsight(Insight insight)
{
return insight.Confidence.HasValue;
}
/// <summary>
/// Method that will determine which member will be used to compute the weights and gets its value
/// </summary>
/// <param name="insight">The insight to create a target for</param>
/// <returns>The value of the selected insight member</returns>
protected override double GetValue(Insight insight) => insight.Confidence ?? 0;
}
}
@@ -0,0 +1,52 @@
# 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 InsightWeightingPortfolioConstructionModel import InsightWeightingPortfolioConstructionModel
class ConfidenceWeightedPortfolioConstructionModel(InsightWeightingPortfolioConstructionModel):
'''Provides an implementation of IPortfolioConstructionModel that generates percent targets based on the
Insight.CONFIDENCE. The target percent holdings of each Symbol is given by the Insight.CONFIDENCE from the last
active Insight for that symbol.
For insights of direction InsightDirection.UP, long targets are returned and for insights of direction
InsightDirection.DOWN, short targets are returned.
If the sum of all the last active Insight per symbol is bigger than 1, it will factor down each target
percent holdings proportionally so the sum is 1.
It will ignore Insight that have no Insight.CONFIDENCE value.'''
def __init__(self, rebalance = Resolution.DAILY, portfolio_bias = PortfolioBias.LONG_SHORT):
'''Initialize a new instance of ConfidenceWeightedPortfolioConstructionModel
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.
portfolio_bias: Specifies the bias of the portfolio (Short, Long/Short, Long)'''
super().__init__(rebalance, portfolio_bias)
def should_create_target_for_insight(self, insight):
'''Method that will determine if the portfolio construction model should create a
target for this insight
Args:
insight: The insight to create a target for'''
# Ignore insights that don't have Confidence value
return insight.confidence is not None
def get_value(self, insight):
'''Method that will determine which member will be used to compute the weights and gets its value
Args:
insight: The insight to create a target for
Returns:
The value of the selected insight member'''
return insight.confidence
@@ -0,0 +1,144 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Scheduling;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of <see cref="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 <see cref="InsightDirection.Up"/>, long targets are returned and for insights of direction
/// <see cref="InsightDirection.Down"/>, short targets are returned.
/// </summary>
public class EqualWeightingPortfolioConstructionModel : PortfolioConstructionModel
{
private readonly PortfolioBias _portfolioBias;
/// <summary>
/// Initialize a new instance of <see cref="EqualWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time
/// in UTC</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public EqualWeightingPortfolioConstructionModel(IDateRule rebalancingDateRules,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: this(rebalancingDateRules.ToFunc(), portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="EqualWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public EqualWeightingPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(rebalancingFunc)
{
_portfolioBias = portfolioBias;
}
/// <summary>
/// Initialize a new instance of <see cref="EqualWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
/// Returning current time will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public EqualWeightingPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null, portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="EqualWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
/// For a given algorithm UTC DateTime the func returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
/// constructor for the date rules parameter.
/// For performance we prefer python algorithms using the C# implementation</remarks>
public EqualWeightingPortfolioConstructionModel(PyObject rebalance,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: this((Func<DateTime, DateTime?>)null, portfolioBias)
{
SetRebalancingFunc(rebalance);
}
/// <summary>
/// Initialize a new instance of <see cref="EqualWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public EqualWeightingPortfolioConstructionModel(TimeSpan timeSpan,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: this(dt => dt.Add(timeSpan), portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="EqualWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="resolution">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public EqualWeightingPortfolioConstructionModel(Resolution resolution = Resolution.Daily,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: this(resolution.ToTimeSpan(), portfolioBias)
{
}
/// <summary>
/// Will determine the target percent for each insight
/// </summary>
/// <param name="activeInsights">The active insights to generate a target for</param>
/// <returns>A target percent for each insight</returns>
protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
{
var result = new Dictionary<Insight, double>(activeInsights.Count);
// give equal weighting to each security
var count = activeInsights.Count(x => x.Direction != InsightDirection.Flat && RespectPortfolioBias(x));
var percent = count == 0 ? 0 : 1m / count;
foreach (var insight in activeInsights)
{
result[insight] =
(double)((int)(RespectPortfolioBias(insight) ? insight.Direction : InsightDirection.Flat)
* percent);
}
return result;
}
/// <summary>
/// Method that will determine if a given insight respects the portfolio bias
/// </summary>
/// <param name="insight">The insight to create a target for</param>
/// <returns>True if the insight respects the portfolio bias</returns>
protected bool RespectPortfolioBias(Insight insight)
{
return _portfolioBias == PortfolioBias.LongShort || (int)insight.Direction == (int)_portfolioBias;
}
}
}
@@ -0,0 +1,62 @@
# 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 *
class EqualWeightingPortfolioConstructionModel(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, portfolio_bias = PortfolioBias.LONG_SHORT):
'''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.
portfolio_bias: Specifies the bias of the portfolio (Short, Long/Short, Long)'''
super().__init__()
self.portfolio_bias = portfolio_bias
# If the argument is an instance of Resolution or Timedelta
# Redefine rebalancing_func
rebalancing_func = rebalance
if isinstance(rebalance, Resolution):
rebalance = Extensions.to_time_span(rebalance)
if isinstance(rebalance, timedelta):
rebalancing_func = lambda dt: dt + rebalance
if rebalancing_func:
self.set_rebalancing_func(rebalancing_func)
def determine_target_percent(self, active_insights):
'''Will determine the target percent for each insight
Args:
active_insights: The active insights to generate a target for'''
result = {}
# give equal weighting to each security
count = sum(x.direction != InsightDirection.FLAT and self.respect_portfolio_bias(x) for x in active_insights)
percent = 0 if count == 0 else 1.0 / count
for insight in active_insights:
result[insight] = (insight.direction if self.respect_portfolio_bias(insight) else InsightDirection.FLAT) * percent
return result
def respect_portfolio_bias(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.portfolio_bias == PortfolioBias.LONG_SHORT or insight.direction == self.portfolio_bias
@@ -0,0 +1,155 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Scheduling;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of <see cref="IPortfolioConstructionModel"/> that generates percent targets based on the
/// <see cref="Insight.Weight"/>. The target percent holdings of each Symbol is given by the <see cref="Insight.Weight"/>
/// from the last active <see cref="Insight"/> for that symbol.
/// For insights of direction <see cref="InsightDirection.Up"/>, long targets are returned and for insights of direction
/// <see cref="InsightDirection.Down"/>, short targets are returned.
/// If the sum of all the last active <see cref="Insight"/> per symbol is bigger than 1, it will factor down each target
/// percent holdings proportionally so the sum is 1.
/// It will ignore <see cref="Insight"/> that have no <see cref="Insight.Weight"/> value.
/// </summary>
public class InsightWeightingPortfolioConstructionModel : EqualWeightingPortfolioConstructionModel
{
/// <summary>
/// Initialize a new instance of <see cref="InsightWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time
/// in UTC</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public InsightWeightingPortfolioConstructionModel(IDateRule rebalancingDateRules,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(rebalancingDateRules, portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="InsightWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
/// For a given algorithm UTC DateTime the func returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
/// constructor for the date rules parameter.
/// For performance we prefer python algorithms using the C# implementation</remarks>
public InsightWeightingPortfolioConstructionModel(PyObject rebalance,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(rebalance, portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="InsightWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance.</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public InsightWeightingPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(rebalancingFunc, portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="InsightWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
/// Returning current time will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public InsightWeightingPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(rebalancingFunc, portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="InsightWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public InsightWeightingPortfolioConstructionModel(TimeSpan timeSpan,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(timeSpan, portfolioBias)
{
}
/// <summary>
/// Initialize a new instance of <see cref="InsightWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="resolution">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
public InsightWeightingPortfolioConstructionModel(Resolution resolution = Resolution.Daily,
PortfolioBias portfolioBias = PortfolioBias.LongShort)
: base(resolution, portfolioBias)
{
}
/// <summary>
/// Method that will determine if the portfolio construction model should create a
/// target for this insight
/// </summary>
/// <param name="insight">The insight to create a target for</param>
/// <returns>True if the portfolio should create a target for the insight</returns>
protected override bool ShouldCreateTargetForInsight(Insight insight)
{
return insight.Weight.HasValue;
}
/// <summary>
/// Will determine the target percent for each insight
/// </summary>
/// <param name="activeInsights">The active insights to generate a target for</param>
/// <returns>A target percent for each insight</returns>
protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
{
var result = new Dictionary<Insight, double>();
// We will adjust weights proportionally in case the sum is > 1 so it sums to 1.
var weightSums = activeInsights.Where(RespectPortfolioBias).Sum(insight => GetValue(insight));
var weightFactor = 1.0;
if (weightSums > 1)
{
weightFactor = 1 / weightSums;
}
foreach (var insight in activeInsights)
{
result[insight] = (int)(RespectPortfolioBias(insight) ? insight.Direction : InsightDirection.Flat)
* GetValue(insight)
* weightFactor;
}
return result;
}
/// <summary>
/// Method that will determine which member will be used to compute the weights and gets its value
/// </summary>
/// <param name="insight">The insight to create a target for</param>
/// <returns>The value of the selected insight member</returns>
protected virtual double GetValue(Insight insight) => insight.Weight != null ? Math.Abs((double)insight.Weight) : 0;
}
}
@@ -0,0 +1,67 @@
# 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 EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
class InsightWeightingPortfolioConstructionModel(EqualWeightingPortfolioConstructionModel):
'''Provides an implementation of IPortfolioConstructionModel that generates percent targets based on the
Insight.WEIGHT. The target percent holdings of each Symbol is given by the Insight.WEIGHT from the last
active Insight for that symbol.
For insights of direction InsightDirection.UP, long targets are returned and for insights of direction
InsightDirection.DOWN, short targets are returned.
If the sum of all the last active Insight per symbol is bigger than 1, it will factor down each target
percent holdings proportionally so the sum is 1.
It will ignore Insight that have no Insight.WEIGHT value.'''
def __init__(self, rebalance = Resolution.DAILY, portfolio_bias = PortfolioBias.LONG_SHORT):
'''Initialize a new instance of InsightWeightingPortfolioConstructionModel
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.
portfolio_bias: Specifies the bias of the portfolio (Short, Long/Short, Long)'''
super().__init__(rebalance, portfolio_bias)
def should_create_target_for_insight(self, insight):
'''Method that will determine if the portfolio construction model should create a
target for this insight
Args:
insight: The insight to create a target for'''
# Ignore insights that don't have Weight value
return insight.weight is not None
def determine_target_percent(self, active_insights):
'''Will determine the target percent for each insight
Args:
active_insights: The active insights to generate a target for'''
result = {}
# We will adjust weights proportionally in case the sum is > 1 so it sums to 1.
weight_sums = sum(self.get_value(insight) for insight in active_insights if self.respect_portfolio_bias(insight))
weight_factor = 1.0
if weight_sums > 1:
weight_factor = 1 / weight_sums
for insight in active_insights:
result[insight] = (insight.direction if self.respect_portfolio_bias(insight) else InsightDirection.FLAT) * self.get_value(insight) * weight_factor
return result
def get_value(self, insight):
'''Method that will determine which member will be used to compute the weights and gets its value
Args:
insight: The insight to create a target for
Returns:
The value of the selected insight member'''
return abs(insight.weight)
@@ -0,0 +1,140 @@
/*
* 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.
*/
using System.Collections.Generic;
using System.Linq;
using Accord.Math;
using Accord.Math.Optimization;
using Accord.Statistics;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of a portfolio optimizer that maximizes the portfolio Sharpe Ratio.
/// The interval of weights in optimization method can be changed based on the long-short algorithm.
/// The default model uses flat risk free rate and weight for an individual security range from -1 to 1.
/// </summary>
public class MaximumSharpeRatioPortfolioOptimizer : IPortfolioOptimizer
{
private double _lower;
private double _upper;
private double _riskFreeRate;
/// <summary>
/// Initialize a new instance of <see cref="MaximumSharpeRatioPortfolioOptimizer"/>
/// </summary>
/// <param name="lower">Lower constraint</param>
/// <param name="upper">Upper constraint</param>
/// <param name="riskFreeRate"></param>
public MaximumSharpeRatioPortfolioOptimizer(double lower = -1, double upper = 1, double riskFreeRate = 0.0)
{
_lower = lower;
_upper = upper;
_riskFreeRate = riskFreeRate;
}
/// <summary>
/// Boundary constraints on weights: lw ≤ w ≤ up
/// </summary>
/// <remarks>
/// Expressed in the substituted variable y = κw (κ = 1ᵀy &gt; 0), the per-weight bounds
/// become linear: yᵢ up·(1ᵀy) ≤ 0 and yᵢ lw·(1ᵀy) ≥ 0.
/// </remarks>
/// <param name="size">number of variables</param>
/// <returns>enumeration of linear constraint objects</returns>
protected IEnumerable<LinearConstraint> GetBoundaryConditions(int size)
{
for (int i = 0; i < size; i++)
{
// yᵢ up·(1ᵀy) ≤ 0
var upper = Vector.Create(size, -_upper);
upper[i] += 1.0;
yield return new LinearConstraint(size)
{
CombinedAs = upper,
ShouldBe = ConstraintType.LesserThanOrEqualTo,
Value = 0.0
};
// yᵢ lw·(1ᵀy) ≥ 0
var lower = Vector.Create(size, -_lower);
lower[i] += 1.0;
yield return new LinearConstraint(size)
{
CombinedAs = lower,
ShouldBe = ConstraintType.GreaterThanOrEqualTo,
Value = 0.0
};
}
}
/// <summary>
/// Perform portfolio optimization for a provided matrix of historical returns and an array of expected returns
/// </summary>
/// <param name="historicalReturns">Matrix of annualized historical returns where each column represents a security and each row returns for the given date/time (size: K x N).</param>
/// <param name="expectedReturns">Array of double with the portfolio annualized expected returns (size: K x 1).</param>
/// <param name="covariance">Multi-dimensional array of double with the portfolio covariance of annualized returns (size: K x K).</param>
/// <returns>Array of double with the portfolio weights (size: K x 1)</returns>
public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
{
covariance = covariance ?? historicalReturns.Covariance();
var returns = (expectedReturns ?? historicalReturns.Mean(0)).Subtract(_riskFreeRate);
var size = covariance.GetLength(0);
var equalWeights = Vector.Create(size, 1.0 / size);
// The Charnes-Cooper substitution needs a portfolio with positive expected excess
// return to exist, otherwise the Sharpe ratio cannot be maximized.
var feasible = _lower >= 0 ? returns.Any(x => x > 0) : returns.Any(x => x != 0);
if (!feasible)
{
return equalWeights;
}
// Charnes-Cooper substitution y = κw (κ = 1ᵀy): maximizing the Sharpe ratio
// (µ r_f)ᵀw / √(wᵀΣw) becomes minimizing wᵀΣw subject to (µ r_f)ᵀy = 1,
// recovering the weights afterwards as w = y / (1ᵀy).
// https://quant.stackexchange.com/questions/18521/sharpe-maximization-under-quadratic-constraints
var constraints = new List<LinearConstraint>
{
// (µ r_f)ᵀy = 1
new LinearConstraint(size)
{
CombinedAs = returns,
ShouldBe = ConstraintType.EqualTo,
Value = 1.0
}
};
// lw ≤ w ≤ up
constraints.AddRange(GetBoundaryConditions(size));
// Setup solver: minimize yᵀΣy
var optfunc = new QuadraticObjectiveFunction(covariance, Vector.Create(size, 0.0));
var solver = new GoldfarbIdnani(optfunc, constraints);
// Solve problem
var success = solver.Minimize(Vector.Copy(equalWeights));
if (!success)
{
return equalWeights;
}
// Recover the portfolio weights: w = y / (1ᵀy)
var y = solver.Solution;
var sum = y.Sum();
return sum > 0 ? y.Divide(sum) : equalWeights;
}
}
}
@@ -0,0 +1,95 @@
# 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 scipy.optimize import minimize
### <summary>
### Provides an implementation of a portfolio optimizer that maximizes the portfolio Sharpe Ratio.
### The interval of weights in optimization method can be changed based on the long-short algorithm.
### The default model uses flat risk free rate and weight for an individual security range from -1 to 1.'''
### </summary>
class MaximumSharpeRatioPortfolioOptimizer:
'''Provides an implementation of a portfolio optimizer that maximizes the portfolio Sharpe Ratio.
The interval of weights in optimization method can be changed based on the long-short algorithm.
The default model uses flat risk free rate and weight for an individual security range from -1 to 1.'''
def __init__(self,
minimum_weight = -1,
maximum_weight = 1,
risk_free_rate = 0):
'''Initialize the MaximumSharpeRatioPortfolioOptimizer
Args:
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.minimum_weight = minimum_weight
self.maximum_weight = maximum_weight
self.risk_free_rate = risk_free_rate
self.expected_returns = []
def optimize(self, historical_returns, expected_returns = None, covariance = None):
'''
Perform portfolio optimization for a provided matrix of historical returns and an array of expected returns
args:
historical_returns: Matrix of annualized historical returns where each column represents a security and each row returns for the given date/time (size: K x N).
expected_returns: Array of double with the portfolio annualized expected returns (size: K x 1).
covariance: Multi-dimensional array of double with the portfolio covariance of annualized returns (size: K x K).
Returns:
Array of double with the portfolio weights (size: K x 1)
'''
if covariance is None:
covariance = historical_returns.cov()
if expected_returns is None:
expected_returns = historical_returns.mean()
expected_returns = expected_returns - self.risk_free_rate
size = covariance.columns.size # K x 1
x0 = np.array(size * [1. / size])
# SLSQP maximizes the Sharpe ratio (µ r_f)^T w / √(w^T Σ w) directly, so the fractional
# objective is optimized in place without any substitution. The budget constraint Σw = 1 and
# the per-weight bounds lw ≤ w ≤ up are applied as-is. The previous implementation instead
# fixed (µ r_f)^T w to the equal-weight return, which collapsed the optimizer to minimum
# variance. The C# implementation uses the Charnes-Cooper QP substitution because its solver
# only handles quadratic objectives.
# https://quant.stackexchange.com/questions/18521/sharpe-maximization-under-quadratic-constraints
constraints = [
# Σw = 1
{'type': 'eq', 'fun': lambda weights: self.get_budget_constraint(weights)}]
opt = minimize(lambda weights: -expected_returns.dot(weights) / np.sqrt(self.portfolio_variance(weights, covariance)), # Objective function: Sharpe ratio
x0, # Initial guess
bounds = self.get_boundary_conditions(size), # Bounds for variables: lw ≤ w ≤ up
constraints = constraints, # Constraints definition
method='SLSQP') # Optimization method: Sequential Least SQuares Programming
return opt['x'] if opt['success'] else x0
def portfolio_variance(self, weights, covariance):
'''Computes the portfolio variance
Args:
weighs: Portfolio weights
covariance: Covariance matrix of historical returns'''
variance = np.dot(weights.T, np.dot(covariance, weights))
if variance == 0 and np.any(weights):
# variance can't be zero, with non zero weights
raise ValueError(f'MaximumSharpeRatioPortfolioOptimizer.portfolio_variance: Volatility cannot be zero. Weights: {weights}')
return variance
def get_boundary_conditions(self, size):
'''Creates the boundary condition for the portfolio weights'''
return tuple((self.minimum_weight, self.maximum_weight) for x in range(size))
def get_budget_constraint(self, weights):
'''Defines a budget constraint: the sum of the weights equals unity'''
return np.sum(weights) - 1
@@ -0,0 +1,350 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Accord.Math;
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Scheduling;
using QuantConnect.Util;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Implementation of On-Line Moving Average Reversion (OLMAR)
/// </summary>
/// <remarks>Li, B., Hoi, S. C. (2012). On-line portfolio selection with moving average reversion. arXiv preprint arXiv:1206.4626.
/// Available at https://arxiv.org/ftp/arxiv/papers/1206/1206.4626.pdf</remarks>
/// <remarks>Using windowSize = 1 => Passive Aggressive Mean Reversion (PAMR) Portfolio</remarks>
public class MeanReversionPortfolioConstructionModel : PortfolioConstructionModel
{
private int _numOfAssets;
private double[] _weightVector;
private decimal _reversionThreshold;
private int _windowSize;
private Resolution _resolution;
private Dictionary<Symbol, MeanReversionSymbolData> _symbolData = new();
/// <summary>
/// Initializes a new instance of the <see cref="MeanReversionPortfolioConstructionModel"/> class
/// </summary>
/// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time
/// in UTC</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="reversionThreshold">Reversion threshold</param>
/// <param name="windowSize">Window size of mean price</param>
/// <param name="resolution">The resolution of the history price and rebalancing</param>
public MeanReversionPortfolioConstructionModel(IDateRule rebalancingDateRules,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
decimal reversionThreshold = 1,
int windowSize = 20,
Resolution resolution = Resolution.Daily)
: this(rebalancingDateRules.ToFunc(), portfolioBias, reversionThreshold, windowSize, resolution)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MeanReversionPortfolioConstructionModel"/> class
/// </summary>
/// <param name="rebalanceResolution">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="reversionThreshold">Reversion threshold</param>
/// <param name="windowSize">Window size of mean price</param>
/// <param name="resolution">The resolution of the history price and rebalancing</param>
public MeanReversionPortfolioConstructionModel(Resolution rebalanceResolution = Resolution.Daily,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
decimal reversionThreshold = 1,
int windowSize = 20,
Resolution resolution = Resolution.Daily)
: this(rebalanceResolution.ToTimeSpan(), portfolioBias, reversionThreshold, windowSize, resolution)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MeanReversionPortfolioConstructionModel"/> class
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="reversionThreshold">Reversion threshold</param>
/// <param name="windowSize">Window size of mean price</param>
/// <param name="resolution">The resolution of the history price and rebalancing</param>
public MeanReversionPortfolioConstructionModel(TimeSpan timeSpan,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
decimal reversionThreshold = 1,
int windowSize = 20,
Resolution resolution = Resolution.Daily)
: this(dt => dt.Add(timeSpan), portfolioBias, reversionThreshold, windowSize, resolution)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MeanReversionPortfolioConstructionModel"/> class
/// </summary>
/// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
/// For a given algorithm UTC DateTime the func returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="reversionThreshold">Reversion threshold</param>
/// <param name="windowSize">Window size of mean price</param>
/// <param name="resolution">The resolution of the history price and rebalancing</param>
public MeanReversionPortfolioConstructionModel(PyObject rebalance,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
decimal reversionThreshold = 1,
int windowSize = 20,
Resolution resolution = Resolution.Daily)
: this((Func<DateTime, DateTime?>)null, portfolioBias, reversionThreshold, windowSize, resolution)
{
SetRebalancingFunc(rebalance);
}
/// <summary>
/// Initializes a new instance of the <see cref="MeanReversionPortfolioConstructionModel"/> class
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored.</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="reversionThreshold">Reversion threshold</param>
/// <param name="windowSize">Window size of mean price</param>
/// <param name="resolution">The resolution of the history price and rebalancing</param>
public MeanReversionPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
decimal reversionThreshold = 1,
int windowSize = 20,
Resolution resolution = Resolution.Daily)
: this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null,
portfolioBias, reversionThreshold, windowSize, resolution)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MeanReversionPortfolioConstructionModel"/> class
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance.</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="reversionThreshold">Reversion threshold</param>
/// <param name="windowSize">Window size of mean price</param>
/// <param name="resolution">The resolution of the history price and rebalancing</param>
public MeanReversionPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
decimal reversionThreshold = 1,
int windowSize = 20,
Resolution resolution = Resolution.Daily)
: base(rebalancingFunc)
{
if (portfolioBias == PortfolioBias.Short)
{
throw new ArgumentException("Long position must be allowed in MeanReversionPortfolioConstructionModel.");
}
_reversionThreshold = reversionThreshold;
_resolution = resolution;
_windowSize = windowSize;
}
/// <summary>
/// Will determine the target percent for each insight
/// </summary>
/// <param name="activeInsights">list of active insights</param>
/// <return>dictionary of insight and respective target weight</return>
protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
{
var targets = new Dictionary<Insight, double>();
// If we have no insights or non-ready just return an empty target list
if (activeInsights.IsNullOrEmpty() ||
!activeInsights.All(x => _symbolData[x.Symbol].IsReady()))
{
return targets;
}
var numOfAssets = activeInsights.Count;
if (_numOfAssets != numOfAssets)
{
_numOfAssets = numOfAssets;
// Initialize price vector and portfolio weightings vector
_weightVector = Enumerable.Repeat((double) 1/_numOfAssets, _numOfAssets).ToArray();
}
// Get price relatives vs expected price (SMA)
var priceRelatives = GetPriceRelatives(activeInsights); // \tilde{x}_{t+1}
// Get step size of next portfolio
// \bar{x}_{t+1} = 1^T * \tilde{x}_{t+1} / m
// \lambda_{t+1} = max( 0, ( b_t * \tilde{x}_{t+1} - \epsilon ) / ||\tilde{x}_{t+1} - \bar{x}_{t+1} * 1|| ^ 2 )
var nextPrediction = priceRelatives.Average(); // \bar{x}_{t+1}
var assetsMeanDev = priceRelatives.Select(x => x - nextPrediction).ToArray();
var secondNorm = Math.Pow(assetsMeanDev.Euclidean(), 2);
double stepSize; // \lambda_{t+1}
if (secondNorm == 0d)
{
stepSize = 0d;
}
else
{
stepSize = (_weightVector.InnerProduct(priceRelatives) - (double)_reversionThreshold) / secondNorm;
stepSize = Math.Max(0d, stepSize);
}
// Get next portfolio weightings
// b_{t+1} = b_t - step_size * ( \tilde{x}_{t+1} - \bar{x}_{t+1} * 1 )
var nextPortfolio = _weightVector.Select((x, i) => x - assetsMeanDev[i] * stepSize);
// Normalize
var normalizedPortfolioWeightVector = SimplexProjection(nextPortfolio);
// Save normalized result for the next portfolio step
_weightVector = normalizedPortfolioWeightVector;
// Update portfolio state
for (int i = 0; i < _numOfAssets; i++)
{
targets.Add(activeInsights[i], normalizedPortfolioWeightVector[i]);
}
return targets;
}
/// <summary>
/// Get price relatives with reference level of SMA
/// </summary>
/// <param name="activeInsights">list of active insights</param>
/// <return>array of price relatives vector</return>
protected virtual double[] GetPriceRelatives(List<Insight> activeInsights)
{
var numOfInsights = activeInsights.Count;
// Initialize a price vector of the next prices relatives' projection
var nextPriceRelatives = new double[numOfInsights];
for (int i = 0; i < numOfInsights; i++)
{
var insight = activeInsights[i];
var symbolData = _symbolData[insight.Symbol];
nextPriceRelatives[i] = insight.Magnitude != null ?
1 + (double)insight.Magnitude * (int)insight.Direction:
(double)symbolData.Identity.Current.Value / (double)symbolData.Sma.Current.Value;
}
return nextPriceRelatives;
}
/// <summary>
/// Event fired each time the we add/remove securities from the data feed
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="changes">The security additions and removals from the algorithm</param>
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
base.OnSecuritiesChanged(algorithm, changes);
// clean up data for removed securities
foreach (var removed in changes.RemovedSecurities)
{
_symbolData.Remove(removed.Symbol, out var symbolData);
symbolData.Reset();
}
// initialize data for added securities
var symbols = changes.AddedSecurities.Select(x => x.Symbol);
foreach(var symbol in symbols)
{
if (!_symbolData.ContainsKey(symbol))
{
_symbolData.Add(symbol, new MeanReversionSymbolData(algorithm, symbol, _windowSize, _resolution));
}
}
}
/// <summary>
/// Cumulative Sum of a given sequence
/// </summary>
/// <param name="sequence">sequence to obtain cumulative sum</param>
/// <return>cumulative sum</return>
public static IEnumerable<double> CumulativeSum(IEnumerable<double> sequence)
{
double sum = 0;
foreach(var item in sequence)
{
sum += item;
yield return sum;
}
}
/// <summary>
/// Normalize the updated portfolio into weight vector:
/// v_{t+1} = arg min || v - v_{t+1} || ^ 2
/// </summary>
/// <remark>Duchi, J., Shalev-Shwartz, S., Singer, Y., and Chandra, T. (2008, July).
/// Efficient projections onto the l1-ball for learning in high dimensions.
/// In Proceedings of the 25th international conference on Machine learning (pp. 272-279).</remark>
/// <param name="vector">unnormalized weight vector</param>
/// <param name="total">regulator, default to be 1, making it a probabilistic simplex</param>
/// <return>normalized weight vector</return>
public static double[] SimplexProjection(IEnumerable<double> vector, double total = 1)
{
if (total <= 0)
{
throw new ArgumentException("Total must be > 0 for Euclidean Projection onto the Simplex.");
}
// Sort v into u in descending order
var mu = vector.OrderByDescending(x => x).ToArray();
var sv = CumulativeSum(mu).ToArray();
var rho = Enumerable.Range(0, vector.Count()).Where(i => mu[i] > (sv[i] - total) / (i+1)).Last();
var theta = (sv[rho] - total) / (rho + 1);
var w = vector.Select(x => Math.Max(x - theta, 0d)).ToArray();
return w;
}
private class MeanReversionSymbolData
{
public Identity Identity;
public SimpleMovingAverage Sma;
public MeanReversionSymbolData(QCAlgorithm algo, Symbol symbol, int windowSize, Resolution resolution)
{
// Indicator of price
Identity = algo.Identity(symbol, resolution);
// Moving average indicator for mean reversion level
Sma = algo.SMA(symbol, windowSize, resolution);
// Warmup indicator
algo.WarmUpIndicator(symbol, Identity, resolution);
algo.WarmUpIndicator(symbol, Sma, resolution);
}
public void Reset()
{
Identity.Reset();
Sma.Reset();
}
public bool IsReady()
{
return (Identity.IsReady & Sma.IsReady);
}
}
}
}
@@ -0,0 +1,196 @@
# 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 *
### <summary>
### Implementation of On-Line Moving Average Reversion (OLMAR)
### </summary>
### <remarks>Li, B., Hoi, S. C. (2012). On-line portfolio selection with moving average reversion. arXiv preprint arXiv:1206.4626.
### Available at https://arxiv.org/ftp/arxiv/papers/1206/1206.4626.pdf</remarks>
### <remarks>Using windowSize = 1 => Passive Aggressive Mean Reversion (PAMR) Portfolio</remarks>
class MeanReversionPortfolioConstructionModel(PortfolioConstructionModel):
def __init__(self,
rebalance = Resolution.Daily,
portfolioBias = PortfolioBias.LongShort,
reversion_threshold = 1,
window_size = 20,
resolution = Resolution.Daily):
"""Initialize the model
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)
reversion_threshold: Reversion threshold
window_size: Window size of mean price calculation
resolution: The resolution of the history price and rebalancing
"""
super().__init__()
if portfolioBias == PortfolioBias.Short:
raise ArgumentException("Long position must be allowed in MeanReversionPortfolioConstructionModel.")
self.reversion_threshold = reversion_threshold
self.window_size = window_size
self.resolution = resolution
self.num_of_assets = 0
# Initialize a dictionary to store stock data
self.symbol_data = {}
# 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: list of active insights
Returns:
dictionary of insight and respective target weight
"""
targets = {}
# If we have no insights or non-ready just return an empty target list
if len(activeInsights) == 0 or not all([self.symbol_data[x.Symbol].IsReady for x in activeInsights]):
return targets
num_of_assets = len(activeInsights)
if self.num_of_assets != num_of_assets:
self.num_of_assets = num_of_assets
# Initialize portfolio weightings vector
self.weight_vector = np.ones(num_of_assets) * (1/num_of_assets)
### Get price relatives vs expected price (SMA)
price_relatives = self.GetPriceRelatives(activeInsights) # \tilde{x}_{t+1}
### Get step size of next portfolio
# \bar{x}_{t+1} = 1^T * \tilde{x}_{t+1} / m
# \lambda_{t+1} = max( 0, ( b_t * \tilde{x}_{t+1} - \epsilon ) / ||\tilde{x}_{t+1} - \bar{x}_{t+1} * 1|| ^ 2 )
next_prediction = price_relatives.mean() # \bar{x}_{t+1}
assets_mean_dev = price_relatives - next_prediction
second_norm = (np.linalg.norm(assets_mean_dev)) ** 2
if second_norm == 0.0:
step_size = 0
else:
step_size = (np.dot(self.weight_vector, price_relatives) - self.reversion_threshold) / second_norm
step_size = max(0, step_size) # \lambda_{t+1}
### Get next portfolio weightings
# b_{t+1} = b_t - step_size * ( \tilde{x}_{t+1} - \bar{x}_{t+1} * 1 )
next_portfolio = self.weight_vector - step_size * assets_mean_dev
# Normalize
normalized_portfolio_weight_vector = self.SimplexProjection(next_portfolio)
# Save normalized result for the next portfolio step
self.weight_vector = normalized_portfolio_weight_vector
# Update portfolio state
for i, insight in enumerate(activeInsights):
targets[insight] = normalized_portfolio_weight_vector[i]
return targets
def GetPriceRelatives(self, activeInsights):
"""Get price relatives with reference level of SMA
Args:
activeInsights: list of active insights
Returns:
array of price relatives vector
"""
# Initialize a price vector of the next prices relatives' projection
next_price_relatives = np.zeros(len(activeInsights))
### Get next price relative predictions
# Using the previous price to simulate assumption of instant reversion
for i, insight in enumerate(activeInsights):
symbol_data = self.symbol_data[insight.Symbol]
next_price_relatives[i] = 1 + insight.Magnitude * insight.Direction \
if insight.Magnitude is not None \
else symbol_data.Identity.Current.Value / symbol_data.Sma.Current.Value
return next_price_relatives
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
super().OnSecuritiesChanged(algorithm, changes)
for removed in changes.RemovedSecurities:
symbol_data = self.symbol_data.pop(removed.Symbol, None)
symbol_data.Reset()
# initialize data for added securities
symbols = [ x.Symbol for x in changes.AddedSecurities ]
for symbol in symbols:
if symbol not in self.symbol_data:
self.symbol_data[symbol] = self.MeanReversionSymbolData(algorithm, symbol, self.window_size, self.resolution)
def SimplexProjection(self, vector, total=1):
"""Normalize the updated portfolio into weight vector:
v_{t+1} = arg min || v - v_{t+1} || ^ 2
Implementation from:
Duchi, J., Shalev-Shwartz, S., Singer, Y., & Chandra, T. (2008, July).
Efficient projections onto the l 1-ball for learning in high dimensions.
In Proceedings of the 25th international conference on Machine learning
(pp. 272-279).
Args:
vector: unnormalized weight vector
total: total weight of output, default to be 1, making it a probabilistic simplex
"""
if total <= 0:
raise ArgumentException("Total must be > 0 for Euclidean Projection onto the Simplex.")
vector = np.asarray(vector)
# Sort v into u in descending order
mu = np.sort(vector)[::-1]
sv = np.cumsum(mu)
rho = np.where(mu > (sv - total) / np.arange(1, len(vector) + 1))[0][-1]
theta = (sv[rho] - total) / (rho + 1)
w = (vector - theta)
w[w < 0] = 0
return w
class MeanReversionSymbolData:
def __init__(self, algo, symbol, window_size, resolution):
# Indicator of price
self.Identity = algo.Identity(symbol, resolution)
# Moving average indicator for mean reversion level
self.Sma = algo.SMA(symbol, window_size, resolution)
# Warmup indicator
algo.WarmUpIndicator(symbol, self.Identity, resolution)
algo.WarmUpIndicator(symbol, self.Sma, resolution)
def Reset(self):
self.Identity.Reset()
self.Sma.Reset()
@property
def IsReady(self):
return self.Identity.IsReady and self.Sma.IsReady
@@ -0,0 +1,333 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Accord.Math;
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Scheduling;
using QuantConnect.Util;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <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>
public class MeanVarianceOptimizationPortfolioConstructionModel : PortfolioConstructionModel
{
private readonly int _lookback;
private readonly int _period;
private readonly Resolution _resolution;
private readonly PortfolioBias _portfolioBias;
private readonly IPortfolioOptimizer _optimizer;
private readonly Dictionary<Symbol, ReturnsSymbolData> _symbolDataDict;
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time
/// in UTC</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="targetReturn">The target portfolio return</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
public MeanVarianceOptimizationPortfolioConstructionModel(IDateRule rebalancingDateRules,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double targetReturn = 0.02,
IPortfolioOptimizer optimizer = null)
: this(rebalancingDateRules.ToFunc(), portfolioBias, lookback, period, resolution, targetReturn, optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalanceResolution">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="targetReturn">The target portfolio return</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
public MeanVarianceOptimizationPortfolioConstructionModel(Resolution rebalanceResolution = Resolution.Daily,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double targetReturn = 0.02,
IPortfolioOptimizer optimizer = null)
: this(rebalanceResolution.ToTimeSpan(), portfolioBias, lookback, period, resolution, targetReturn, optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="targetReturn">The target portfolio return</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
public MeanVarianceOptimizationPortfolioConstructionModel(TimeSpan timeSpan,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double targetReturn = 0.02,
IPortfolioOptimizer optimizer = null)
: this(dt => dt.Add(timeSpan), portfolioBias, lookback, period, resolution, targetReturn, optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
/// For a given algorithm UTC DateTime the func returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="targetReturn">The target portfolio return</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
/// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
/// constructor for the date rules parameter.
/// For performance we prefer python algorithms using the C# implementation</remarks>
public MeanVarianceOptimizationPortfolioConstructionModel(PyObject rebalance,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double targetReturn = 0.02,
PyObject optimizer = null)
: this((Func<DateTime, DateTime?>)null, portfolioBias, lookback, period, resolution, targetReturn, null)
{
SetRebalancingFunc(rebalance);
if (optimizer != null)
{
if (optimizer.TryConvert<IPortfolioOptimizer>(out var csharpOptimizer))
{
_optimizer = csharpOptimizer;
}
else
{
_optimizer = new PortfolioOptimizerPythonWrapper(optimizer);
}
}
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
/// Returning current time will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="targetReturn">The target portfolio return</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
public MeanVarianceOptimizationPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double targetReturn = 0.02,
IPortfolioOptimizer optimizer = null)
: this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null,
portfolioBias,
lookback,
period,
resolution,
targetReturn,
optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance.</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="targetReturn">The target portfolio return</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
public MeanVarianceOptimizationPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 63,
Resolution resolution = Resolution.Daily,
double targetReturn = 0.02,
IPortfolioOptimizer optimizer = null)
: base(rebalancingFunc)
{
_lookback = lookback;
_period = period;
_resolution = resolution;
_portfolioBias = portfolioBias;
var lower = portfolioBias == PortfolioBias.Long ? 0 : -1;
var upper = portfolioBias == PortfolioBias.Short ? 0 : 1;
_optimizer = optimizer ?? new MinimumVariancePortfolioOptimizer(lower, upper, targetReturn);
_symbolDataDict = new Dictionary<Symbol, ReturnsSymbolData>();
}
/// <summary>
/// Method that will determine if the portfolio construction model should create a
/// target for this insight
/// </summary>
/// <param name="insight">The insight to create a target for</param>
/// <returns>True if the portfolio should create a target for the insight</returns>
protected override bool ShouldCreateTargetForInsight(Insight insight)
{
var filteredInsight = FilterInvalidInsightMagnitude(Algorithm, new[] { insight }).FirstOrDefault();
if (filteredInsight == null)
{
return false;
}
ReturnsSymbolData data;
if (_symbolDataDict.TryGetValue(insight.Symbol, out data))
{
if (!insight.Magnitude.HasValue)
{
Algorithm.SetRunTimeError(
new ArgumentNullException(
insight.Symbol.Value,
"MeanVarianceOptimizationPortfolioConstructionModel does not accept 'null' as Insight.Magnitude. " +
"Please checkout the selected Alpha Model specifications: " + insight.SourceModel));
return false;
}
data.Add(Algorithm.Time, insight.Magnitude.Value.SafeDecimalCast());
}
return true;
}
/// <summary>
/// Will determine the target percent for each insight
/// </summary>
/// <param name="activeInsights">The active insights to generate a target for</param>
/// <returns>A target percent for each insight</returns>
protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
{
var targets = new Dictionary<Insight, double>();
// If we have no insights just return an empty target list
if (activeInsights.IsNullOrEmpty())
{
return targets;
}
var symbols = activeInsights.Select(x => x.Symbol).ToList();
// Get symbols' returns, we use simple return according to
// Meucci, Attilio, Quant Nugget 2: Linear vs. Compounded Returns Common Pitfalls in Portfolio Management (May 1, 2010).
// GARP Risk Professional, pp. 49-51, April 2010 , Available at SSRN: https://ssrn.com/abstract=1586656
var returns = _symbolDataDict.FormReturnsMatrix(symbols);
// The optimization method processes the data frame
var w = _optimizer.Optimize(returns);
// process results
if (w.Length > 0)
{
var sidx = 0;
foreach (var symbol in symbols)
{
var weight = w[sidx];
// don't trust the optimizer
if (_portfolioBias != PortfolioBias.LongShort
&& Math.Sign(weight) != (int)_portfolioBias)
{
weight = 0;
}
targets[activeInsights.First(insight => insight.Symbol == symbol)] = weight;
sidx++;
}
}
return targets;
}
/// <summary>
/// Event fired each time the we add/remove securities from the data feed
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="changes">The security additions and removals from the algorithm</param>
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
base.OnSecuritiesChanged(algorithm, changes);
// clean up data for removed securities
foreach (var removed in changes.RemovedSecurities)
{
ReturnsSymbolData data;
if (_symbolDataDict.TryGetValue(removed.Symbol, out data))
{
_symbolDataDict.Remove(removed.Symbol);
}
}
if (changes.AddedSecurities.Count == 0)
return;
// initialize data for added securities
foreach (var added in changes.AddedSecurities)
{
if (!_symbolDataDict.ContainsKey(added.Symbol))
{
var symbolData = new ReturnsSymbolData(added.Symbol, _lookback, _period);
_symbolDataDict[added.Symbol] = symbolData;
}
}
// warmup our indicators by pushing history through the consolidators
algorithm.History(changes.AddedSecurities.Select(security => security.Symbol), _lookback * _period, _resolution)
.PushThrough(bar =>
{
ReturnsSymbolData symbolData;
if (_symbolDataDict.TryGetValue(bar.Symbol, out symbolData))
{
symbolData.Update(bar.EndTime, bar.Value);
}
});
}
}
}
@@ -0,0 +1,170 @@
# 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 Portfolio.MinimumVariancePortfolioOptimizer import MinimumVariancePortfolioOptimizer
### <summary>
### Provides an implementation of Mean-Variance portfolio optimization based on modern portfolio theory.
### The default model uses the MinimumVariancePortfolioOptimizer that accepts a 63-row matrix of 1-day returns.
### </summary>
class MeanVarianceOptimizationPortfolioConstructionModel(PortfolioConstructionModel):
def __init__(self,
rebalance = Resolution.DAILY,
portfolio_bias = PortfolioBias.LONG_SHORT,
lookback = 1,
period = 63,
resolution = Resolution.DAILY,
target_return = 0.02,
optimizer = None):
"""Initialize the model
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.
portfolio_bias: Specifies the bias of the portfolio (Short, Long/Short, Long)
lookback(int): Historical return lookback period
period(int): The time interval of history price to calculate the weight
resolution: The resolution of the history price
optimizer(class): Method used to compute the portfolio weights"""
super().__init__()
self.lookback = lookback
self.period = period
self.resolution = resolution
self.portfolio_bias = portfolio_bias
self.sign = lambda x: -1 if x < 0 else (1 if x > 0 else 0)
lower = 0 if portfolio_bias == PortfolioBias.LONG else -1
upper = 0 if portfolio_bias == PortfolioBias.SHORT else 1
self.optimizer = MinimumVariancePortfolioOptimizer(lower, upper, target_return) if optimizer is None else optimizer
self.symbol_data_by_symbol = {}
# If the argument is an instance of Resolution or Timedelta
# Redefine rebalancing_func
rebalancing_func = rebalance
if isinstance(rebalance, Resolution):
rebalance = Extensions.to_time_span(rebalance)
if isinstance(rebalance, timedelta):
rebalancing_func = lambda dt: dt + rebalance
if rebalancing_func:
self.set_rebalancing_func(rebalancing_func)
def should_create_target_for_insight(self, insight):
if len(PortfolioConstructionModel.filter_invalid_insight_magnitude(self.algorithm, [insight])) == 0:
return False
symbol_data = self.symbol_data_by_symbol.get(insight.symbol)
if insight.magnitude is None:
self.algorithm.set_run_time_error(ArgumentNullException('MeanVarianceOptimizationPortfolioConstructionModel does not accept \'None\' as Insight.magnitude. Please checkout the selected Alpha Model specifications.'))
return False
symbol_data.add(self.algorithm.time, insight.magnitude)
return True
def determine_target_percent(self, active_insights):
"""
Will determine the target percent for each insight
Args:
Returns:
"""
targets = {}
# If we have no insights just return an empty target list
if len(active_insights) == 0:
return targets
symbols = [insight.symbol for insight in active_insights]
# Create a dictionary keyed by the symbols in the insights with an pandas.series as value to create a data frame
returns = { str(symbol.id) : data.return_ for symbol, data in self.symbol_data_by_symbol.items() if symbol in symbols }
returns = pd.DataFrame(returns)
# The portfolio optimizer finds the optional weights for the given data
weights = self.optimizer.optimize(returns)
weights = pd.Series(weights, index = returns.columns)
# Create portfolio targets from the specified insights
for insight in active_insights:
weight = weights[str(insight.symbol.id)]
# don't trust the optimizer
if self.portfolio_bias != PortfolioBias.LONG_SHORT and self.sign(weight) != self.portfolio_bias:
weight = 0
targets[insight] = weight
return targets
def on_securities_changed(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
super().on_securities_changed(algorithm, changes)
for removed in changes.removed_securities:
symbol_data = self.symbol_data_by_symbol.pop(removed.symbol, None)
symbol_data.reset()
# initialize data for added securities
symbols = [x.symbol for x in changes.added_securities]
for symbol in [x for x in symbols if x not in self.symbol_data_by_symbol]:
self.symbol_data_by_symbol[symbol] = self.MeanVarianceSymbolData(symbol, self.lookback, self.period)
history = algorithm.history[TradeBar](symbols, self.lookback * self.period, self.resolution)
for bars in history:
for symbol, bar in bars.items():
symbol_data = self.symbol_data_by_symbol.get(symbol).update(bar.end_time, bar.value)
class MeanVarianceSymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, symbol, lookback, period):
self._symbol = symbol
self.roc = RateOfChange(f'{symbol}.roc({lookback})', lookback)
self.roc.updated += self.on_rate_of_change_updated
self.window = RollingWindow(period)
def reset(self):
self.roc.updated -= self.on_rate_of_change_updated
self.roc.reset()
self.window.reset()
def update(self, time, value):
return self.roc.update(time, value)
def on_rate_of_change_updated(self, roc, value):
if roc.is_ready:
self.window.add(value)
def add(self, time, value):
item = IndicatorDataPoint(self._symbol, time, value)
self.window.add(item)
# Get symbols' returns, we use simple return according to
# Meucci, Attilio, Quant Nugget 2: Linear vs. Compounded Returns Common Pitfalls in Portfolio Management (May 1, 2010).
# GARP Risk Professional, pp. 49-51, April 2010 , Available at SSRN: https://ssrn.com/abstract=1586656
@property
def return_(self):
return pd.Series(
data = [x.value for x in self.window],
index = [x.end_time for x in self.window])
@property
def is_ready(self):
return self.window.is_ready
def __str__(self, **kwargs):
return '{}: {:.2%}'.format(self.roc.name, self.window[0])
@@ -0,0 +1,136 @@
/*
* 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.
*/
using System.Collections.Generic;
using System.Linq;
using Accord.Math;
using Accord.Math.Optimization;
using Accord.Statistics;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of a minimum variance portfolio optimizer that calculate the optimal weights
/// with the weight range from -1 to 1 and minimize the portfolio variance with a target return of 2%
/// </summary>
/// <remarks>The budged constrain is scaled down/up to ensure that the sum of the absolute value of the weights is 1.</remarks>
public class MinimumVariancePortfolioOptimizer : IPortfolioOptimizer
{
private double _lower;
private double _upper;
private double _targetReturn;
/// <summary>
/// Initialize a new instance of <see cref="MinimumVariancePortfolioOptimizer"/>
/// </summary>
/// <param name="lower">Lower bound</param>
/// <param name="upper">Upper bound</param>
/// <param name="targetReturn">Target return</param>
public MinimumVariancePortfolioOptimizer(double lower = -1, double upper = 1, double targetReturn = 0.02)
{
_lower = lower;
_upper = upper;
_targetReturn = targetReturn;
}
/// <summary>
/// Sum of all weight is one: 1^T w = 1 / Σw = 1
/// </summary>
/// <param name="size">number of variables</param>
/// <returns>linear constaraint object</returns>
protected LinearConstraint GetBudgetConstraint(int size)
{
return new LinearConstraint(size)
{
CombinedAs = Vector.Create(size, 1.0),
ShouldBe = ConstraintType.EqualTo,
Value = 1.0
};
}
/// <summary>
/// Boundary constraints on weights: lw ≤ w ≤ up
/// </summary>
/// <param name="size">number of variables</param>
/// <returns>enumeration of linear constaraint objects</returns>
protected IEnumerable<LinearConstraint> GetBoundaryConditions(int size)
{
for (var i = 0; i < size; i++)
{
yield return new LinearConstraint(1)
{
VariablesAtIndices = new[] { i },
ShouldBe = ConstraintType.GreaterThanOrEqualTo,
Value = _lower
};
yield return new LinearConstraint(1)
{
VariablesAtIndices = new[] { i },
ShouldBe = ConstraintType.LesserThanOrEqualTo,
Value = _upper
};
}
}
/// <summary>
/// Perform portfolio optimization for a provided matrix of historical returns and an array of expected returns
/// </summary>
/// <param name="historicalReturns">Matrix of annualized historical returns where each column represents a security and each row returns for the given date/time (size: K x N).</param>
/// <param name="expectedReturns">Array of double with the portfolio annualized expected returns (size: K x 1).</param>
/// <param name="covariance">Multi-dimensional array of double with the portfolio covariance of annualized returns (size: K x K).</param>
/// <returns>Array of double with the portfolio weights (size: K x 1)</returns>
public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
{
covariance ??= historicalReturns.Covariance();
var size = covariance.GetLength(0);
var returns = expectedReturns ?? historicalReturns.Mean(0);
var constraints = new List<LinearConstraint>
{
// w^T µ ≥ β
new (size)
{
CombinedAs = returns,
ShouldBe = ConstraintType.EqualTo,
Value = _targetReturn
},
// Σw = 1
GetBudgetConstraint(size),
};
// lw ≤ w ≤ up
constraints.AddRange(GetBoundaryConditions(size));
// Setup solver
var optfunc = new QuadraticObjectiveFunction(covariance, Vector.Create(size, 0.0));
var solver = new GoldfarbIdnani(optfunc, constraints);
// Solve problem
var x0 = Vector.Create(size, 1.0 / size);
var success = solver.Minimize(Vector.Copy(x0));
if (!success) return x0;
// We cannot accept NaN
var solution = solver.Solution
.Select(x => x.IsNaNOrInfinity() ? 0 : x).ToArray();
// Scale the solution to ensure that the sum of the absolute weights is 1
var sumOfAbsoluteWeights = solution.Abs().Sum();
if (sumOfAbsoluteWeights.IsNaNOrZero()) return x0;
return solution.Divide(sumOfAbsoluteWeights);
}
}
}
@@ -0,0 +1,94 @@
# 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 scipy.optimize import minimize
### <summary>
### Provides an implementation of a portfolio optimizer that calculate the optimal weights
### with the weight range from -1 to 1 and minimize the portfolio variance with a target return of 2%
### </summary>
### <remarks>The budged constrain is scaled down/up to ensure that the sum of the absolute value of the weights is 1.</remarks>
class MinimumVariancePortfolioOptimizer:
'''Provides an implementation of a portfolio optimizer that calculate the optimal weights
with the weight range from -1 to 1 and minimize the portfolio variance with a target return of 2%'''
def __init__(self,
minimum_weight = -1,
maximum_weight = 1,
target_return = 0.02):
'''Initialize the MinimumVariancePortfolioOptimizer
Args:
minimum_weight(float): The lower bounds on portfolio weights
maximum_weight(float): The upper bounds on portfolio weights
target_return(float): The target portfolio return'''
self.minimum_weight = minimum_weight
self.maximum_weight = maximum_weight
self.target_return = target_return
def optimize(self, historical_returns, expected_returns = None, covariance = None):
'''
Perform portfolio optimization for a provided matrix of historical returns and an array of expected returns
args:
historical_returns: Matrix of annualized historical returns where each column represents a security and each row returns for the given date/time (size: K x N).
expected_returns: Array of double with the portfolio annualized expected returns (size: K x 1).
covariance: Multi-dimensional array of double with the portfolio covariance of annualized returns (size: K x K).
Returns:
Array of double with the portfolio weights (size: K x 1)
'''
if covariance is None:
covariance = historical_returns.cov()
if expected_returns is None:
expected_returns = historical_returns.mean()
size = historical_returns.columns.size # K x 1
x0 = np.array(size * [1. / size])
constraints = [
{'type': 'eq', 'fun': lambda weights: self.get_budget_constraint(weights)},
{'type': 'eq', 'fun': lambda weights: self.get_target_constraint(weights, expected_returns)}]
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
opt = minimize(lambda weights: self.portfolio_variance(weights, covariance), # Objective function
x0, # Initial guess
bounds = self.get_boundary_conditions(size), # Bounds for variables
constraints = constraints, # Constraints definition
method='SLSQP') # Optimization method: Sequential Least Squares Programming (SLSQP)
if not opt['success']: return x0
# Scale the solution to ensure that the sum of the absolute weights is 1
sum_of_absolute_weights = np.sum(np.abs(opt['x']))
return opt['x'] / sum_of_absolute_weights
def portfolio_variance(self, weights, covariance):
'''Computes the portfolio variance
Args:
weighs: Portfolio weights
covariance: Covariance matrix of historical returns'''
variance = np.dot(weights.T, np.dot(covariance, weights))
if variance == 0 and np.any(weights):
# variance can't be zero, with non zero weights
raise ValueError(f'MinimumVariancePortfolioOptimizer.portfolio_variance: Volatility cannot be zero. Weights: {weights}')
return variance
def get_boundary_conditions(self, size):
'''Creates the boundary condition for the portfolio weights'''
return tuple((self.minimum_weight, self.maximum_weight) for x in range(size))
def get_budget_constraint(self, weights):
'''Defines a budget constraint: the sum of the weights equals unity'''
return np.sum(weights) - 1
def get_target_constraint(self, weights, expected_returns):
'''Ensure that the portfolio return target a given return'''
return np.dot(np.matrix(expected_returns), np.matrix(weights).T).item() - self.target_return
@@ -0,0 +1,48 @@
/*
* 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.
*
*/
using Python.Runtime;
using QuantConnect.Python;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Python wrapper for custom portfolio optimizer
/// </summary>
public class PortfolioOptimizerPythonWrapper : BasePythonWrapper<IPortfolioOptimizer>, IPortfolioOptimizer
{
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="portfolioOptimizer">The python model to wrapp</param>
public PortfolioOptimizerPythonWrapper(PyObject portfolioOptimizer)
: base(portfolioOptimizer)
{
}
/// <summary>
/// Perform portfolio optimization for a provided matrix of historical returns and an array of expected returns
/// </summary>
/// <param name="historicalReturns">Matrix of annualized historical returns where each column represents a security and each row returns for the given date/time (size: K x N).</param>
/// <param name="expectedReturns">Array of double with the portfolio annualized expected returns (size: K x 1).</param>
/// <param name="covariance">Multi-dimensional array of double with the portfolio covariance of annualized returns (size: K x K).</param>
/// <returns>Array of double with the portfolio weights (size: K x 1)</returns>
public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
{
return InvokeMethod<double[]>(nameof(Optimize), historicalReturns, expectedReturns, covariance);
}
}
}
@@ -0,0 +1,153 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Indicators;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Contains returns specific to a symbol required for optimization model
/// </summary>
public class ReturnsSymbolData
{
private readonly Symbol _symbol;
private readonly RateOfChange _roc;
private readonly RollingWindow<IndicatorDataPoint> _window;
/// <summary>
/// The symbol's asset rate of change indicator
/// </summary>
public RateOfChange ROC { get { return _roc; } }
/// <summary>
/// Initializes a new instance of the <see cref="ReturnsSymbolData"/> class
/// </summary>
/// <param name="symbol">The symbol of the data that updates the indicators</param>
/// <param name="lookback">Look-back period for the RateOfChange indicator</param>
/// <param name="period">Size of rolling window that contains historical RateOfChange</param>
public ReturnsSymbolData(Symbol symbol, int lookback, int period)
{
_symbol = symbol;
_roc = new RateOfChange($"{_symbol}.ROC({lookback})", lookback);
_window = new RollingWindow<IndicatorDataPoint>(period);
_roc.Updated += OnRateOfChangeUpdated;
}
/// <summary>
/// Historical returns
/// </summary>
public Dictionary<DateTime, double> Returns => _window.ToDictionary(x => x.EndTime, x => (double) x.Value);
/// <summary>
/// Adds an item to this window and shifts all other elements
/// </summary>
/// <param name="time">The time associated with the value</param>
/// <param name="value">The value to use to update this window</param>
public void Add(DateTime time, decimal value)
{
var item = new IndicatorDataPoint(_symbol, time, value);
AddToWindow(item);
}
/// <summary>
/// Updates the state of the RateOfChange with the given value and returns true
/// if this indicator is ready, false otherwise
/// </summary>
/// <param name="time">The time associated with the value</param>
/// <param name="value">The value to use to update this indicator</param>
/// <returns>True if this indicator is ready, false otherwise</returns>
public bool Update(DateTime time, decimal value)
{
return _roc.Update(time, value);
}
/// <summary>
/// Resets all indicators of this object to its initial state
/// </summary>
public void Reset()
{
_roc.Updated -= OnRateOfChangeUpdated;
_roc.Reset();
_window.Reset();
}
/// <summary>
/// When the RateOfChange is updated, adds the new value to the RollingWindow
/// </summary>
/// <param name="roc"></param>
/// <param name="updated"></param>
private void OnRateOfChangeUpdated(object roc, IndicatorDataPoint updated)
{
if (_roc.IsReady)
{
AddToWindow(updated);
}
}
private void AddToWindow(IndicatorDataPoint updated)
{
if (_window.Samples > 0 && _window[0].EndTime == updated.EndTime)
{
// this could happen with fill forward bars in the history request
return;
}
_window.Add(updated);
}
}
/// <summary>
/// Extension methods for <see cref="ReturnsSymbolData"/>
/// </summary>
public static class ReturnsSymbolDataExtensions
{
/// <summary>
/// Converts a dictionary of <see cref="ReturnsSymbolData"/> keyed by <see cref="Symbol"/> into a matrix
/// </summary>
/// <param name="symbolData">Dictionary of <see cref="ReturnsSymbolData"/> keyed by <see cref="Symbol"/> to be converted into a matrix</param>
/// <param name="symbols">List of <see cref="Symbol"/> to be included in the matrix</param>
public static double[,] FormReturnsMatrix(this Dictionary<Symbol, ReturnsSymbolData> symbolData, IEnumerable<Symbol> symbols)
{
var returnsByDate = (from s in symbols join sd in symbolData on s equals sd.Key select sd.Value.Returns).ToList();
// Consolidate by date
var alldates = returnsByDate.SelectMany(r => r.Keys).Distinct().ToList();
var max = symbolData.Count == 0 ? 0 : symbolData.Max(kvp => kvp.Value.Returns.Count);
// Perfect match between the dates in the ReturnsSymbolData objects
if (max == alldates.Count)
{
return Accord.Math.Matrix.Create(alldates
// if a return date isn't found for a symbol we use 'double.NaN'
.Select(d => returnsByDate.Select(s => s.GetValueOrDefault(d, double.NaN)).ToArray())
.Where(r => !r.Select(Math.Abs).Sum().IsNaNOrZero()) // remove empty rows
.ToArray());
}
// If it is not a match, we assume that each index correspond to the same point in time
var returnsByIndex = returnsByDate.Select((doubles, i) => doubles.Values.ToArray());
return Accord.Math.Matrix.Create(Enumerable.Range(0, max)
// there is no guarantee that all symbols have the same amount of returns so we need to check range and use 'double.NaN' if required as above
.Select(d => returnsByIndex.Select(s => s.Length < (d + 1) ? double.NaN : s[d]).ToArray())
.Where(r => !r.Select(Math.Abs).Sum().IsNaNOrZero()) // remove empty rows
.ToArray());
}
}
}
@@ -0,0 +1,265 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Accord.Math;
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Scheduling;
using QuantConnect.Util;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Risk Parity Portfolio Construction Model
/// </summary>
/// <remarks>Spinu, F. (2013). An algorithm for computing risk parity weights. Available at SSRN 2297383.
/// Available at https://papers.ssrn.com/sol3/Papers.cfm?abstract_id=2297383</remarks>
public class RiskParityPortfolioConstructionModel : PortfolioConstructionModel
{
private readonly int _lookback;
private readonly int _period;
private readonly Resolution _resolution;
private readonly IPortfolioOptimizer _optimizer;
private readonly Dictionary<Symbol, ReturnsSymbolData> _symbolDataDict;
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time in UTC</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
public RiskParityPortfolioConstructionModel(IDateRule rebalancingDateRules,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 252,
Resolution resolution = Resolution.Daily,
IPortfolioOptimizer optimizer = null)
: this(rebalancingDateRules.ToFunc(), portfolioBias, lookback, period, resolution, optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalanceResolution">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
public RiskParityPortfolioConstructionModel(Resolution rebalanceResolution = Resolution.Daily,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 252,
Resolution resolution = Resolution.Daily,
IPortfolioOptimizer optimizer = null)
: this(rebalanceResolution.ToTimeSpan(), portfolioBias, lookback, period, resolution, optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
public RiskParityPortfolioConstructionModel(TimeSpan timeSpan,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 252,
Resolution resolution = Resolution.Daily,
IPortfolioOptimizer optimizer = null)
: this(dt => dt.Add(timeSpan), portfolioBias, lookback, period, resolution, optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
/// For a given algorithm UTC DateTime the func returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
/// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
/// constructor for the date rules parameter.
/// For performance we prefer python algorithms using the C# implementation</remarks>
public RiskParityPortfolioConstructionModel(PyObject rebalance,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 252,
Resolution resolution = Resolution.Daily,
IPortfolioOptimizer optimizer = null)
: this((Func<DateTime, DateTime?>)null, portfolioBias, lookback, period, resolution, optimizer)
{
SetRebalancingFunc(rebalance);
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
/// Returning current time will trigger rebalance. If null will be ignored</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
public RiskParityPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 252,
Resolution resolution = Resolution.Daily,
IPortfolioOptimizer optimizer = null)
: this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null,
portfolioBias,
lookback,
period,
resolution,
optimizer)
{
}
/// <summary>
/// Initialize the model
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance.</param>
/// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="period">The time interval of history price to calculate the weight</param>
/// <param name="resolution">The resolution of the history price</param>
/// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
public RiskParityPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc,
PortfolioBias portfolioBias = PortfolioBias.LongShort,
int lookback = 1,
int period = 252,
Resolution resolution = Resolution.Daily,
IPortfolioOptimizer optimizer = null)
: base(rebalancingFunc)
{
if (portfolioBias == PortfolioBias.Short)
{
throw new ArgumentException("Long position must be allowed in RiskParityPortfolioConstructionModel.");
}
_lookback = lookback;
_period = period;
_resolution = resolution;
_optimizer = optimizer ?? new RiskParityPortfolioOptimizer();
_symbolDataDict = new Dictionary<Symbol, ReturnsSymbolData>();
}
/// <summary>
/// Will determine the target percent for each insight
/// </summary>
/// <param name="activeInsights">The active insights to generate a target for</param>
/// <returns>A target percent for each insight</returns>
protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
{
var targets = new Dictionary<Insight, double>();
// If we have no insights just return an empty target list
if (activeInsights.IsNullOrEmpty())
{
return targets;
}
var symbols = activeInsights.Select(x => x.Symbol).ToList();
// Get symbols' returns
var returns = _symbolDataDict.FormReturnsMatrix(symbols);
// The optimization method processes the data frame
var w = _optimizer.Optimize(returns);
// process results
if (w.Length > 0)
{
var sidx = 0;
foreach (var symbol in symbols)
{
var weight = w[sidx];
targets[activeInsights.First(insight => insight.Symbol == symbol)] = weight;
sidx++;
}
}
return targets;
}
/// <summary>
/// Event fired each time the we add/remove securities from the data feed
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="changes">The security additions and removals from the algorithm</param>
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
base.OnSecuritiesChanged(algorithm, changes);
// clean up data for removed securities
foreach (var removed in changes.RemovedSecurities)
{
_symbolDataDict.Remove(removed.Symbol, out var removedSymbolData);
algorithm.UnregisterIndicator(removedSymbolData.ROC);
}
if (changes.AddedSecurities.Count == 0)
{
return;
}
// initialize data for added securities
foreach (var added in changes.AddedSecurities)
{
if (!_symbolDataDict.ContainsKey(added.Symbol))
{
var symbolData = new ReturnsSymbolData(added.Symbol, _lookback, _period);
_symbolDataDict[added.Symbol] = symbolData;
algorithm.RegisterIndicator(added.Symbol, symbolData.ROC, _resolution);
}
}
// warmup our indicators by pushing history through the consolidators
algorithm.History(changes.AddedSecurities.Select(security => security.Symbol), _lookback * _period, _resolution)
.PushThrough(bar =>
{
ReturnsSymbolData symbolData;
if (_symbolDataDict.TryGetValue(bar.Symbol, out symbolData))
{
symbolData.Update(bar.EndTime, bar.Value);
}
});
}
}
}
@@ -0,0 +1,158 @@
# 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 Portfolio.RiskParityPortfolioOptimizer import RiskParityPortfolioOptimizer
### <summary>
### Risk Parity Portfolio Construction Model
### </summary>
### <remarks>Spinu, F. (2013). An algorithm for computing risk parity weights. Available at SSRN 2297383.
### Available at https://papers.ssrn.com/sol3/Papers.cfm?abstract_id=2297383</remarks>
class RiskParityPortfolioConstructionModel(PortfolioConstructionModel):
def __init__(self,
rebalance = Resolution.DAILY,
portfolio_bias = PortfolioBias.LONG_SHORT,
lookback = 1,
period = 252,
resolution = Resolution.DAILY,
optimizer = None):
"""Initialize the model
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.
portfolio_bias: Specifies the bias of the portfolio (Short, Long/Short, Long)
lookback(int): Historical return lookback period
period(int): The time interval of history price to calculate the weight
resolution: The resolution of the history price
optimizer(class): Method used to compute the portfolio weights"""
super().__init__()
if portfolio_bias == PortfolioBias.SHORT:
raise ArgumentException("Long position must be allowed in RiskParityPortfolioConstructionModel.")
self.lookback = lookback
self.period = period
self.resolution = resolution
self.sign = lambda x: -1 if x < 0 else (1 if x > 0 else 0)
self.optimizer = RiskParityPortfolioOptimizer() if optimizer is None else optimizer
self._symbol_data_by_symbol = {}
# If the argument is an instance of Resolution or Timedelta
# Redefine rebalancing_func
rebalancing_func = rebalance
if isinstance(rebalance, int):
rebalance = Extensions.to_time_span(rebalance)
if isinstance(rebalance, timedelta):
rebalancing_func = lambda dt: dt + rebalance
if rebalancing_func:
self.set_rebalancing_func(rebalancing_func)
def determine_target_percent(self, active_insights):
"""Will determine the target percent for each insight
Args:
active_insights: list of active insights
Returns:
dictionary of insight and respective target weight
"""
targets = {}
# If we have no insights just return an empty target list
if len(active_insights) == 0:
return targets
symbols = [insight.symbol for insight in active_insights]
# Create a dictionary keyed by the symbols in the insights with an pandas.series as value to create a data frame
returns = { str(symbol) : data.return_ for symbol, data in self._symbol_data_by_symbol.items() if symbol in symbols }
returns = pd.DataFrame(returns)
# The portfolio optimizer finds the optional weights for the given data
weights = self.optimizer.optimize(returns)
weights = pd.Series(weights, index = returns.columns)
# Create portfolio targets from the specified insights
for insight in active_insights:
targets[insight] = weights[str(insight.symbol)]
return targets
def on_securities_changed(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
super().on_securities_changed(algorithm, changes)
for removed in changes.removed_securities:
symbol_data = self._symbol_data_by_symbol.pop(removed.symbol, None)
symbol_data.reset()
algorithm.unregister_indicator(symbol_data.roc)
# initialize data for added securities
symbols = [ x.symbol for x in changes.added_securities ]
history = algorithm.history(symbols, self.lookback * self.period, self.resolution)
if history.empty: return
tickers = history.index.levels[0]
for ticker in tickers:
symbol = SymbolCache.get_symbol(ticker)
if symbol not in self._symbol_data_by_symbol:
symbol_data = self.RiskParitySymbolData(symbol, self.lookback, self.period)
symbol_data.warm_up_indicators(history.loc[ticker])
self._symbol_data_by_symbol[symbol] = symbol_data
algorithm.register_indicator(symbol, symbol_data.roc, self.resolution)
class RiskParitySymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, symbol, lookback, period):
self._symbol = symbol
self.roc = RateOfChange(f'{symbol}.roc({lookback})', lookback)
self.roc.updated += self.on_rate_of_change_updated
self.window = RollingWindow(period)
def reset(self):
self.roc.updated -= self.on_rate_of_change_updated
self.roc.reset()
self.window.reset()
def warm_up_indicators(self, history):
for tuple in history.itertuples():
self.roc.update(tuple.Index, tuple.close)
def on_rate_of_change_updated(self, roc, value):
if roc.is_ready:
self.window.add(value)
def add(self, time, value):
item = IndicatorDataPoint(self._symbol, time, value)
self.window.add(item)
@property
def return_(self):
return pd.Series(
data = [x.value for x in self.window],
index = [x.end_time for x in self.window])
@property
def is_ready(self):
return self.window.is_ready
def __str__(self, **kwargs):
return '{}: {:.2%}'.format(self.roc.name, self.window[0])
@@ -0,0 +1,120 @@
/*
* 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.
*/
using System;
using System.Linq;
using Accord.Math;
using Accord.Statistics;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of a risk parity portfolio optimizer that calculate the optimal weights
/// with the weight range from 0 to 1 and equalize the risk carried by each asset
/// </summary>
public class RiskParityPortfolioOptimizer : IPortfolioOptimizer
{
private double _lower = 1e-05;
private double _upper = Double.MaxValue;
/// <summary>
/// Initialize a new instance of <see cref="RiskParityPortfolioOptimizer"/>
/// </summary>
/// <param name="lower">The lower bounds on portfolio weights</param>
/// <param name="upper">The upper bounds on portfolio weights</param>
public RiskParityPortfolioOptimizer(double? lower = null, double? upper = null)
{
_lower = lower ?? _lower; // has to be greater than or equal to 0
_upper = upper ?? _upper;
}
/// <summary>
/// Perform portfolio optimization for a provided matrix of historical returns and an array of expected returns
/// </summary>
/// <param name="historicalReturns">Matrix of annualized historical returns where each column represents a security and each row returns for the given date/time (size: K x N).</param>
/// <param name="expectedReturns">Risk budget vector (size: K x 1).</param>
/// <param name="covariance">Multi-dimensional array of double with the portfolio covariance of annualized returns (size: K x K).</param>
/// <returns>Array of double with the portfolio weights (size: K x 1)</returns>
public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
{
covariance = covariance ?? historicalReturns.Covariance();
var size = covariance.GetLength(0);
// Optimization Problem
// minimize_{x >= 0} f(x) = 1/2 * x^T.S.x - b^T.log(x)
// b = 1 / num_of_assets (equal budget of risk)
// df(x)/dx = S.x - b / x
// H(x) = S + Diag(b / x^2)
expectedReturns = expectedReturns ?? Vector.Create(size, 1d / size);
var solution = RiskParityNewtonMethodOptimization(size, covariance, expectedReturns);
// Normalize weights: w = x / x^T.1
solution = Elementwise.Divide(solution, solution.Sum());
// Make sure the vector is within range
return solution.Select(x => Math.Clamp(x, _lower, _upper)).ToArray();
}
/// <summary>
/// Newton method of minimization
/// </summary>
/// <param name="numberOfVariables">The number of variables (size of weight vector).</param>
/// <param name="covariance">Covariance matrix (size: K x K).</param>
/// <param name="budget">The risk budget (size: K x 1).</param>
/// <param name="tolerance">Tolerance level of objective difference with previous steps to accept minimization result.</param>
/// <param name="maximumIteration">Maximum iteration per optimization.</param>
/// <returns>Array of double of argumented minimization</returns>
protected double[] RiskParityNewtonMethodOptimization(int numberOfVariables, double[,] covariance, double[] budget, double tolerance = 1e-11, int maximumIteration = 15000)
{
if (numberOfVariables < 1 || numberOfVariables > 1000)
{
throw new ArgumentException("Argument \"numberOfVariables\" must be a positive integer between 1 and 1000");
}
else if (numberOfVariables == 1)
{
return new double[]{1d};
}
Func<double[], double> objective = (x) => 0.5 * Matrix.Dot(Matrix.Dot(x, covariance), x) - Matrix.Dot(budget, Elementwise.Log(x));
Func<double[], double[]> gradient = (x) => Elementwise.Subtract(Matrix.Dot(covariance, x), Elementwise.Divide(budget, x));
Func<double[], double[,]> hessian = (x) => Elementwise.Add(covariance, Matrix.Diagonal(Elementwise.Divide(budget, Elementwise.Multiply(x, x))));
var weight = Vector.Create(numberOfVariables, 1d / numberOfVariables);
var newObjective = Double.MinValue;
var oldObjective = Double.MaxValue;
var iter = 0;
while (Math.Abs(newObjective - oldObjective) > tolerance && iter < maximumIteration)
{
// Store old objective value
oldObjective = newObjective;
// Get parameters for Newton method gradient descend
var invHess = Matrix.Inverse(hessian(weight));
var jacobian = gradient(weight);
// Get next weight vector
// x^{k + 1} = x^{k} - H^{-1}(x^{k}).df(x^{k}))
weight = Elementwise.Subtract(weight, Matrix.Dot(invHess, jacobian));
// Store new objective value
newObjective = objective(weight);
iter++;
}
return weight;
}
}
}
@@ -0,0 +1,63 @@
# 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 scipy.optimize import *
### <summary>
### Provides an implementation of a risk parity portfolio optimizer that calculate the optimal weights
### with the weight range from 0 to 1 and equalize the risk carried by each asset
### </summary>
class RiskParityPortfolioOptimizer:
def __init__(self,
minimum_weight = 1e-05,
maximum_weight = sys.float_info.max):
'''Initialize the RiskParityPortfolioOptimizer
Args:
minimum_weight(float): The lower bounds on portfolio weights
maximum_weight(float): The upper bounds on portfolio weights'''
self.minimum_weight = minimum_weight if minimum_weight >= 1e-05 else 1e-05
self.maximum_weight = maximum_weight if maximum_weight >= minimum_weight else minimum_weight
def optimize(self, historical_returns, budget = None, covariance = None):
'''
Perform portfolio optimization for a provided matrix of historical returns and an array of expected returns
args:
historical_returns: Matrix of annualized historical returns where each column represents a security and each row returns for the given date/time (size: K x N).
budget: Risk budget vector (size: K x 1).
covariance: Multi-dimensional array of double with the portfolio covariance of annualized returns (size: K x K).
Returns:
Array of double with the portfolio weights (size: K x 1)
'''
if covariance is None:
covariance = np.cov(historical_returns.T)
size = historical_returns.columns.size # K x 1
# Optimization Problem
# minimize_{x >= 0} f(x) = 1/2 * x^T.S.x - b^T.log(x)
# b = 1 / num_of_assets (equal budget of risk)
# df(x)/dx = S.x - b / x
# H(x) = S + Diag(b / x^2)
# lw <= x <= up
x0 = np.array(size * [1. / size])
budget = budget if budget is not None else x0
objective = lambda weights: 0.5 * weights.T @ covariance @ weights - budget.T @ np.log(weights)
gradient = lambda weights: covariance @ weights - budget / weights
hessian = lambda weights: covariance + np.diag((budget / weights**2).flatten())
solver = minimize(objective, jac=gradient, hess=hessian, x0=x0, method="Newton-CG")
if not solver["success"]: return x0
# Normalize weights: w = x / x^T.1
return np.clip(solver["x"]/np.sum(solver["x"]), self.minimum_weight, self.maximum_weight)
@@ -0,0 +1,208 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of <see cref="IPortfolioConstructionModel"/> that generates percent targets based on the
/// <see cref="CompanyReference.IndustryTemplateCode"/>.
/// The target percent holdings of each sector is 1/S where S is the number of sectors and
/// the target percent holdings of each security is 1/N where N is the number of securities of each sector.
/// For insights of direction <see cref="InsightDirection.Up"/>, long targets are returned and for insights of direction
/// <see cref="InsightDirection.Down"/>, short targets are returned.
/// It will ignore <see cref="Insight"/> for symbols that have no <see cref="CompanyReference.IndustryTemplateCode"/> value.
/// </summary>
public class SectorWeightingPortfolioConstructionModel : EqualWeightingPortfolioConstructionModel
{
private readonly Dictionary<Symbol, string> _sectorCodeBySymbol = new Dictionary<Symbol, string>();
/// <summary>
/// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time
/// in UTC</param>
public SectorWeightingPortfolioConstructionModel(IDateRule rebalancingDateRules)
: base(rebalancingDateRules.ToFunc())
{
}
/// <summary>
/// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
public SectorWeightingPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc)
: base(rebalancingFunc)
{
}
/// <summary>
/// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
/// Returning current time will trigger rebalance. If null will be ignored</param>
public SectorWeightingPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc)
: this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null)
{
}
/// <summary>
/// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
/// For a given algorithm UTC DateTime the func returns the next expected rebalance time
/// or null if unknown, in which case the function will be called again in the next loop. Returning current time
/// will trigger rebalance. If null will be ignored</param>
/// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
/// constructor for the date rules parameter.
/// For performance we prefer python algorithms using the C# implementation</remarks>
public SectorWeightingPortfolioConstructionModel(PyObject rebalance)
: this((Func<DateTime, DateTime?>)null)
{
SetRebalancingFunc(rebalance);
}
/// <summary>
/// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
public SectorWeightingPortfolioConstructionModel(TimeSpan timeSpan)
: this(dt => dt.Add(timeSpan))
{
}
/// <summary>
/// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="resolution">Rebalancing frequency</param>
public SectorWeightingPortfolioConstructionModel(Resolution resolution = Resolution.Daily)
: this(resolution.ToTimeSpan())
{
}
/// <summary>
/// Method that will determine if the portfolio construction model should create a
/// target for this insight
/// </summary>
/// <param name="insight">The insight to create a target for</param>
/// <returns>True if the portfolio should create a target for the insight</returns>
protected override bool ShouldCreateTargetForInsight(Insight insight)
{
return _sectorCodeBySymbol.ContainsKey(insight.Symbol);
}
/// <summary>
/// Will determine the target percent for each insight
/// </summary>
/// <param name="activeInsights">The active insights to generate a target for</param>
/// <returns>A target percent for each insight</returns>
protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
{
var result = new Dictionary<Insight, double>();
var insightBySectorCode = new Dictionary<string, List<Insight>>();
foreach (var insight in activeInsights)
{
if (insight.Direction == InsightDirection.Flat)
{
result[insight] = 0;
continue;
}
List<Insight> insights;
var sectorCode = _sectorCodeBySymbol[insight.Symbol];
if (insightBySectorCode.TryGetValue(sectorCode, out insights))
{
insights.Add(insight);
}
else
{
insightBySectorCode[sectorCode] = new List<Insight> { insight };
}
}
// give equal weighting to each sector
var sectorPercent = insightBySectorCode.Count == 0 ? 0 : 1m / insightBySectorCode.Count;
foreach (var kvp in insightBySectorCode)
{
var insights = kvp.Value;
// give equal weighting to each security
var count = insights.Count;
var percent = count == 0 ? 0 : sectorPercent / count;
foreach (var insight in insights)
{
result[insight] = (double)((int)insight.Direction * percent);
}
}
return result;
}
/// <summary>
/// Event fired each time the we add/remove securities from the data feed
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="changes">The security additions and removals from the algorithm</param>
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
foreach (var security in changes.RemovedSecurities)
{
// Removes the symbol from the _sectorCodeBySymbol dictionary
// since we cannot emit PortfolioTarget for removed securities
var symbol = security.Symbol;
if (_sectorCodeBySymbol.ContainsKey(symbol))
{
_sectorCodeBySymbol.Remove(symbol);
}
}
foreach (var security in changes.AddedSecurities)
{
var sectorCode = GetSectorCode(security);
if (!string.IsNullOrEmpty(sectorCode))
{
_sectorCodeBySymbol[security.Symbol] = sectorCode;
}
}
base.OnSecuritiesChanged(algorithm, changes);
}
/// <summary>
/// Gets the sector code
/// </summary>
/// <param name="security">The security to create a sector code for</param>
/// <returns>The value of the sector code for the security</returns>
/// <remarks>Other sectors can be defined using <see cref="AssetClassification"/></remarks>
protected virtual string GetSectorCode(Security security)
{
return security.Fundamentals?.CompanyReference.IndustryTemplateCode;
}
}
}
@@ -0,0 +1,102 @@
# 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 EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
class SectorWeightingPortfolioConstructionModel(EqualWeightingPortfolioConstructionModel):
'''Provides an implementation of IPortfolioConstructionModel that
generates percent targets based on the CompanyReference.industry_template_code.
The target percent holdings of each sector is 1/S where S is the number of sectors and
the target percent holdings of each security is 1/N where N is the number of securities of each sector.
For insights of direction InsightDirection.UP, long targets are returned and for insights of direction
InsightDirection.DOWN, short targets are returned.
It will ignore Insight for symbols that have no CompanyReference.industry_template_code'''
def __init__(self, rebalance = Resolution.DAILY):
'''Initialize a new instance of InsightWeightingPortfolioConstructionModel
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.'''
super().__init__(rebalance)
self.sector_code_by_symbol = dict()
def should_create_target_for_insight(self, insight):
'''Method that will determine if the portfolio construction model should create a
target for this insight
Args:
insight: The insight to create a target for'''
return insight.symbol in self.sector_code_by_symbol
def determine_target_percent(self, active_insights):
'''Will determine the target percent for each insight
Args:
active_insights: The active insights to generate a target for'''
result = dict()
insight_by_sector_code = dict()
for insight in active_insights:
if insight.direction == InsightDirection.FLAT:
result[insight] = 0
continue
sector_code = self.sector_code_by_symbol.get(insight.symbol)
insights = insight_by_sector_code.pop(sector_code, list())
insights.append(insight)
insight_by_sector_code[sector_code] = insights
# give equal weighting to each sector
sector_percent = 0 if len(insight_by_sector_code) == 0 else 1.0 / len(insight_by_sector_code)
for _, insights in insight_by_sector_code.items():
# give equal weighting to each security
count = len(insights)
percent = 0 if count == 0 else sector_percent / count
for insight in insights:
result[insight] = insight.direction * percent
return result
def on_securities_changed(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'''
for security in changes.removed_securities:
# Removes the symbol from the self.sector_code_by_symbol dictionary
# since we cannot emit PortfolioTarget for removed securities
self.sector_code_by_symbol.pop(security.symbol, None)
for security in changes.added_securities:
sector_code = self.get_sector_code(security)
if sector_code:
self.sector_code_by_symbol[security.symbol] = sector_code
super().on_securities_changed(algorithm, changes)
def get_sector_code(self, security):
'''Gets the sector code
Args:
security: The security to create a sector code for
Returns:
The value of the sector code for the security
Remarks:
Other sectors can be defined using AssetClassification'''
fundamentals = security.fundamentals
company_reference = security.fundamentals.company_reference if fundamentals else None
return company_reference.industry_template_code if company_reference else None
@@ -0,0 +1,40 @@
/*
* 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.
*/
using Accord.Math;
using Accord.Statistics;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of a portfolio optimizer with unconstrained mean variance.
/// </summary>
public class UnconstrainedMeanVariancePortfolioOptimizer : IPortfolioOptimizer
{
/// <summary>
/// Perform portfolio optimization for a provided matrix of historical returns and an array of expected returns
/// </summary>
/// <param name="historicalReturns">Matrix of historical returns where each column represents a security and each row returns for the given date/time (size: K x N).</param>
/// <param name="expectedReturns">Array of double with the portfolio annualized expected returns (size: K x 1).</param>
/// <param name="covariance">Multi-dimensional array of double with the portfolio covariance of annualized returns (size: K x K).</param>
/// <returns>Array of double with the portfolio weights (size: K x 1)</returns>
public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
{
var Π = (expectedReturns ?? historicalReturns.Mean(0));
var Σ = covariance ?? historicalReturns.Covariance();
return Π.Dot(Σ.Inverse());
}
}
}
@@ -0,0 +1,37 @@
# 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 numpy import dot
from numpy.linalg import inv
### <summary>
### Provides an implementation of a portfolio optimizer with unconstrained mean variance.'''
### </summary>
class UnconstrainedMeanVariancePortfolioOptimizer:
'''Provides an implementation of a portfolio optimizer with unconstrained mean variance.'''
def optimize(self, historical_returns, expected_returns = None, covariance = None):
'''
Perform portfolio optimization for a provided matrix of historical returns and an array of expected returns
args:
historical_returns: Matrix of historical returns where each column represents a security and each row returns for the given date/time (size: K x N).
expected_returns: Array of double with the portfolio annualized expected returns (size: K x 1).
covariance: Multi-dimensional array of double with the portfolio covariance of annualized returns (size: K x K).</param>
Returns:
Array of double with the portfolio weights (size: K x 1)
'''
if expected_returns is None:
expected_returns = historical_returns.mean()
if covariance is None:
covariance = historical_returns.cov()
return expected_returns.dot(inv(covariance))