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,286 @@
/*
* 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.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Securities;
using static System.FormattableString;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// This alpha model is designed to accept every possible pair combination
/// from securities selected by the universe selection model
/// This model generates alternating long ratio/short ratio insights emitted as a group
/// </summary>
public class BasePairsTradingAlphaModel : AlphaModel
{
private readonly int _lookback;
private readonly Resolution _resolution;
private readonly TimeSpan _predictionInterval;
private readonly decimal _threshold;
private readonly Dictionary<Tuple<Symbol, Symbol>, PairData> _pairs;
/// <summary>
/// List of security objects present in the universe
/// </summary>
public HashSet<Security> Securities { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BasePairsTradingAlphaModel"/> class
/// </summary>
/// <param name="lookback">Lookback period of the analysis</param>
/// <param name="resolution">Analysis resolution</param>
/// <param name="threshold">The percent [0, 100] deviation of the ratio from the mean before emitting an insight</param>
public BasePairsTradingAlphaModel(
int lookback = 1,
Resolution resolution = Resolution.Daily,
decimal threshold = 1m
)
{
_lookback = lookback;
_resolution = resolution;
_threshold = threshold;
_predictionInterval = _resolution.ToTimeSpan().Multiply(_lookback);
_pairs = new Dictionary<Tuple<Symbol, Symbol>, PairData>();
Securities = new HashSet<Security>();
Name = Invariant($"{nameof(BasePairsTradingAlphaModel)}({_lookback},{_resolution},{_threshold.Normalize()})");
}
/// <summary>
/// Updates this alpha model with the latest data from the algorithm.
/// This is called each time the algorithm receives data for subscribed securities
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="data">The new data available</param>
/// <returns>The new insights generated</returns>
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
{
var insights = new List<Insight>();
foreach (var kvp in _pairs)
{
insights.AddRange(kvp.Value.GetInsightGroup());
}
return insights;
}
/// <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)
{
NotifiedSecurityChanges.UpdateCollection(Securities, changes);
UpdatePairs(algorithm);
// Remove pairs that has assets that were removed from the universe
foreach (var security in changes.RemovedSecurities)
{
var symbol = security.Symbol;
var keys = _pairs.Keys.Where(k => k.Item1 == symbol || k.Item2 == symbol).ToList();
foreach (var key in keys)
{
var pair = _pairs[key];
pair.Dispose();
_pairs.Remove(key);
}
}
}
/// <summary>
/// Check whether the assets pass a pairs trading test
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="asset1">The first asset's symbol in the pair</param>
/// <param name="asset2">The second asset's symbol in the pair</param>
/// <returns>True if the statistical test for the pair is successful</returns>
public virtual bool HasPassedTest(QCAlgorithm algorithm, Symbol asset1, Symbol asset2)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(HasPassedTest), out bool result, algorithm, asset1, asset2))
{
return result;
}
return true;
}
private void UpdatePairs(QCAlgorithm algorithm)
{
var assets = Securities.Select(x => x.Symbol).ToArray();
for (var i = 0; i < assets.Length; i++)
{
var assetI = assets[i];
for (var j = i + 1; j < assets.Length; j++)
{
var assetJ = assets[j];
var pairSymbol = Tuple.Create(assetI, assetJ);
var invert = Tuple.Create(assetJ, assetI);
if (_pairs.ContainsKey(pairSymbol) || _pairs.ContainsKey(invert))
{
continue;
}
if (!HasPassedTest(algorithm, assetI, assetJ))
{
continue;
}
var pairData = new PairData(algorithm, assetI, assetJ, _predictionInterval, _threshold);
_pairs.Add(pairSymbol, pairData);
}
}
}
private class PairData : IDisposable
{
private enum State
{
ShortRatio,
FlatRatio,
LongRatio
};
private State _state = State.FlatRatio;
private readonly QCAlgorithm _algorithm;
private readonly Symbol _asset1;
private readonly Symbol _asset2;
private readonly IDataConsolidator _identityConsolidator1;
private readonly IDataConsolidator _identityConsolidator2;
private readonly IndicatorBase<IndicatorDataPoint> _asset1Price;
private readonly IndicatorBase<IndicatorDataPoint> _asset2Price;
private readonly IndicatorBase<IndicatorDataPoint> _ratio;
private readonly IndicatorBase<IndicatorDataPoint> _mean;
private readonly IndicatorBase<IndicatorDataPoint> _upperThreshold;
private readonly IndicatorBase<IndicatorDataPoint> _lowerThreshold;
private readonly TimeSpan _predictionInterval;
/// <summary>
/// Create a new pair
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="asset1">The first asset's symbol in the pair</param>
/// <param name="asset2">The second asset's symbol in the pair</param>
/// <param name="period">Period over which this insight is expected to come to fruition</param>
/// <param name="threshold">The percent [0, 100] deviation of the ratio from the mean before emitting an insight</param>
public PairData(QCAlgorithm algorithm, Symbol asset1, Symbol asset2, TimeSpan period, decimal threshold)
{
_algorithm = algorithm;
_asset1 = asset1;
_asset2 = asset2;
// Created the Identity indicator for a given Symbol and
// the consolidator it is registered to. The consolidator reference
// will be used to remove it from SubscriptionManager
(Identity, IDataConsolidator) CreateIdentityIndicator(Symbol symbol)
{
var resolution = algorithm.SubscriptionManager
.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(symbol)
.Min(x => x.Resolution);
var name = algorithm.CreateIndicatorName(symbol, "close", resolution);
var identity = new Identity(name);
var consolidator = algorithm.ResolveConsolidator(symbol, resolution);
algorithm.RegisterIndicator(symbol, identity, consolidator);
return (identity, consolidator);
}
(_asset1Price, _identityConsolidator1) = CreateIdentityIndicator(asset1);
(_asset2Price, _identityConsolidator2) = CreateIdentityIndicator(asset2);
_ratio = _asset1Price.Over(_asset2Price);
_mean = new ExponentialMovingAverage(500).Of(_ratio);
var upper = new ConstantIndicator<IndicatorDataPoint>("ct", 1 + threshold / 100m);
_upperThreshold = _mean.Times(upper, "UpperThreshold");
var lower = new ConstantIndicator<IndicatorDataPoint>("ct", 1 - threshold / 100m);
_lowerThreshold = _mean.Times(lower, "LowerThreshold");
_predictionInterval = period;
}
/// <summary>
/// On disposal, remove the consolidators from the subscription manager
/// </summary>
public void Dispose()
{
_algorithm.SubscriptionManager.RemoveConsolidator(_asset1, _identityConsolidator1);
_algorithm.SubscriptionManager.RemoveConsolidator(_asset2, _identityConsolidator2);
}
/// <summary>
/// Gets the insights group for the pair
/// </summary>
/// <returns>Insights grouped by an unique group id</returns>
public IEnumerable<Insight> GetInsightGroup()
{
if (!_mean.IsReady)
{
return Enumerable.Empty<Insight>();
}
// don't re-emit the same direction
if (_state != State.LongRatio && _ratio > _upperThreshold)
{
_state = State.LongRatio;
// asset1/asset2 is more than 2 std away from mean, short asset1, long asset2
var shortAsset1 = Insight.Price(_asset1, _predictionInterval, InsightDirection.Down);
var longAsset2 = Insight.Price(_asset2, _predictionInterval, InsightDirection.Up);
// creates a group id and set the GroupId property on each insight object
return Insight.Group(shortAsset1, longAsset2);
}
// don't re-emit the same direction
if (_state != State.ShortRatio && _ratio < _lowerThreshold)
{
_state = State.ShortRatio;
// asset1/asset2 is less than 2 std away from mean, long asset1, short asset2
var longAsset1 = Insight.Price(_asset1, _predictionInterval, InsightDirection.Up);
var shortAsset2 = Insight.Price(_asset2, _predictionInterval, InsightDirection.Down);
// creates a group id and set the GroupId property on each insight object
return Insight.Group(longAsset1, shortAsset2);
}
return Enumerable.Empty<Insight>();
}
}
}
}
@@ -0,0 +1,194 @@
# 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 enum import Enum
class BasePairsTradingAlphaModel(AlphaModel):
'''This alpha model is designed to accept every possible pair combination
from securities selected by the universe selection model
This model generates alternating long ratio/short ratio insights emitted as a group'''
def __init__(self, lookback = 1,
resolution = Resolution.DAILY,
threshold = 1):
''' Initializes a new instance of the PairsTradingAlphaModel class
Args:
lookback: Lookback period of the analysis
resolution: Analysis resolution
threshold: The percent [0, 100] deviation of the ratio from the mean before emitting an insight'''
self.lookback = lookback
self.resolution = resolution
self.threshold = threshold
self.prediction_interval = Time.multiply(Extensions.to_time_span(self.resolution), self.lookback)
self.pairs = dict()
self.securities = set()
self.name = f'{self.__class__.__name__}({self.lookback},{resolution},{Extensions.normalize_to_str(threshold)})'
def update(self, algorithm, data):
''' Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
insights = []
for key, pair in self.pairs.items():
insights.extend(pair.get_insight_group())
return 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'''
for security in changes.added_securities:
self.securities.add(security)
for security in changes.removed_securities:
if security in self.securities:
self.securities.remove(security)
self.update_pairs(algorithm)
for security in changes.removed_securities:
keys = [k for k in self.pairs.keys() if security.symbol in k]
for key in keys:
self.pairs.pop(key).dispose()
def update_pairs(self, algorithm):
symbols = sorted([x.symbol for x in self.securities])
for i in range(0, len(symbols)):
asset_i = symbols[i]
for j in range(1 + i, len(symbols)):
asset_j = symbols[j]
pair_symbol = (asset_i, asset_j)
invert = (asset_j, asset_i)
if pair_symbol in self.pairs or invert in self.pairs:
continue
if not self.has_passed_test(algorithm, asset_i, asset_j):
continue
pair = self.Pair(algorithm, asset_i, asset_j, self.prediction_interval, self.threshold)
self.pairs[pair_symbol] = pair
def has_passed_test(self, algorithm, asset1, asset2):
'''Check whether the assets pass a pairs trading test
Args:
algorithm: The algorithm instance that experienced the change in securities
asset1: The first asset's symbol in the pair
asset2: The second asset's symbol in the pair
Returns:
True if the statistical test for the pair is successful'''
return True
class Pair:
class State(Enum):
SHORT_RATIO = -1
FLAT_RATIO = 0
LONG_RATIO = 1
def __init__(self, algorithm, asset1, asset2, prediction_interval, threshold):
'''Create a new pair
Args:
algorithm: The algorithm instance that experienced the change in securities
asset1: The first asset's symbol in the pair
asset2: The second asset's symbol in the pair
prediction_interval: Period over which this insight is expected to come to fruition
threshold: The percent [0, 100] deviation of the ratio from the mean before emitting an insight'''
self.state = self.State.FLAT_RATIO
self.algorithm = algorithm
self.asset1 = asset1
self.asset2 = asset2
# Created the Identity indicator for a given Symbol and
# the consolidator it is registered to. The consolidator reference
# will be used to remove it from SubscriptionManager
def create_identity_indicator(symbol: Symbol):
resolution = min([x.resolution for x in algorithm.subscription_manager.subscription_data_config_service.get_subscription_data_configs(symbol)])
name = algorithm.create_indicator_name(symbol, "close", resolution)
identity = Identity(name)
consolidator = algorithm.resolve_consolidator(symbol, resolution)
algorithm.register_indicator(symbol, identity, consolidator)
return identity, consolidator
self.asset1_price, self.identity_consolidator1 = create_identity_indicator(asset1);
self.asset2_price, self.identity_consolidator2 = create_identity_indicator(asset2);
self.ratio = IndicatorExtensions.over(self.asset1_price, self.asset2_price)
self.mean = IndicatorExtensions.of(ExponentialMovingAverage(500), self.ratio)
upper = ConstantIndicator[IndicatorDataPoint]("ct", 1 + threshold / 100)
self.upper_threshold = IndicatorExtensions.times(self.mean, upper)
lower = ConstantIndicator[IndicatorDataPoint]("ct", 1 - threshold / 100)
self.lower_threshold = IndicatorExtensions.times(self.mean, lower)
self.prediction_interval = prediction_interval
def dispose(self):
'''
On disposal, remove the consolidators from the subscription manager
'''
self.algorithm.subscription_manager.remove_consolidator(self.asset1, self.identity_consolidator1)
self.algorithm.subscription_manager.remove_consolidator(self.asset2, self.identity_consolidator2)
def get_insight_group(self):
'''Gets the insights group for the pair
Returns:
Insights grouped by an unique group id'''
if not self.mean.is_ready:
return []
# don't re-emit the same direction
if self.state is not self.State.LONG_RATIO and self.ratio > self.upper_threshold:
self.state = self.State.LONG_RATIO
# asset1/asset2 is more than 2 std away from mean, short asset1, long asset2
short_asset_1 = Insight.price(self.asset1, self.prediction_interval, InsightDirection.DOWN)
long_asset_2 = Insight.price(self.asset2, self.prediction_interval, InsightDirection.UP)
# creates a group id and set the GroupId property on each insight object
return Insight.group(short_asset_1, long_asset_2)
# don't re-emit the same direction
if self.state is not self.State.SHORT_RATIO and self.ratio < self.lower_threshold:
self.state = self.State.SHORT_RATIO
# asset1/asset2 is less than 2 std away from mean, long asset1, short asset2
long_asset_1 = Insight.price(self.asset1, self.prediction_interval, InsightDirection.UP)
short_asset_2 = Insight.price(self.asset2, self.prediction_interval, InsightDirection.DOWN)
# creates a group id and set the GroupId property on each insight object
return Insight.group(long_asset_1, short_asset_2)
return []
@@ -0,0 +1,141 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using static System.FormattableString;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Provides an implementation of <see cref="IAlphaModel"/> that always returns the same insight for each security
/// </summary>
public class ConstantAlphaModel : AlphaModel
{
private readonly InsightType _type;
private readonly InsightDirection _direction;
private readonly TimeSpan _period;
private readonly double? _magnitude;
private readonly double? _confidence;
private readonly double? _weight;
private readonly HashSet<Security> _securities;
private readonly Dictionary<Symbol, DateTime> _insightsTimeBySymbol;
/// <summary>
/// Initializes a new instance of the <see cref="ConstantAlphaModel"/> class
/// </summary>
/// <param name="type">The type of insight</param>
/// <param name="direction">The direction of the insight</param>
/// <param name="period">The period over which the insight with come to fruition</param>
/// <param name="magnitude">The predicted change in magnitude as a +- percentage</param>
/// <param name="confidence">The confidence in the insight</param>
/// <param name="weight">The portfolio weight of the insights</param>
public ConstantAlphaModel(InsightType type, InsightDirection direction, TimeSpan period, double? magnitude = null, double? confidence = null, double? weight = null)
{
_type = type;
_direction = direction;
_period = period;
// Optional
_magnitude = magnitude;
_confidence = confidence;
_weight = weight;
_securities = new HashSet<Security>();
_insightsTimeBySymbol = new Dictionary<Symbol, DateTime>();
Name = $"{nameof(ConstantAlphaModel)}({type},{direction},{period}";
if (magnitude.HasValue)
{
Name += Invariant($",{magnitude.Value}");
}
if (confidence.HasValue)
{
Name += Invariant($",{confidence.Value}");
}
Name += ")";
}
/// <summary>
/// Creates a constant insight for each security as specified via the constructor
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="data">The new data available</param>
/// <returns>The new insights generated</returns>
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
{
foreach (var security in _securities)
{
// security price could be zero until we get the first data point. e.g. this could happen
// when adding both forex and equities, we will first get a forex data point
if (security.Price != 0
&& ShouldEmitInsight(algorithm.UtcTime, security.Symbol))
{
yield return new Insight(security.Symbol, _period, _type, _direction, _magnitude, _confidence, weight: _weight);
}
}
}
/// <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)
{
NotifiedSecurityChanges.UpdateCollection(_securities, changes);
// this will allow the insight to be re-sent when the security re-joins the universe
foreach (var removed in changes.RemovedSecurities)
{
_insightsTimeBySymbol.Remove(removed.Symbol);
}
}
/// <summary>
/// Determine if its time to emit insight for this symbol
/// </summary>
/// <param name="utcTime">Time of the insight</param>
/// <param name="symbol">The symbol to emit an insight for</param>
protected virtual bool ShouldEmitInsight(DateTime utcTime, Symbol symbol)
{
if (symbol.IsCanonical())
{
// canonical futures & options are none tradable
return false;
}
DateTime generatedTimeUtc;
if (_insightsTimeBySymbol.TryGetValue(symbol, out generatedTimeUtc))
{
// we previously emitted a insight for this symbol, check it's period to see
// if we should emit another insight
if (utcTime - generatedTimeUtc < _period)
{
return false;
}
}
// we either haven't emitted a insight for this symbol or the previous
// insight's period has expired, so emit a new insight now for this symbol
_insightsTimeBySymbol[symbol] = utcTime;
return true;
}
}
}
@@ -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 *
class ConstantAlphaModel(AlphaModel):
''' Provides an implementation of IAlphaModel that always returns the same insight for each security'''
def __init__(self, type, direction, period, magnitude = None, confidence = None, weight = None):
'''Initializes a new instance of the ConstantAlphaModel class
Args:
type: The type of insight
direction: The direction of the insight
period: The period over which the insight with come to fruition
magnitude: The predicted change in magnitude as a +- percentage
confidence: The confidence in the insight
weight: The portfolio weight of the insights'''
self.type = type
self.direction = direction
self.period = period
self.magnitude = magnitude
self.confidence = confidence
self.weight = weight
self.securities = []
self.insights_time_by_symbol = {}
self.Name = '{}({},{},{}'.format(self.__class__.__name__, type, direction, strfdelta(period))
if magnitude is not None:
self.Name += ',{}'.format(magnitude)
if confidence is not None:
self.Name += ',{}'.format(confidence)
self.Name += ')'
def update(self, algorithm, data):
''' Creates a constant insight for each security as specified via the constructor
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
insights = []
for security in self.securities:
# security price could be zero until we get the first data point. e.g. this could happen
# when adding both forex and equities, we will first get a forex data point
if security.price != 0 and self.should_emit_insight(algorithm.utc_time, security.symbol):
insights.append(Insight(security.symbol, self.period, self.type, self.direction, self.magnitude, self.confidence, weight = self.weight))
return 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'''
for added in changes.added_securities:
self.securities.append(added)
# this will allow the insight to be re-sent when the security re-joins the universe
for removed in changes.removed_securities:
if removed in self.securities:
self.securities.remove(removed)
if removed.symbol in self.insights_time_by_symbol:
self.insights_time_by_symbol.pop(removed.symbol)
def should_emit_insight(self, utc_time, symbol):
if symbol.is_canonical():
# canonical futures & options are none tradable
return False
generated_time_utc = self.insights_time_by_symbol.get(symbol)
if generated_time_utc is not None:
# we previously emitted a insight for this symbol, check it's period to see
# if we should emit another insight
if utc_time - generated_time_utc < self.period:
return False
# we either haven't emitted a insight for this symbol or the previous
# insight's period has expired, so emit a new insight now for this symbol
self.insights_time_by_symbol[symbol] = utc_time
return True
def strfdelta(tdelta):
d = tdelta.days
h, rem = divmod(tdelta.seconds, 3600)
m, s = divmod(rem, 60)
return "{}.{:02d}:{:02d}:{:02d}".format(d,h,m,s)
@@ -0,0 +1,209 @@
/*
* 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;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Data.Consolidators;
using QuantConnect.Indicators;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Alpha model that uses an EMA cross to create insights
/// </summary>
public class EmaCrossAlphaModel : AlphaModel
{
private readonly int _fastPeriod;
private readonly int _slowPeriod;
private readonly Resolution _resolution;
private readonly int _predictionInterval;
/// <summary>
/// This is made protected for testing purposes
/// </summary>
protected Dictionary<Symbol, SymbolData> SymbolDataBySymbol { get; }
/// <summary>
/// Initializes a new instance of the <see cref="EmaCrossAlphaModel"/> class
/// </summary>
/// <param name="fastPeriod">The fast EMA period</param>
/// <param name="slowPeriod">The slow EMA period</param>
/// <param name="resolution">The resolution of data sent into the EMA indicators</param>
public EmaCrossAlphaModel(
int fastPeriod = 12,
int slowPeriod = 26,
Resolution resolution = Resolution.Daily
)
{
_fastPeriod = fastPeriod;
_slowPeriod = slowPeriod;
_resolution = resolution;
_predictionInterval = fastPeriod;
SymbolDataBySymbol = new Dictionary<Symbol, SymbolData>();
Name = $"{nameof(EmaCrossAlphaModel)}({fastPeriod},{slowPeriod},{resolution})";
}
/// <summary>
/// Updates this alpha model with the latest data from the algorithm.
/// This is called each time the algorithm receives data for subscribed securities
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="data">The new data available</param>
/// <returns>The new insights generated</returns>
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
{
var insights = new List<Insight>();
foreach (var symbolData in SymbolDataBySymbol.Values)
{
if (symbolData.Fast.IsReady && symbolData.Slow.IsReady)
{
var insightPeriod = _resolution.ToTimeSpan().Multiply(_predictionInterval);
if (symbolData.FastIsOverSlow)
{
if (symbolData.Slow > symbolData.Fast)
{
insights.Add(Insight.Price(symbolData.Symbol, insightPeriod, InsightDirection.Down));
}
}
else if (symbolData.SlowIsOverFast)
{
if (symbolData.Fast > symbolData.Slow)
{
insights.Add(Insight.Price(symbolData.Symbol, insightPeriod, InsightDirection.Up));
}
}
}
symbolData.FastIsOverSlow = symbolData.Fast > symbolData.Slow;
}
return insights;
}
/// <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 added in changes.AddedSecurities)
{
SymbolData symbolData;
if (!SymbolDataBySymbol.TryGetValue(added.Symbol, out symbolData))
{
SymbolDataBySymbol[added.Symbol] = new SymbolData(added, _fastPeriod, _slowPeriod, algorithm, _resolution);
}
else
{
// a security that was already initialized was re-added, reset the indicators
symbolData.Fast.Reset();
symbolData.Slow.Reset();
}
}
foreach (var removed in changes.RemovedSecurities)
{
SymbolData symbolData;
if (SymbolDataBySymbol.TryGetValue(removed.Symbol, out symbolData))
{
// clean up our consolidators
symbolData.RemoveConsolidators();
SymbolDataBySymbol.Remove(removed.Symbol);
}
}
}
/// <summary>
/// Contains data specific to a symbol required by this model
/// </summary>
public class SymbolData
{
private readonly QCAlgorithm _algorithm;
private readonly IDataConsolidator _fastConsolidator;
private readonly IDataConsolidator _slowConsolidator;
private readonly ExponentialMovingAverage _fast;
private readonly ExponentialMovingAverage _slow;
private readonly Security _security;
/// <summary>
/// Symbol associated with the data
/// </summary>
public Symbol Symbol => _security.Symbol;
/// <summary>
/// Fast Exponential Moving Average (EMA)
/// </summary>
public ExponentialMovingAverage Fast => _fast;
/// <summary>
/// Slow Exponential Moving Average (EMA)
/// </summary>
public ExponentialMovingAverage Slow => _slow;
/// <summary>
/// True if the fast is above the slow, otherwise false.
/// This is used to prevent emitting the same signal repeatedly
/// </summary>
public bool FastIsOverSlow { get; set; }
/// <summary>
/// Flag indicating if the Slow EMA is over the Fast one
/// </summary>
public bool SlowIsOverFast => !FastIsOverSlow;
/// <summary>
/// Initializes an instance of the class SymbolData with the given arguments
/// </summary>
public SymbolData(
Security security,
int fastPeriod,
int slowPeriod,
QCAlgorithm algorithm,
Resolution resolution)
{
_algorithm = algorithm;
_security = security;
_fastConsolidator = algorithm.ResolveConsolidator(security.Symbol, resolution);
_slowConsolidator = algorithm.ResolveConsolidator(security.Symbol, resolution);
algorithm.SubscriptionManager.AddConsolidator(security.Symbol, _fastConsolidator);
algorithm.SubscriptionManager.AddConsolidator(security.Symbol, _slowConsolidator);
// create fast/slow EMAs
_fast = new ExponentialMovingAverage(security.Symbol, fastPeriod, ExponentialMovingAverage.SmoothingFactorDefault(fastPeriod));
_slow = new ExponentialMovingAverage(security.Symbol, slowPeriod, ExponentialMovingAverage.SmoothingFactorDefault(slowPeriod));
algorithm.RegisterIndicator(security.Symbol, _fast, _fastConsolidator);
algorithm.RegisterIndicator(security.Symbol, _slow, _slowConsolidator);
algorithm.WarmUpIndicator(security.Symbol, _fast, resolution);
algorithm.WarmUpIndicator(security.Symbol, _slow, resolution);
}
/// <summary>
/// Remove Fast and Slow consolidators
/// </summary>
public void RemoveConsolidators()
{
_algorithm.SubscriptionManager.RemoveConsolidator(Symbol, _fastConsolidator);
_algorithm.SubscriptionManager.RemoveConsolidator(Symbol, _slowConsolidator);
}
}
}
}
@@ -0,0 +1,115 @@
# 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 EmaCrossAlphaModel(AlphaModel):
'''Alpha model that uses an EMA cross to create insights'''
def __init__(self,
fast_period = 12,
slow_period = 26,
resolution = Resolution.DAILY):
'''Initializes a new instance of the EmaCrossAlphaModel class
Args:
fast_period: The fast EMA period
slow_period: The slow EMA period'''
self.fast_period = fast_period
self.slow_period = slow_period
self.resolution = resolution
self.prediction_interval = Time.multiply(Extensions.to_time_span(resolution), fast_period)
self.symbol_data_by_symbol = {}
self.name = '{}({},{},{})'.format(self.__class__.__name__, fast_period, slow_period, resolution)
def update(self, algorithm, data):
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
insights = []
for symbol, symbol_data in self.symbol_data_by_symbol.items():
if symbol_data.fast.is_ready and symbol_data.slow.is_ready:
if symbol_data.fast_is_over_slow:
if symbol_data.slow > symbol_data.fast:
insights.append(Insight.price(symbol_data.symbol, self.prediction_interval, InsightDirection.DOWN))
elif symbol_data.slow_is_over_fast:
if symbol_data.fast > symbol_data.slow:
insights.append(Insight.price(symbol_data.symbol, self.prediction_interval, InsightDirection.UP))
symbol_data.fast_is_over_slow = symbol_data.fast > symbol_data.slow
return 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'''
for added in changes.added_securities:
symbol_data = self.symbol_data_by_symbol.get(added.symbol)
if symbol_data is None:
symbol_data = SymbolData(added, self.fast_period, self.slow_period, algorithm, self.resolution)
self.symbol_data_by_symbol[added.symbol] = symbol_data
else:
# a security that was already initialized was re-added, reset the indicators
symbol_data.fast.reset()
symbol_data.slow.reset()
for removed in changes.removed_securities:
data = self.symbol_data_by_symbol.pop(removed.symbol, None)
if data is not None:
# clean up our consolidators
data.remove_consolidators()
class SymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, security, fast_period, slow_period, algorithm, resolution):
self.security = security
self.symbol = security.symbol
self.algorithm = algorithm
self.fast_consolidator = algorithm.resolve_consolidator(security.symbol, resolution)
self.slow_consolidator = algorithm.resolve_consolidator(security.symbol, resolution)
algorithm.subscription_manager.add_consolidator(security.symbol, self.fast_consolidator)
algorithm.subscription_manager.add_consolidator(security.symbol, self.slow_consolidator)
# create fast/slow EMAs
self.fast = ExponentialMovingAverage(security.symbol, fast_period, ExponentialMovingAverage.smoothing_factor_default(fast_period))
self.slow = ExponentialMovingAverage(security.symbol, slow_period, ExponentialMovingAverage.smoothing_factor_default(slow_period))
algorithm.register_indicator(security.symbol, self.fast, self.fast_consolidator);
algorithm.register_indicator(security.symbol, self.slow, self.slow_consolidator);
algorithm.warm_up_indicator(security.symbol, self.fast, resolution);
algorithm.warm_up_indicator(security.symbol, self.slow, resolution);
# True if the fast is above the slow, otherwise false.
# This is used to prevent emitting the same signal repeatedly
self.fast_is_over_slow = False
def remove_consolidators(self):
self.algorithm.subscription_manager.remove_consolidator(self.security.symbol, self.fast_consolidator)
self.algorithm.subscription_manager.remove_consolidator(self.security.symbol, self.slow_consolidator)
@property
def slow_is_over_fast(self):
return not self.fast_is_over_slow
@@ -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.
*/
using System;
using System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Alpha model that uses historical returns to create insights
/// </summary>
public class HistoricalReturnsAlphaModel : AlphaModel
{
private readonly int _lookback;
private readonly Resolution _resolution;
private readonly TimeSpan _predictionInterval;
private readonly Dictionary<Symbol, SymbolData> _symbolDataBySymbol;
private readonly InsightCollection _insightCollection;
/// <summary>
/// Initializes a new instance of the <see cref="HistoricalReturnsAlphaModel"/> class
/// </summary>
/// <param name="lookback">Historical return lookback period</param>
/// <param name="resolution">The resolution of historical data</param>
public HistoricalReturnsAlphaModel(
int lookback = 1,
Resolution resolution = Resolution.Daily
)
{
_lookback = lookback;
_resolution = resolution;
_predictionInterval = _resolution.ToTimeSpan().Multiply(_lookback);
_symbolDataBySymbol = new Dictionary<Symbol, SymbolData>();
_insightCollection = new InsightCollection();
Name = $"{nameof(HistoricalReturnsAlphaModel)}({lookback},{resolution})";
}
/// <summary>
/// Updates this alpha model with the latest data from the algorithm.
/// This is called each time the algorithm receives data for subscribed securities
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="data">The new data available</param>
/// <returns>The new insights generated</returns>
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
{
var insights = new List<Insight>();
foreach (var (symbol, symbolData) in _symbolDataBySymbol)
{
if (symbolData.CanEmit())
{
var direction = InsightDirection.Flat;
var magnitude = (double)symbolData.ROC.Current.Value;
if (magnitude > 0) direction = InsightDirection.Up;
if (magnitude < 0) direction = InsightDirection.Down;
if (direction == InsightDirection.Flat)
{
CancelInsights(algorithm, symbol);
continue;
}
insights.Add(Insight.Price(symbolData.Security.Symbol, _predictionInterval, direction, magnitude, null));
}
}
_insightCollection.AddRange(insights);
return insights;
}
/// <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)
{
// clean up data for removed securities
foreach (var removed in changes.RemovedSecurities)
{
SymbolData data;
if (_symbolDataBySymbol.TryGetValue(removed.Symbol, out data))
{
_symbolDataBySymbol.Remove(removed.Symbol);
algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator);
}
CancelInsights(algorithm, removed.Symbol);
}
// initialize data for added securities
var addedSymbols = new List<Symbol>();
foreach (var added in changes.AddedSecurities)
{
if (!_symbolDataBySymbol.ContainsKey(added.Symbol))
{
var symbolData = new SymbolData(algorithm, added, _lookback, _resolution);
_symbolDataBySymbol[added.Symbol] = symbolData;
addedSymbols.Add(symbolData.Security.Symbol);
}
}
if (addedSymbols.Count > 0)
{
// warmup our indicators by pushing history through the consolidators
algorithm.History(addedSymbols, _lookback, _resolution)
.PushThrough(bar =>
{
SymbolData symbolData;
if (_symbolDataBySymbol.TryGetValue(bar.Symbol, out symbolData))
{
symbolData.ROC.Update(bar.EndTime, bar.Value);
}
});
}
}
private void CancelInsights(QCAlgorithm algorithm, Symbol symbol)
{
if (_insightCollection.TryGetValue(symbol, out var insights))
{
algorithm.Insights.Cancel(insights);
_insightCollection.Clear(new[] { symbol });
}
}
/// <summary>
/// Contains data specific to a symbol required by this model
/// </summary>
private class SymbolData
{
public Security Security;
public IDataConsolidator Consolidator;
public RateOfChange ROC;
public long previous = 0;
public SymbolData(QCAlgorithm algorithm, Security security, int lookback, Resolution resolution)
{
Security = security;
Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution);
algorithm.SubscriptionManager.AddConsolidator(security.Symbol, Consolidator);
ROC = new RateOfChange(security.Symbol.ToString(), lookback);
algorithm.RegisterIndicator(security.Symbol, ROC, Consolidator);
}
public bool CanEmit()
{
if (previous == ROC.Samples) return false;
previous = ROC.Samples;
return ROC.IsReady;
}
}
}
}
@@ -0,0 +1,126 @@
# 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 HistoricalReturnsAlphaModel(AlphaModel):
'''Uses Historical returns to create insights.'''
def __init__(self, *args, **kwargs):
'''Initializes a new default instance of the HistoricalReturnsAlphaModel class.
Args:
lookback(int): Historical return lookback period
resolution: The resolution of historical data'''
self.lookback = kwargs['lookback'] if 'lookback' in kwargs else 1
self.resolution = kwargs['resolution'] if 'resolution' in kwargs else Resolution.DAILY
self.prediction_interval = Time.multiply(Extensions.to_time_span(self.resolution), self.lookback)
self._symbol_data_by_symbol = {}
self.insight_collection = InsightCollection()
def update(self, algorithm, data):
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
insights = []
for symbol, symbol_data in self._symbol_data_by_symbol.items():
if symbol_data.can_emit:
direction = InsightDirection.FLAT
magnitude = symbol_data.return_
if magnitude > 0: direction = InsightDirection.UP
if magnitude < 0: direction = InsightDirection.DOWN
if direction == InsightDirection.FLAT:
self.cancel_insights(algorithm, symbol)
continue
insights.append(Insight.price(symbol, self.prediction_interval, direction, magnitude, None))
self.insight_collection.add_range(insights)
return 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'''
# clean up data for removed securities
for removed in changes.removed_securities:
symbol_data = self._symbol_data_by_symbol.pop(removed.symbol, None)
if symbol_data is not None:
symbol_data.remove_consolidators(algorithm)
self.cancel_insights(algorithm, removed.symbol)
# initialize data for added securities
symbols = [ x.symbol for x in changes.added_securities ]
history = algorithm.history(symbols, self.lookback, 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 = SymbolData(symbol, self.lookback)
self._symbol_data_by_symbol[symbol] = symbol_data
symbol_data.register_indicators(algorithm, self.resolution)
symbol_data.warm_up_indicators(history.loc[ticker])
def cancel_insights(self, algorithm, symbol):
if not self.insight_collection.contains_key(symbol):
return
insights = self.insight_collection[symbol]
algorithm.insights.cancel(insights)
self.insight_collection.clear([ symbol ]);
class SymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, symbol, lookback):
self.symbol = symbol
self.roc = RateOfChange('{}.roc({})'.format(symbol, lookback), lookback)
self.consolidator = None
self.previous = 0
def register_indicators(self, algorithm, resolution):
self.consolidator = algorithm.resolve_consolidator(self.symbol, resolution)
algorithm.register_indicator(self.symbol, self.roc, self.consolidator)
def remove_consolidators(self, algorithm):
if self.consolidator is not None:
algorithm.subscription_manager.remove_consolidator(self.symbol, self.consolidator)
def warm_up_indicators(self, history):
for tuple in history.itertuples():
self.roc.update(tuple.Index, tuple.close)
@property
def return_(self):
return float(self.roc.current.value)
@property
def can_emit(self):
if self.previous == self.roc.samples:
return False
self.previous = self.roc.samples
return self.roc.is_ready
def __str__(self, **kwargs):
return '{}: {:.2%}'.format(self.roc.name, (1 + self.return_)**252 - 1)
@@ -0,0 +1,203 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Defines a custom alpha model that uses MACD crossovers. The MACD signal line is
/// used to generate up/down insights if it's stronger than the bounce threshold.
/// If the MACD signal is within the bounce threshold then a flat price insight is returned.
/// </summary>
public class MacdAlphaModel : AlphaModel
{
private readonly int _fastPeriod;
private readonly int _slowPeriod;
private readonly int _signalPeriod;
private readonly MovingAverageType _movingAverageType;
private readonly Resolution _resolution;
private const decimal BounceThresholdPercent = 0.01m;
private InsightCollection _insightCollection = new();
/// <summary>
/// Dictionary containing basic information for each symbol present as key
/// </summary>
protected Dictionary<Symbol, SymbolData> _symbolData { get; init; }
/// <summary>
/// Initializes a new instance of the <see cref="MacdAlphaModel"/> class
/// </summary>
/// <param name="fastPeriod">The MACD fast period</param>
/// <param name="slowPeriod">The MACD slow period</param>
/// <param name="signalPeriod">The smoothing period for the MACD signal</param>
/// <param name="movingAverageType">The type of moving average to use in the MACD</param>
/// <param name="resolution">The resolution of data sent into the MACD indicator</param>
public MacdAlphaModel(
int fastPeriod = 12,
int slowPeriod = 26,
int signalPeriod = 9,
MovingAverageType movingAverageType = MovingAverageType.Exponential,
Resolution resolution = Resolution.Daily
)
{
_fastPeriod = fastPeriod;
_slowPeriod = slowPeriod;
_signalPeriod = signalPeriod;
_movingAverageType = movingAverageType;
_resolution = resolution;
_symbolData = new Dictionary<Symbol, SymbolData>();
Name = $"{nameof(MacdAlphaModel)}({fastPeriod},{slowPeriod},{signalPeriod},{movingAverageType},{resolution})";
}
/// <summary>
/// Determines an insight for each security based on it's current MACD signal
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="data">The new data available</param>
/// <returns>The new insights generated</returns>
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
{
foreach (var sd in _symbolData.Values)
{
if (sd.Security.Price == 0)
{
continue;
}
var direction = InsightDirection.Flat;
var normalizedSignal = sd.MACD.Signal / sd.Security.Price;
if (normalizedSignal > BounceThresholdPercent)
{
direction = InsightDirection.Up;
}
else if (normalizedSignal < -BounceThresholdPercent)
{
direction = InsightDirection.Down;
}
// ignore signal for same direction as previous signal
if (direction == sd.PreviousDirection)
{
continue;
}
sd.PreviousDirection = direction;
if (direction == InsightDirection.Flat)
{
CancelInsights(algorithm, sd.Security.Symbol);
continue;
}
var insightPeriod = _resolution.ToTimeSpan().Multiply(_fastPeriod);
var insight = Insight.Price(sd.Security.Symbol, insightPeriod, direction);
_insightCollection.Add(insight);
yield return insight;
}
}
/// <summary>
/// Event fired each time the we add/remove securities from the data feed.
/// This initializes the MACD for each added security and cleans up the indicator for each removed security.
/// </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 added in changes.AddedSecurities)
{
if (_symbolData.ContainsKey(added.Symbol))
{
continue;
}
_symbolData.Add(added.Symbol, new SymbolData(algorithm, added, _fastPeriod, _slowPeriod, _signalPeriod, _movingAverageType, _resolution));
}
foreach (var removed in changes.RemovedSecurities)
{
var symbol = removed.Symbol;
SymbolData data;
if (_symbolData.TryGetValue(symbol, out data))
{
// clean up our consolidator
algorithm.SubscriptionManager.RemoveConsolidator(symbol, data.Consolidator);
_symbolData.Remove(symbol);
}
// remove from insight collection manager
CancelInsights(algorithm, symbol);
}
}
private void CancelInsights(QCAlgorithm algorithm, Symbol symbol)
{
if (_insightCollection.TryGetValue(symbol, out var insights))
{
algorithm.Insights.Cancel(insights);
_insightCollection.Clear(new[] { symbol });
}
}
/// <summary>
/// Class representing basic data of a symbol
/// </summary>
public class SymbolData
{
/// <summary>
/// Previous direction property
/// </summary>
public InsightDirection? PreviousDirection { get; set; }
/// <summary>
/// Security of the Symbol Data
/// </summary>
public Security Security { get; init; }
/// <summary>
/// Consolidator property
/// </summary>
public IDataConsolidator Consolidator { get; init; }
/// <summary>
/// Moving Average Convergence Divergence indicator
/// </summary>
public MovingAverageConvergenceDivergence MACD { get; init; }
/// <summary>
/// Initializes an instance of the SymbolData class with the given arguments
/// </summary>
public SymbolData(QCAlgorithm algorithm, Security security, int fastPeriod, int slowPeriod, int signalPeriod, MovingAverageType movingAverageType, Resolution resolution)
{
Security = security;
Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution);
algorithm.SubscriptionManager.AddConsolidator(security.Symbol, Consolidator);
MACD = new MovingAverageConvergenceDivergence(fastPeriod, slowPeriod, signalPeriod, movingAverageType);
algorithm.RegisterIndicator(security.Symbol, MACD, Consolidator);
algorithm.WarmUpIndicator(security.Symbol, MACD, resolution);
}
}
}
}
@@ -0,0 +1,121 @@
# 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 MacdAlphaModel(AlphaModel):
'''Defines a custom alpha model that uses MACD crossovers. The MACD signal line
is used to generate up/down insights if it's stronger than the bounce threshold.
If the MACD signal is within the bounce threshold then a flat price insight is returned.'''
def __init__(self,
fastPeriod = 12,
slowPeriod = 26,
signalPeriod = 9,
movingAverageType = MovingAverageType.Exponential,
resolution = Resolution.Daily):
''' Initializes a new instance of the MacdAlphaModel class
Args:
fastPeriod: The MACD fast period
slowPeriod: The MACD slow period</param>
signalPeriod: The smoothing period for the MACD signal
movingAverageType: The type of moving average to use in the MACD'''
self.fastPeriod = fastPeriod
self.slowPeriod = slowPeriod
self.signalPeriod = signalPeriod
self.movingAverageType = movingAverageType
self.resolution = resolution
self.insightPeriod = Time.Multiply(Extensions.ToTimeSpan(resolution), fastPeriod)
self.bounceThresholdPercent = 0.01
self.insightCollection = InsightCollection()
self.symbolData = {}
self.Name = '{}({},{},{},{},{})'.format(self.__class__.__name__, fastPeriod, slowPeriod, signalPeriod, movingAverageType, resolution)
def Update(self, algorithm, data):
''' Determines an insight for each security based on it's current MACD signal
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
insights = []
for key, sd in self.symbolData.items():
if sd.Security.Price == 0:
continue
direction = InsightDirection.Flat
normalized_signal = sd.MACD.Signal.Current.Value / sd.Security.Price
if normalized_signal > self.bounceThresholdPercent:
direction = InsightDirection.Up
elif normalized_signal < -self.bounceThresholdPercent:
direction = InsightDirection.Down
# ignore signal for same direction as previous signal
if direction == sd.PreviousDirection:
continue
sd.PreviousDirection = direction
if direction == InsightDirection.Flat:
self.CancelInsights(algorithm, sd.Security.Symbol)
continue
insight = Insight.Price(sd.Security.Symbol, self.insightPeriod, direction)
insights.append(insight)
self.insightCollection.Add(insight)
return insights
def OnSecuritiesChanged(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed.
This initializes the MACD for each added security and cleans up the indicator for each removed security.
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
for added in changes.AddedSecurities:
self.symbolData[added.Symbol] = SymbolData(algorithm, added, self.fastPeriod, self.slowPeriod, self.signalPeriod, self.movingAverageType, self.resolution)
for removed in changes.RemovedSecurities:
symbol = removed.Symbol
data = self.symbolData.pop(symbol, None)
if data is not None:
# clean up our consolidator
algorithm.SubscriptionManager.RemoveConsolidator(symbol, data.Consolidator)
# remove from insight collection manager
self.CancelInsights(algorithm, symbol)
def CancelInsights(self, algorithm, symbol):
if not self.insightCollection.ContainsKey(symbol):
return
insights = self.insightCollection[symbol]
algorithm.Insights.Cancel(insights)
self.insightCollection.Clear([ symbol ]);
class SymbolData:
def __init__(self, algorithm, security, fastPeriod, slowPeriod, signalPeriod, movingAverageType, resolution):
self.Security = security
self.MACD = MovingAverageConvergenceDivergence(fastPeriod, slowPeriod, signalPeriod, movingAverageType)
self.Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
algorithm.RegisterIndicator(security.Symbol, self.MACD, self.Consolidator)
algorithm.WarmUpIndicator(security.Symbol, self.MACD, resolution)
self.PreviousDirection = None
@@ -0,0 +1,149 @@
/*
* 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 MathNet.Numerics.Statistics;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using System;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// This alpha model is designed to rank every pair combination by its pearson correlation
/// and trade the pair with the hightest correlation
/// This model generates alternating long ratio/short ratio insights emitted as a group
/// </summary>
public class PearsonCorrelationPairsTradingAlphaModel : BasePairsTradingAlphaModel
{
private readonly int _lookback;
private readonly Resolution _resolution;
private readonly double _minimumCorrelation;
private Tuple<Symbol, Symbol> _bestPair;
/// <summary>
/// Initializes a new instance of the <see cref="PearsonCorrelationPairsTradingAlphaModel"/> class
/// </summary>
/// <param name="lookback">Lookback period of the analysis</param>
/// <param name="resolution">Analysis resolution</param>
/// <param name="threshold">The percent [0, 100] deviation of the ratio from the mean before emitting an insight</param>
/// <param name="minimumCorrelation">The minimum correlation to consider a tradable pair</param>
public PearsonCorrelationPairsTradingAlphaModel(int lookback = 15, Resolution resolution = Resolution.Minute, decimal threshold = 1m, double minimumCorrelation = .5)
: base(lookback, resolution, threshold)
{
_lookback = lookback;
_resolution = resolution;
_minimumCorrelation = minimumCorrelation;
}
/// <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)
{
NotifiedSecurityChanges.UpdateCollection(Securities, changes);
var symbols = Securities.Select(x => x.Symbol).ToArray();
var history = algorithm.History(symbols, _lookback, _resolution);
var vectors = GetPriceVectors(history);
if (vectors.LongLength == 0)
{
algorithm.Debug($"PearsonCorrelationPairsTradingAlphaModel.OnSecuritiesChanged(): The requested historical data does not have series of prices with the same date/time. Please consider increasing the looback period. Current lookback: {_lookback}");
}
else
{
var pearsonMatrix = Correlation.PearsonMatrix(vectors).UpperTriangle();
var maxValue = pearsonMatrix.Enumerate().Where(x => Math.Abs(x) < 1).Max();
if (maxValue >= _minimumCorrelation)
{
var maxTuple = pearsonMatrix.Find(x => x == maxValue);
_bestPair = Tuple.Create(symbols[maxTuple.Item1], symbols[maxTuple.Item2]);
}
}
base.OnSecuritiesChanged(algorithm, changes);
}
/// <summary>
/// Check whether the assets pass a pairs trading test
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="asset1">The first asset's symbol in the pair</param>
/// <param name="asset2">The second asset's symbol in the pair</param>
/// <returns>True if the statistical test for the pair is successful</returns>
public override bool HasPassedTest(QCAlgorithm algorithm, Symbol asset1, Symbol asset2)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(HasPassedTest), out bool result, algorithm, asset1, asset2))
{
return result;
}
return _bestPair != null && asset1 == _bestPair.Item1 && asset2 == _bestPair.Item2;
}
private double[][] GetPriceVectors(IEnumerable<Slice> slices)
{
var symbols = Securities.Select(x => x.Symbol).ToArray();
var timeZones = Securities.ToDictionary(x => x.Symbol, y => y.Exchange.TimeZone);
// Special case: daily data and securities from different timezone
var isDailyAndMultipleTimeZone = _resolution == Resolution.Daily && timeZones.Values.Distinct().Count() > 1;
var bars = new List<BaseData>();
if (isDailyAndMultipleTimeZone)
{
bars.AddRange(slices
.GroupBy(x => x.Time.Date)
.Where(x => x.Sum(k => k.Count) == symbols.Length)
.SelectMany(x => x.SelectMany(y => y.Values)));
}
else
{
bars.AddRange(slices
.Where(x => x.Count == symbols.Length)
.SelectMany(x => x.Values));
}
return bars
.GroupBy(x => x.Symbol)
.Select(x =>
{
var array = x.Select(b => Math.Log((double)b.Price)).ToArray();
if (array.Length > 1)
{
for (var i = array.Length - 1; i > 0; i--)
{
array[i] = array[i] - array[i - 1];
}
array[0] = array[1];
return array;
}
else
{
return new double[0];
}
}).ToArray();
}
}
}
@@ -0,0 +1,108 @@
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from AlgorithmImports import *
from Alphas.BasePairsTradingAlphaModel import BasePairsTradingAlphaModel
from scipy.stats import pearsonr
class PearsonCorrelationPairsTradingAlphaModel(BasePairsTradingAlphaModel):
''' This alpha model is designed to rank every pair combination by its pearson correlation
and trade the pair with the hightest correlation
This model generates alternating long ratio/short ratio insights emitted as a group'''
def __init__(self, lookback = 15,
resolution = Resolution.MINUTE,
threshold = 1,
minimum_correlation = .5):
'''Initializes a new instance of the PearsonCorrelationPairsTradingAlphaModel class
Args:
lookback: lookback period of the analysis
resolution: analysis resolution
threshold: The percent [0, 100] deviation of the ratio from the mean before emitting an insight
minimum_correlation: The minimum correlation to consider a tradable pair'''
super().__init__(lookback, resolution, threshold)
self.lookback = lookback
self.resolution = resolution
self.minimum_correlation = minimum_correlation
self.best_pair = ()
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.added_securities:
self.securities.add(security)
for security in changes.removed_securities:
if security in self.securities:
self.securities.remove(security)
symbols = sorted([ x.symbol for x in self.securities ])
history = algorithm.history(symbols, self.lookback, self.resolution)
if not history.empty:
history = history.close.unstack(level=0)
df = self.get_price_dataframe(history)
stop = len(df.columns)
corr = dict()
for i in range(0, stop):
for j in range(i+1, stop):
if (j, i) not in corr:
corr[(i, j)] = pearsonr(df.iloc[:,i], df.iloc[:,j])[0]
corr = sorted(corr.items(), key = lambda kv: kv[1])
if corr[-1][1] >= self.minimum_correlation:
self.best_pair = (symbols[corr[-1][0][0]], symbols[corr[-1][0][1]])
super().on_securities_changed(algorithm, changes)
def has_passed_test(self, algorithm, asset1, asset2):
'''Check whether the assets pass a pairs trading test
Args:
algorithm: The algorithm instance that experienced the change in securities
asset1: The first asset's symbol in the pair
asset2: The second asset's symbol in the pair
Returns:
True if the statistical test for the pair is successful'''
return self.best_pair is not None and self.best_pair[0] == asset1 and self.best_pair[1] == asset2
def get_price_dataframe(self, df):
timezones = { x.symbol.value: x.exchange.time_zone for x in self.securities }
# Use log prices
df = np.log(df)
is_single_timeZone = len(set(timezones.values())) == 1
if not is_single_timeZone:
series_dict = dict()
for column in df:
# Change the dataframe index from data time to UTC time
to_utc = lambda x: Extensions.convert_to_utc(x, timezones[column])
if self.resolution == Resolution.DAILY:
to_utc = lambda x: Extensions.convert_to_utc(x, timezones[column]).date()
data = df[[column]]
data.index = data.index.map(to_utc)
series_dict[column] = data[column]
df = pd.DataFrame(series_dict).dropna()
return (df - df.shift(1)).dropna()
+214
View File
@@ -0,0 +1,214 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Uses Wilder's RSI to create insights. Using default settings, a cross over below 30 or above 70 will
/// trigger a new insight.
/// </summary>
public class RsiAlphaModel : AlphaModel
{
private readonly Dictionary<Symbol, SymbolData> _symbolDataBySymbol = new Dictionary<Symbol, SymbolData>();
private readonly int _period;
private readonly Resolution _resolution;
/// <summary>
/// Initializes a new instance of the <see cref="RsiAlphaModel"/> class
/// </summary>
/// <param name="period">The RSI indicator period</param>
/// <param name="resolution">The resolution of data sent into the RSI indicator</param>
public RsiAlphaModel(
int period = 14,
Resolution resolution = Resolution.Daily
)
{
_period = period;
_resolution = resolution;
Name = $"{nameof(RsiAlphaModel)}({_period},{_resolution})";
}
/// <summary>
/// Updates this alpha model with the latest data from the algorithm.
/// This is called each time the algorithm receives data for subscribed securities
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="data">The new data available</param>
/// <returns>The new insights generated</returns>
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
{
var insights = new List<Insight>();
foreach (var kvp in _symbolDataBySymbol)
{
var symbol = kvp.Key;
var rsi = kvp.Value.RSI;
var previousState = kvp.Value.State;
var state = GetState(rsi, previousState);
if (state != previousState && rsi.IsReady)
{
var insightPeriod = _resolution.ToTimeSpan().Multiply(_period);
switch (state)
{
case State.TrippedLow:
insights.Add(Insight.Price(symbol, insightPeriod, InsightDirection.Up));
break;
case State.TrippedHigh:
insights.Add(Insight.Price(symbol, insightPeriod, InsightDirection.Down));
break;
}
}
kvp.Value.State = state;
}
return insights;
}
/// <summary>
/// Cleans out old security data and initializes the RSI for any newly added securities.
/// This functional also seeds any new indicators using a history request.
/// </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)
{
// clean up data for removed securities
foreach (var security in changes.RemovedSecurities)
{
SymbolData symbolData;
if (_symbolDataBySymbol.TryGetValue(security.Symbol, out symbolData))
{
_symbolDataBySymbol.Remove(security.Symbol);
symbolData.Dispose();
}
}
// initialize data for added securities
var addedSymbols = new List<Symbol>();
foreach (var added in changes.AddedSecurities)
{
if (!_symbolDataBySymbol.ContainsKey(added.Symbol))
{
var symbolData = new SymbolData(algorithm, added.Symbol, _period, _resolution);
_symbolDataBySymbol[added.Symbol] = symbolData;
addedSymbols.Add(added.Symbol);
}
}
if (addedSymbols.Count > 0)
{
// warmup our indicators by pushing history through the consolidators
algorithm.History(addedSymbols, _period, _resolution)
.PushThrough(data =>
{
SymbolData symbolData;
if (_symbolDataBySymbol.TryGetValue(data.Symbol, out symbolData))
{
symbolData.Update(data);
}
});
}
}
/// <summary>
/// Determines the new state. This is basically cross-over detection logic that
/// includes considerations for bouncing using the configured bounce tolerance.
/// </summary>
private State GetState(RelativeStrengthIndex rsi, State previous)
{
if (rsi > 70m)
{
return State.TrippedHigh;
}
if (rsi < 30m)
{
return State.TrippedLow;
}
if (previous == State.TrippedLow)
{
if (rsi > 35m)
{
return State.Middle;
}
}
if (previous == State.TrippedHigh)
{
if (rsi < 65m)
{
return State.Middle;
}
}
return previous;
}
/// <summary>
/// Contains data specific to a symbol required by this model
/// </summary>
private class SymbolData : IDisposable
{
public State State { get; set; }
public RelativeStrengthIndex RSI { get; }
private Symbol _symbol { get; }
private QCAlgorithm _algorithm;
private IDataConsolidator _consolidator;
public SymbolData(QCAlgorithm algorithm, Symbol symbol, int period, Resolution resolution)
{
_algorithm = algorithm;
_symbol = symbol;
State = State.Middle;
RSI = new RelativeStrengthIndex(period, MovingAverageType.Wilders);
_consolidator = _algorithm.ResolveConsolidator(symbol, resolution);
algorithm.RegisterIndicator(symbol, RSI, _consolidator);
}
public void Update(BaseData bar)
{
_consolidator.Update(bar);
}
public void Dispose()
{
_algorithm.SubscriptionManager.RemoveConsolidator(_symbol, _consolidator);
}
}
/// <summary>
/// Defines the state. This is used to prevent signal spamming and aid in bounce detection.
/// </summary>
private enum State
{
TrippedLow,
Middle,
TrippedHigh
}
}
}
+127
View File
@@ -0,0 +1,127 @@
# 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 QuantConnect.Logging import *
from enum import Enum
class RsiAlphaModel(AlphaModel):
'''Uses Wilder's RSI to create insights.
Using default settings, a cross over below 30 or above 70 will trigger a new insight.'''
def __init__(self,
period = 14,
resolution = Resolution.DAILY):
'''Initializes a new instance of the RsiAlphaModel class
Args:
period: The RSI indicator period'''
self.period = period
self.resolution = resolution
self.insight_period = Time.multiply(Extensions.to_time_span(resolution), period)
self.symbol_data_by_symbol ={}
self.name = '{}({},{})'.format(self.__class__.__name__, period, resolution)
def update(self, algorithm, data):
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
insights = []
for symbol, symbol_data in self.symbol_data_by_symbol.items():
rsi = symbol_data.rsi
previous_state = symbol_data.state
state = self.get_state(rsi, previous_state)
if state != previous_state and rsi.is_ready:
if state == State.TRIPPED_LOW:
insights.append(Insight.price(symbol, self.insight_period, InsightDirection.UP))
if state == State.TRIPPED_HIGH:
insights.append(Insight.price(symbol, self.insight_period, InsightDirection.DOWN))
symbol_data.state = state
return insights
def on_securities_changed(self, algorithm, changes):
'''Cleans out old security data and initializes the RSI for any newly added securities.
Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
# clean up data for removed securities
for security in changes.removed_securities:
symbol_data = self.symbol_data_by_symbol.pop(security.symbol, None)
if symbol_data:
symbol_data.dispose()
# initialize data for added securities
added_symbols = []
for security in changes.added_securities:
symbol = security.symbol
if symbol not in self.symbol_data_by_symbol:
symbol_data = SymbolData(algorithm, symbol, self.period, self.resolution)
self.symbol_data_by_symbol[symbol] = symbol_data
added_symbols.append(symbol)
if added_symbols:
history = algorithm.history[TradeBar](added_symbols, self.period, self.resolution)
for trade_bars in history:
for bar in trade_bars.values():
self.symbol_data_by_symbol[bar.symbol].update(bar)
def get_state(self, rsi, previous):
''' Determines the new state. This is basically cross-over detection logic that
includes considerations for bouncing using the configured bounce tolerance.'''
if rsi.current.value > 70:
return State.TRIPPED_HIGH
if rsi.current.value < 30:
return State.TRIPPED_LOW
if previous == State.TRIPPED_LOW:
if rsi.current.value > 35:
return State.MIDDLE
if previous == State.TRIPPED_HIGH:
if rsi.current.value < 65:
return State.MIDDLE
return previous
class SymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, algorithm, symbol, period, resolution):
self.algorithm = algorithm
self.symbol = symbol
self.state = State.MIDDLE
self.rsi = RelativeStrengthIndex(period, MovingAverageType.WILDERS)
self.consolidator = algorithm.resolve_consolidator(symbol, resolution)
algorithm.register_indicator(symbol, self.rsi, self.consolidator)
def update(self, bar):
self.consolidator.update(bar)
def dispose(self):
self.algorithm.subscription_manager.remove_consolidator(self.symbol, self.consolidator)
class State(Enum):
'''Defines the state. This is used to prevent signal spamming and aid in bounce detection.'''
TRIPPED_LOW = 0
MIDDLE = 1
TRIPPED_HIGH = 2
@@ -0,0 +1,99 @@
/*
* 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 QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Algorithm.Framework.Portfolio;
namespace QuantConnect.Algorithm.Framework.Execution
{
/// <summary>
/// Execution model that submits orders while the current spread is in desirably tight extent.
/// </summary>
/// <remarks>Note this execution model will not work using <see cref="Resolution.Daily"/>
/// since Exchange.ExchangeOpen will be false, suggested resolution is <see cref="Resolution.Minute"/></remarks>
public class SpreadExecutionModel : ExecutionModel
{
private readonly decimal _acceptingSpreadPercent;
private readonly PortfolioTargetCollection _targetsCollection;
/// <summary>
/// Initializes a new instance of the <see cref="SpreadExecutionModel"/> class
/// </summary>
/// <param name="acceptingSpreadPercent">Maximum spread accepted comparing to current price in percentage.</param>
/// <param name="asynchronous">If true, orders will be submitted asynchronously</param>
public SpreadExecutionModel(decimal acceptingSpreadPercent = 0.005m, bool asynchronous = true)
: base(asynchronous)
{
_acceptingSpreadPercent = Math.Abs(acceptingSpreadPercent);
_targetsCollection = new PortfolioTargetCollection();
}
/// <summary>
/// Submit orders for the specified portfolio targets if the spread is tighter/equal to preset level
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="targets">The portfolio targets to be ordered</param>
public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
// update the complete set of portfolio targets with the new targets
_targetsCollection.AddRange(targets);
// for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if (!_targetsCollection.IsEmpty)
{
foreach (var target in _targetsCollection.OrderByMarginImpact(algorithm))
{
var symbol = target.Symbol;
// calculate remaining quantity to be ordered
var unorderedQuantity = OrderSizing.GetUnorderedQuantity(algorithm, target);
if (unorderedQuantity != 0)
{
// get security object
var security = algorithm.Securities[symbol];
// check order entry conditions
if (PriceIsFavorable(security))
{
algorithm.MarketOrder(symbol, unorderedQuantity, Asynchronous, target.Tag);
}
}
}
_targetsCollection.ClearFulfilled(algorithm);
}
}
/// <summary>
/// Determines if the current spread is equal or tighter than preset level
/// </summary>
protected virtual bool PriceIsFavorable(Security security)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(PriceIsFavorable), out bool result, security))
{
return result;
}
// Has to be in opening hours of exchange to avoid extreme spread in OTC period
// Price has to be larger than zero to avoid zero division error, or negative price causing the spread percentage lower than preset value by accident
return security.Exchange.ExchangeOpen
&& security.Price > 0 && security.AskPrice > 0 && security.BidPrice > 0
&& (security.AskPrice - security.BidPrice) / security.Price <= _acceptingSpreadPercent;
}
}
}
@@ -0,0 +1,60 @@
# 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 SpreadExecutionModel(ExecutionModel):
'''Execution model that submits orders while the current spread is tight.
Note this execution model will not work using Resolution.DAILY since Exchange.exchange_open will be false, suggested resolution is Minute
'''
def __init__(self, accepting_spread_percent=0.005, asynchronous=True):
'''Initializes a new instance of the SpreadExecutionModel class'''
super().__init__(asynchronous)
self.targets_collection = PortfolioTargetCollection()
# Gets or sets the maximum spread compare to current price in percentage.
self.accepting_spread_percent = Math.abs(accepting_spread_percent)
def execute(self, algorithm, targets):
'''Executes market orders if the spread percentage to price is in desirable range.
Args:
algorithm: The algorithm instance
targets: The portfolio targets'''
# update the complete set of portfolio targets with the new targets
self.targets_collection.add_range(targets)
# for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if not self.targets_collection.is_empty:
for target in self.targets_collection.order_by_margin_impact(algorithm):
symbol = target.symbol
# calculate remaining quantity to be ordered
unordered_quantity = OrderSizing.get_unordered_quantity(algorithm, target)
# check order entry conditions
if unordered_quantity != 0:
# get security information
security = algorithm.securities[symbol]
if self.spread_is_favorable(security):
algorithm.market_order(symbol, unordered_quantity, self.asynchronous, target.tag)
self.targets_collection.clear_fulfilled(algorithm)
def spread_is_favorable(self, security):
'''Determines if the spread is in desirable range.'''
# Price has to be larger than zero to avoid zero division error, or negative price causing the spread percentage < 0 by error
# Has to be in opening hours of exchange to avoid extreme spread in OTC period
return security.exchange.exchange_open \
and security.price > 0 and security.ask_price > 0 and security.bid_price > 0 \
and (security.ask_price - security.bid_price) / security.price <= self.accepting_spread_percent
@@ -0,0 +1,231 @@
/*
* 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.Algorithm.Framework.Portfolio;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Securities;
using QuantConnect.Orders;
namespace QuantConnect.Algorithm.Framework.Execution
{
/// <summary>
/// Execution model that submits orders while the current market prices is at least the configured number of standard
/// deviations away from the mean in the favorable direction (below/above for buy/sell respectively)
/// </summary>
public class StandardDeviationExecutionModel : ExecutionModel
{
private readonly int _period;
private readonly decimal _deviations;
private readonly Resolution _resolution;
private readonly PortfolioTargetCollection _targetsCollection;
private readonly Dictionary<Symbol, SymbolData> _symbolData;
/// <summary>
/// Gets or sets the maximum order value in units of the account currency.
/// This defaults to $20,000. For example, if purchasing a stock with a price
/// of $100, then the maximum order size would be 200 shares.
/// </summary>
public decimal MaximumOrderValue { get; set; } = 20 * 1000;
/// <summary>
/// Initializes a new instance of the <see cref="StandardDeviationExecutionModel"/> class
/// </summary>
/// <param name="period">Period of the standard deviation indicator</param>
/// <param name="deviations">The number of deviations away from the mean before submitting an order</param>
/// <param name="resolution">The resolution of the STD and SMA indicators</param>
/// <param name="asynchronous">If true, orders should be submitted asynchronously</param>
public StandardDeviationExecutionModel(
int period = 60,
decimal deviations = 2m,
Resolution resolution = Resolution.Minute,
bool asynchronous = true
)
: base(asynchronous)
{
_period = period;
_deviations = deviations;
_resolution = resolution;
_targetsCollection = new PortfolioTargetCollection();
_symbolData = new Dictionary<Symbol, SymbolData>();
}
/// <summary>
/// Executes market orders if the standard deviation of price is more than the configured number of deviations
/// in the favorable direction.
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="targets">The portfolio targets</param>
public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
_targetsCollection.AddRange(targets);
// for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if (!_targetsCollection.IsEmpty)
{
foreach (var target in _targetsCollection.OrderByMarginImpact(algorithm))
{
var symbol = target.Symbol;
// calculate remaining quantity to be ordered
var unorderedQuantity = OrderSizing.GetUnorderedQuantity(algorithm, target);
// fetch our symbol data containing our STD/SMA indicators
SymbolData data;
if (!_symbolData.TryGetValue(symbol, out data))
{
continue;
}
// check order entry conditions
if (data.STD.IsReady && PriceIsFavorable(data, unorderedQuantity))
{
// Adjust order size to respect the maximum total order value
var orderSize = OrderSizing.GetOrderSizeForMaximumValue(data.Security, MaximumOrderValue, unorderedQuantity);
if (orderSize != 0)
{
algorithm.MarketOrder(symbol, orderSize, Asynchronous, target.Tag);
}
}
}
_targetsCollection.ClearFulfilled(algorithm);
}
}
/// <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 added in changes.AddedSecurities)
{
// initialize new securities
if (!_symbolData.ContainsKey(added.Symbol))
{
_symbolData[added.Symbol] = new SymbolData(algorithm, added, _period, _resolution);
}
}
foreach (var removed in changes.RemovedSecurities)
{
// clean up data from removed securities
SymbolData data;
if (_symbolData.TryGetValue(removed.Symbol, out data))
{
if (IsSafeToRemove(algorithm, removed.Symbol))
{
_symbolData.Remove(removed.Symbol);
algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator);
}
}
}
}
/// <summary>
/// Determines if the current price is more than the configured number of standard deviations
/// away from the mean in the favorable direction.
/// </summary>
protected virtual bool PriceIsFavorable(SymbolData data, decimal unorderedQuantity)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(PriceIsFavorable), out bool result, data, unorderedQuantity))
{
return result;
}
var deviations = _deviations * data.STD;
return unorderedQuantity > 0
? data.Security.BidPrice < data.SMA - deviations
: data.Security.AskPrice > data.SMA + deviations;
}
/// <summary>
/// Determines if it's safe to remove the associated symbol data
/// </summary>
protected virtual bool IsSafeToRemove(QCAlgorithm algorithm, Symbol symbol)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(IsSafeToRemove), out bool result, algorithm, symbol))
{
return result;
}
// confirm the security isn't currently a member of any universe
return !algorithm.UniverseManager.Any(kvp => kvp.Value.ContainsMember(symbol));
}
/// <summary>
/// Symbol Data for this Execution Model
/// </summary>
protected class SymbolData
{
/// <summary>
/// Security
/// </summary>
public Security Security { get; }
/// <summary>
/// Standard Deviation
/// </summary>
public StandardDeviation STD { get; }
/// <summary>
/// Simple Moving Average
/// </summary>
public SimpleMovingAverage SMA { get; }
/// <summary>
/// Data Consolidator
/// </summary>
public IDataConsolidator Consolidator { get; }
/// <summary>
/// Initialize an instance of <see cref="SymbolData"/>
/// </summary>
/// <param name="algorithm">Algorithm for this security</param>
/// <param name="security">The security we are using</param>
/// <param name="period">Period of the SMA and STD</param>
/// <param name="resolution">Resolution for this symbol</param>
public SymbolData(QCAlgorithm algorithm, Security security, int period, Resolution resolution)
{
Security = security;
Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution);
var smaName = algorithm.CreateIndicatorName(security.Symbol, "SMA" + period, resolution);
SMA = new SimpleMovingAverage(smaName, period);
algorithm.RegisterIndicator(security.Symbol, SMA, Consolidator);
var stdName = algorithm.CreateIndicatorName(security.Symbol, "STD" + period, resolution);
STD = new StandardDeviation(stdName, period);
algorithm.RegisterIndicator(security.Symbol, STD, Consolidator);
// warmup our indicators by pushing history through the indicators
foreach (var bar in algorithm.History(Security.Symbol, period, resolution))
{
SMA.Update(bar.EndTime, bar.Value);
STD.Update(bar.EndTime, bar.Value);
}
}
}
}
}
@@ -0,0 +1,127 @@
# 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 StandardDeviationExecutionModel(ExecutionModel):
'''Execution model that submits orders while the current market prices is at least the configured number of standard
deviations away from the mean in the favorable direction (below/above for buy/sell respectively)'''
def __init__(self,
period = 60,
deviations = 2,
resolution = Resolution.MINUTE,
asynchronous=True):
'''Initializes a new instance of the StandardDeviationExecutionModel class
Args:
period: Period of the standard deviation indicator
deviations: The number of deviations away from the mean before submitting an order
resolution: The resolution of the STD and SMA indicators
asynchronous: If True, orders will be submitted asynchronously.'''
super().__init__(asynchronous)
self.period = period
self.deviations = deviations
self.resolution = resolution
self.targets_collection = PortfolioTargetCollection()
self._symbol_data = {}
# Gets or sets the maximum order value in units of the account currency.
# This defaults to $20,000. For example, if purchasing a stock with a price
# of $100, then the maximum order size would be 200 shares.
self.maximum_order_value = 20000
def execute(self, algorithm, targets):
'''Executes market orders if the standard deviation of price is more
than the configured number of deviations in the favorable direction.
Args:
algorithm: The algorithm instance
targets: The portfolio targets'''
self.targets_collection.add_range(targets)
# for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if not self.targets_collection.is_empty:
for target in self.targets_collection.order_by_margin_impact(algorithm):
symbol = target.symbol
# calculate remaining quantity to be ordered
unordered_quantity = OrderSizing.get_unordered_quantity(algorithm, target)
# fetch our symbol data containing our STD/SMA indicators
data = self._symbol_data.get(symbol, None)
if data is None: return
# check order entry conditions
if data.std.is_ready and self.price_is_favorable(data, unordered_quantity):
# Adjust order size to respect the maximum total order value
order_size = OrderSizing.get_order_size_for_maximum_value(data.security, self.maximum_order_value, unordered_quantity)
if order_size != 0:
algorithm.market_order(symbol, order_size, self.asynchronous, target.tag)
self.targets_collection.clear_fulfilled(algorithm)
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 added in changes.added_securities:
if added.symbol not in self._symbol_data:
self._symbol_data[added.symbol] = SymbolData(algorithm, added, self.period, self.resolution)
for removed in changes.removed_securities:
# clean up data from removed securities
symbol = removed.symbol
if symbol in self._symbol_data:
if self.is_safe_to_remove(algorithm, symbol):
data = self._symbol_data.pop(symbol)
algorithm.subscription_manager.remove_consolidator(symbol, data.consolidator)
def price_is_favorable(self, data, unordered_quantity):
'''Determines if the current price is more than the configured
number of standard deviations away from the mean in the favorable direction.'''
sma = data.sma.current.value
deviations = self.deviations * data.std.current.value
if unordered_quantity > 0:
return data.security.bid_price < sma - deviations
else:
return data.security.ask_price > sma + deviations
def is_safe_to_remove(self, algorithm, symbol):
'''Determines if it's safe to remove the associated symbol data'''
# confirm the security isn't currently a member of any universe
return not any([kvp.value.contains_member(symbol) for kvp in algorithm.universe_manager])
class SymbolData:
def __init__(self, algorithm, security, period, resolution):
symbol = security.symbol
self.security = security
self.consolidator = algorithm.resolve_consolidator(symbol, resolution)
sma_name = algorithm.create_indicator_name(symbol, f"SMA{period}", resolution)
self.sma = SimpleMovingAverage(sma_name, period)
algorithm.register_indicator(symbol, self.sma, self.consolidator)
std_name = algorithm.create_indicator_name(symbol, f"STD{period}", resolution)
self.std = StandardDeviation(std_name, period)
algorithm.register_indicator(symbol, self.std, self.consolidator)
# warmup our indicators by pushing history through the indicators
bars = algorithm.history[self.consolidator.input_type](symbol, period, resolution)
for bar in bars:
self.sma.update(bar.end_time, bar.close)
self.std.update(bar.end_time, bar.close)
@@ -0,0 +1,207 @@
/*
* 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.Algorithm.Framework.Portfolio;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Securities;
using QuantConnect.Orders;
namespace QuantConnect.Algorithm.Framework.Execution
{
/// <summary>
/// Execution model that submits orders while the current market price is more favorable that the current volume weighted average price.
/// </summary>
public class VolumeWeightedAveragePriceExecutionModel : ExecutionModel
{
private readonly PortfolioTargetCollection _targetsCollection = new PortfolioTargetCollection();
private readonly Dictionary<Symbol, SymbolData> _symbolData = new Dictionary<Symbol, SymbolData>();
/// <summary>
/// Gets or sets the maximum order quantity as a percentage of the current bar's volume.
/// This defaults to 0.01m = 1%. For example, if the current bar's volume is 100, then
/// the maximum order size would equal 1 share.
/// </summary>
public decimal MaximumOrderQuantityPercentVolume { get; set; } = 0.01m;
/// <summary>
/// Initializes a new instance of the <see cref="VolumeWeightedAveragePriceExecutionModel"/> class.
/// </summary>
/// <param name="asynchronous">If true, orders will be submitted asynchronously</param>
public VolumeWeightedAveragePriceExecutionModel(bool asynchronous = true)
: base(asynchronous)
{
}
/// <summary>
/// Submit orders for the specified portfolio targets.
/// This model is free to delay or spread out these orders as it sees fit
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="targets">The portfolio targets to be ordered</param>
public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
// update the complete set of portfolio targets with the new targets
_targetsCollection.AddRange(targets);
// for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if (!_targetsCollection.IsEmpty)
{
foreach (var target in _targetsCollection.OrderByMarginImpact(algorithm))
{
var symbol = target.Symbol;
// calculate remaining quantity to be ordered
var unorderedQuantity = OrderSizing.GetUnorderedQuantity(algorithm, target);
// fetch our symbol data containing our VWAP indicator
SymbolData data;
if (!_symbolData.TryGetValue(symbol, out data))
{
continue;
}
// check order entry conditions
if (PriceIsFavorable(data, unorderedQuantity))
{
// adjust order size to respect maximum order size based on a percentage of current volume
var orderSize = OrderSizing.GetOrderSizeForPercentVolume(
data.Security, MaximumOrderQuantityPercentVolume, unorderedQuantity);
if (orderSize != 0)
{
algorithm.MarketOrder(data.Security.Symbol, orderSize, Asynchronous, target.Tag);
}
}
}
_targetsCollection.ClearFulfilled(algorithm);
}
}
/// <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 added in changes.AddedSecurities)
{
if (!_symbolData.ContainsKey(added.Symbol))
{
_symbolData[added.Symbol] = new SymbolData(algorithm, added);
}
}
foreach (var removed in changes.RemovedSecurities)
{
// clean up removed security data
SymbolData data;
if (_symbolData.TryGetValue(removed.Symbol, out data))
{
if (IsSafeToRemove(algorithm, removed.Symbol))
{
_symbolData.Remove(removed.Symbol);
algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator);
}
}
}
}
/// <summary>
/// Determines if it's safe to remove the associated symbol data
/// </summary>
protected virtual bool IsSafeToRemove(QCAlgorithm algorithm, Symbol symbol)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(IsSafeToRemove), out bool result, algorithm, symbol))
{
return result;
}
// confirm the security isn't currently a member of any universe
return !algorithm.UniverseManager.Any(kvp => kvp.Value.ContainsMember(symbol));
}
/// <summary>
/// Determines if the current price is better than VWAP
/// </summary>
protected virtual bool PriceIsFavorable(SymbolData data, decimal unorderedQuantity)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(PriceIsFavorable), out bool result, data, unorderedQuantity))
{
return result;
}
if (unorderedQuantity > 0)
{
if (data.Security.BidPrice < data.VWAP)
{
return true;
}
}
else
{
if (data.Security.AskPrice > data.VWAP)
{
return true;
}
}
return false;
}
/// <summary>
/// Symbol data for this Execution Model
/// </summary>
protected class SymbolData
{
/// <summary>
/// Security
/// </summary>
public Security Security { get; }
/// <summary>
/// VWAP Indicator
/// </summary>
public IntradayVwap VWAP { get; }
/// <summary>
/// Data Consolidator
/// </summary>
public IDataConsolidator Consolidator { get; }
/// <summary>
/// Initialize a new instance of <see cref="SymbolData"/>
/// </summary>
public SymbolData(QCAlgorithm algorithm, Security security)
{
Security = security;
Consolidator = algorithm.ResolveConsolidator(security.Symbol, security.Resolution);
var name = algorithm.CreateIndicatorName(security.Symbol, "VWAP", security.Resolution);
VWAP = new IntradayVwap(name);
algorithm.RegisterIndicator(security.Symbol, VWAP, Consolidator, bd => (BaseData)bd);
}
}
}
}
@@ -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 *
class VolumeWeightedAveragePriceExecutionModel(ExecutionModel):
'''Execution model that submits orders while the current market price is more favorable that the current volume weighted average price.'''
def __init__(self, asynchronous=True):
'''Initializes a new instance of the VolumeWeightedAveragePriceExecutionModel class'''
super().__init__(asynchronous)
self.targets_collection = PortfolioTargetCollection()
self.symbol_data = {}
# Gets or sets the maximum order quantity as a percentage of the current bar's volume.
# This defaults to 0.01m = 1%. For example, if the current bar's volume is 100,
# then the maximum order size would equal 1 share.
self.maximum_order_quantity_percent_volume = 0.01
def execute(self, algorithm, targets):
'''Executes market orders if the standard deviation of price is more
than the configured number of deviations in the favorable direction.
Args:
algorithm: The algorithm instance
targets: The portfolio targets'''
# update the complete set of portfolio targets with the new targets
self.targets_collection.add_range(targets)
# for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if not self.targets_collection.is_empty:
for target in self.targets_collection.order_by_margin_impact(algorithm):
symbol = target.symbol
# calculate remaining quantity to be ordered
unordered_quantity = OrderSizing.get_unordered_quantity(algorithm, target)
# fetch our symbol data containing our VWAP indicator
data = self.symbol_data.get(symbol, None)
if data is None: return
# check order entry conditions
if self.price_is_favorable(data, unordered_quantity):
# adjust order size to respect maximum order size based on a percentage of current volume
order_size = OrderSizing.get_order_size_for_percent_volume(data.security, self.maximum_order_quantity_percent_volume, unordered_quantity)
if order_size != 0:
algorithm.market_order(symbol, order_size, self.asynchronous, target.tag)
self.targets_collection.clear_fulfilled(algorithm)
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 removed in changes.removed_securities:
# clean up removed security data
if removed.symbol in self.symbol_data:
if self.is_safe_to_remove(algorithm, removed.symbol):
data = self.symbol_data.pop(removed.symbol)
algorithm.subscription_manager.remove_consolidator(removed.symbol, data.consolidator)
for added in changes.added_securities:
if added.symbol not in self.symbol_data:
self.symbol_data[added.symbol] = SymbolData(algorithm, added)
def price_is_favorable(self, data, unordered_quantity):
'''Determines if the current price is more than the configured
number of standard deviations away from the mean in the favorable direction.'''
if unordered_quantity > 0:
if data.security.bid_price < data.vwap:
return True
else:
if data.security.ask_price > data.vwap:
return True
return False
def is_safe_to_remove(self, algorithm, symbol):
'''Determines if it's safe to remove the associated symbol data'''
# confirm the security isn't currently a member of any universe
return not any([kvp.value.contains_member(symbol) for kvp in algorithm.universe_manager])
class SymbolData:
def __init__(self, algorithm, security):
self.security = security
self.consolidator = algorithm.resolve_consolidator(security.symbol, security.resolution)
name = algorithm.create_indicator_name(security.symbol, "VWAP", security.resolution)
self._vwap = IntradayVwap(name)
algorithm.register_indicator(security.symbol, self._vwap, self.consolidator)
@property
def vwap(self):
return self._vwap.value
class IntradayVwap:
'''Defines the canonical intraday VWAP indicator'''
def __init__(self, name):
self.name = name
self.value = 0.0
self.last_date = datetime.min
self.sum_of_volume = 0.0
self.sum_of_price_times_volume = 0.0
@property
def is_ready(self):
return self.sum_of_volume > 0.0
def update(self, input):
'''Computes the new VWAP'''
success, volume, average_price = self.get_volume_and_average_price(input)
if not success:
return self.is_ready
# reset vwap on daily boundaries
if self.last_date != input.end_time.date():
self.sum_of_volume = 0.0
self.sum_of_price_times_volume = 0.0
self.last_date = input.end_time.date()
# running totals for Σ PiVi / Σ Vi
self.sum_of_volume += volume
self.sum_of_price_times_volume += average_price * volume
if self.sum_of_volume == 0.0:
# if we have no trade volume then use the current price as VWAP
self.value = input.value
return self.is_ready
self.value = self.sum_of_price_times_volume / self.sum_of_volume
return self.is_ready
def get_volume_and_average_price(self, input):
'''Determines the volume and price to be used for the current input in the VWAP computation'''
if type(input) is Tick:
if input.tick_type == TickType.TRADE:
return True, float(input.quantity), float(input.last_price)
if type(input) is TradeBar:
if not input.is_fill_forward:
average_price = float(input.high + input.low + input.close) / 3
return True, float(input.volume), average_price
return False, 0.0, 0.0
@@ -0,0 +1,128 @@
/*
* 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 QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Algorithm.Framework
{
/// <summary>
/// Provides convenience methods for updating collections in responses to securities changed events
/// </summary>
public static class NotifiedSecurityChanges
{
/// <summary>
/// Adds and removes the security changes to/from the collection
/// </summary>
/// <param name="securities">The securities collection to be updated with the changes</param>
/// <param name="changes">The changes to be applied to the securities collection</param>
public static void UpdateCollection(ICollection<Security> securities, SecurityChanges changes)
{
Update(changes, securities.Add, removed => securities.Remove(removed));
}
/// <summary>
/// Adds and removes the security changes to/from the collection
/// </summary>
/// <param name="securities">The securities collection to be updated with the changes</param>
/// <param name="changes">The changes to be applied to the securities collection</param>
/// <param name="valueFactory">Delegate used to create instances of <typeparamref name="TValue"/> from a <see cref="Security"/> object</param>
public static void UpdateCollection<TValue>(ICollection<TValue> securities, SecurityChanges changes, Func<Security, TValue> valueFactory)
{
Update(changes, added => securities.Add(valueFactory(added)), removed => securities.Remove(valueFactory(removed)));
}
/// <summary>
/// Adds and removes the security changes to/from the collection
/// </summary>
/// <param name="dictionary">The securities collection to be updated with the changes</param>
/// <param name="changes">The changes to be applied to the securities collection</param>
/// <param name="valueFactory">Factory for creating dictonary values for a key</param>
public static void UpdateDictionary<TValue>(
IDictionary<Security, TValue> dictionary,
SecurityChanges changes,
Func<Security, TValue> valueFactory
)
{
UpdateDictionary(dictionary, changes, security => security, valueFactory);
}
/// <summary>
/// Adds and removes the security changes to/from the collection
/// </summary>
/// <param name="dictionary">The securities collection to be updated with the changes</param>
/// <param name="changes">The changes to be applied to the securities collection</param>
/// <param name="valueFactory">Factory for creating dictonary values for a key</param>
public static void UpdateDictionary<TValue>(
IDictionary<Symbol, TValue> dictionary,
SecurityChanges changes,
Func<Security, TValue> valueFactory
)
{
UpdateDictionary(dictionary, changes, security => security.Symbol, valueFactory);
}
/// <summary>
/// Most generic form of <see cref="UpdateCollection"/>
/// </summary>
/// <typeparam name="TKey">The dictionary's key type</typeparam>
/// <typeparam name="TValue">The dictionary's value type</typeparam>
/// <param name="dictionary">The dictionary to update</param>
/// <param name="changes">The <seealso cref="SecurityChanges"/> to apply to the dictionary</param>
/// <param name="keyFactory">Selector pulling <typeparamref name="TKey"/> from a <seealso cref="Security"/></param>
/// <param name="valueFactory">Selector pulling <typeparamref name="TValue"/> from a <seealso cref="Security"/></param>
public static void UpdateDictionary<TKey, TValue>(
IDictionary<TKey, TValue> dictionary,
SecurityChanges changes,
Func<Security, TKey> keyFactory,
Func<Security, TValue> valueFactory
)
{
Update(changes,
added => dictionary.Add(keyFactory(added), valueFactory(added)),
removed =>
{
var key = keyFactory(removed);
var entry = dictionary[key];
dictionary.Remove(key);
// give the entry a chance to clean up after itself
var disposable = entry as IDisposable;
disposable.DisposeSafely();
});
}
/// <summary>
/// Invokes the provided <paramref name="add"/> and <paramref name="remove"/> functions for each
/// </summary>
/// <param name="changes">The security changes to process</param>
/// <param name="add">Function called for each added security</param>
/// <param name="remove">Function called for each removed security</param>
public static void Update(SecurityChanges changes, Action<Security> add, Action<Security> remove)
{
foreach (var added in changes.AddedSecurities)
{
add(added);
}
foreach (var removed in changes.RemovedSecurities)
{
remove(removed);
}
}
}
}
@@ -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))
@@ -0,0 +1,17 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("QuantConnect.Algorithm.Framework")]
[assembly: AssemblyProduct("QuantConnect.Algorithm.Framework")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("75981418-7246-4b91-b136-482728e02901")]
@@ -0,0 +1,157 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<RootNamespace>QuantConnect.Algorithm.Framework</RootNamespace>
<AssemblyName>QuantConnect.Algorithm.Framework</AssemblyName>
<TargetFramework>net10.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<OutputPath>bin\$(Configuration)\</OutputPath>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<DocumentationFile>bin\$(Configuration)\QuantConnect.Algorithm.Framework.xml</DocumentationFile>
<PackageTags>Library</PackageTags>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<Description>QuantConnect LEAN Engine: Algorithm.Framework Project - The core QCAlgorithm framework implementation</Description>
<NoWarn>CA1062</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\Debug\</OutputPath>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="QuantConnect.pythonnet" Version="2.0.60" />
<PackageReference Include="Accord" Version="3.6.0" />
<PackageReference Include="Accord.Math" Version="3.6.0" />
<PackageReference Include="Accord.Statistics" Version="3.6.0" />
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
<PackageReference Include="NodaTime" Version="3.0.5" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Algorithm\QuantConnect.Algorithm.csproj" />
<ProjectReference Include="..\Common\QuantConnect.csproj" />
<ProjectReference Include="..\Indicators\QuantConnect.Indicators.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="Portfolio\BlackLittermanOptimizationPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\ConfidenceWeightedPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\AccumulativeInsightPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\RiskParityPortfolioOptimizer.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\SectorWeightingPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\InsightWeightingPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\UnconstrainedMeanVariancePortfolioOptimizer.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\MaximumSharpeRatioPortfolioOptimizer.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\MeanReversionPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\MeanVarianceOptimizationPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\MinimumVariancePortfolioOptimizer.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\RiskParityPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Alphas\PearsonCorrelationPairsTradingAlphaModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Alphas\HistoricalReturnsAlphaModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Alphas\BasePairsTradingAlphaModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Execution\VolumeWeightedAveragePriceExecutionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Execution\StandardDeviationExecutionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Execution\SpreadExecutionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\EqualWeightingPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Risk\TrailingStopRiskManagementModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Risk\MaximumDrawdownPercentPortfolio.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Risk\MaximumUnrealizedProfitPercentPerSecurity.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Risk\MaximumDrawdownPercentPerSecurity.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Risk\MaximumSectorExposureRiskManagementModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Selection\ETFConstituentsUniverseSelectionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Selection\EmaCrossUniverseSelectionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Selection\FutureUniverseSelectionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Selection\OptionUniverseSelectionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Selection\QC500UniverseSelectionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Selection\FundamentalUniverseSelectionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Alphas\ConstantAlphaModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Alphas\RsiAlphaModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Alphas\EmaCrossAlphaModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Alphas\MacdAlphaModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,73 @@
/*
* 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 QuantConnect.Algorithm.Framework.Portfolio;
namespace QuantConnect.Algorithm.Framework.Risk
{
/// <summary>
/// Provides an implementation of <see cref="IRiskManagementModel"/> that limits the drawdown
/// per holding to the specified percentage
/// </summary>
public class MaximumDrawdownPercentPerSecurity : RiskManagementModel
{
private readonly decimal _maximumDrawdownPercent;
/// <summary>
/// Initializes a new instance of the <see cref="MaximumDrawdownPercentPerSecurity"/> class
/// </summary>
/// <param name="maximumDrawdownPercent">The maximum percentage drawdown allowed for any single security holding,
/// defaults to 5% drawdown per security</param>
public MaximumDrawdownPercentPerSecurity(
decimal maximumDrawdownPercent = 0.05m
)
{
_maximumDrawdownPercent = -Math.Abs(maximumDrawdownPercent);
}
/// <summary>
/// Manages the algorithm's risk at each time step
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="targets">The current portfolio targets to be assessed for risk</param>
public override IEnumerable<IPortfolioTarget> ManageRisk(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
foreach (var kvp in algorithm.Securities)
{
var security = kvp.Value;
if (!security.Invested)
{
continue;
}
var pnl = security.Holdings.UnrealizedProfitPercent;
if (pnl < _maximumDrawdownPercent)
{
var symbol = security.Symbol;
// Cancel insights
algorithm.Insights.Cancel(new[] { symbol });
// liquidate
yield return new PortfolioTarget(symbol, 0);
}
}
}
}
}
@@ -0,0 +1,47 @@
# 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 MaximumDrawdownPercentPerSecurity(RiskManagementModel):
'''Provides an implementation of IRiskManagementModel that limits the drawdown per holding to the specified percentage'''
def __init__(self, maximum_drawdown_percent = 0.05):
'''Initializes a new instance of the MaximumDrawdownPercentPerSecurity class
Args:
maximum_drawdown_percent: The maximum percentage drawdown allowed for any single security holding'''
self.maximum_drawdown_percent = -abs(maximum_drawdown_percent)
def manage_risk(self, algorithm, targets):
'''Manages the algorithm's risk at each time step
Args:
algorithm: The algorithm instance
targets: The current portfolio targets to be assessed for risk'''
targets = []
for kvp in algorithm.securities:
security = kvp.value
if not security.invested:
continue
pnl = security.holdings.unrealized_profit_percent
if pnl < self.maximum_drawdown_percent:
symbol = security.symbol
# Cancel insights
algorithm.insights.cancel([symbol])
# liquidate
targets.append(PortfolioTarget(symbol, 0))
return targets
@@ -0,0 +1,92 @@
/*
* 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 QuantConnect.Algorithm.Framework.Portfolio;
namespace QuantConnect.Algorithm.Framework.Risk
{
/// <summary>
/// Provides an implementation of <see cref="IRiskManagementModel"/> that limits the drawdown of the portfolio
/// to the specified percentage. Once this is triggered the algorithm will need to be manually restarted.
/// </summary>
public class MaximumDrawdownPercentPortfolio : RiskManagementModel
{
private readonly decimal _maximumDrawdownPercent;
private decimal _portfolioHigh;
private bool _initialised = false;
private bool _isTrailing;
/// <summary>
/// Initializes a new instance of the <see cref="MaximumDrawdownPercentPortfolio"/> class
/// </summary>
/// <param name="maximumDrawdownPercent">The maximum percentage drawdown allowed for algorithm portfolio
/// compared with starting value, defaults to 5% drawdown</param>
/// <param name="isTrailing">If "false", the drawdown will be relative to the starting value of the portfolio.
/// If "true", the drawdown will be relative the last maximum portfolio value</param>
public MaximumDrawdownPercentPortfolio(decimal maximumDrawdownPercent = 0.05m, bool isTrailing = false)
{
_maximumDrawdownPercent = -Math.Abs(maximumDrawdownPercent);
_isTrailing = isTrailing;
}
/// <summary>
/// Manages the algorithm's risk at each time step
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="targets">The current portfolio targets to be assessed for risk</param>
public override IEnumerable<IPortfolioTarget> ManageRisk(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
var currentValue = algorithm.Portfolio.TotalPortfolioValue;
if (!_initialised)
{
_portfolioHigh = currentValue; // Set initial portfolio value
_initialised = true;
}
// Update trailing high value if in trailing mode
if (_isTrailing && (_portfolioHigh < currentValue))
{
_portfolioHigh = currentValue;
yield break; // return if new high reached
}
var pnl = GetTotalDrawdownPercent(currentValue);
if (pnl < _maximumDrawdownPercent && targets.Length != 0)
{
// reset the trailing high value for restart investing on next rebalcing period
_initialised = false;
foreach (var target in targets)
{
var symbol = target.Symbol;
// Cancel insights
algorithm.Insights.Cancel(new[] { symbol });
// liquidate
yield return new PortfolioTarget(symbol, 0);
}
}
}
private decimal GetTotalDrawdownPercent(decimal currentValue)
{
return (currentValue / _portfolioHigh) - 1.0m;
}
}
}
@@ -0,0 +1,64 @@
# 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 MaximumDrawdownPercentPortfolio(RiskManagementModel):
'''Provides an implementation of IRiskManagementModel that limits the drawdown of the portfolio to the specified percentage.'''
def __init__(self, maximum_drawdown_percent = 0.05, is_trailing = False):
'''Initializes a new instance of the MaximumDrawdownPercentPortfolio class
Args:
maximum_drawdown_percent: The maximum percentage drawdown allowed for algorithm portfolio compared with starting value, defaults to 5% drawdown</param>
is_trailing: If "false", the drawdown will be relative to the starting value of the portfolio.
If "true", the drawdown will be relative the last maximum portfolio value'''
self.maximum_drawdown_percent = -abs(maximum_drawdown_percent)
self.is_trailing = is_trailing
self.initialised = False
self.portfolio_high = 0
def manage_risk(self, algorithm, targets):
'''Manages the algorithm's risk at each time step
Args:
algorithm: The algorithm instance
targets: The current portfolio targets to be assessed for risk'''
current_value = algorithm.portfolio.total_portfolio_value
if not self.initialised:
self.portfolio_high = current_value # Set initial portfolio value
self.initialised = True
# Update trailing high value if in trailing mode
if self.is_trailing and self.portfolio_high < current_value:
self.portfolio_high = current_value
return [] # return if new high reached
pnl = self.get_total_drawdown_percent(current_value)
if pnl < self.maximum_drawdown_percent and len(targets) != 0:
self.initialised = False # reset the trailing high value for restart investing on next rebalcing period
risk_adjusted_targets = []
for target in targets:
symbol = target.symbol
# Cancel insights
algorithm.insights.cancel([symbol])
# liquidate
risk_adjusted_targets.append(PortfolioTarget(symbol, 0))
return risk_adjusted_targets
return []
def get_total_drawdown_percent(self, current_value):
return (float(current_value) / float(self.portfolio_high)) - 1.0
@@ -0,0 +1,131 @@
/*
* 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.Algorithm.Framework.Portfolio;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Algorithm.Framework.Risk
{
/// <summary>
/// Provides an implementation of <see cref="IRiskManagementModel"/> that limits
/// the sector exposure to the specified percentage
/// </summary>
public class MaximumSectorExposureRiskManagementModel : RiskManagementModel
{
private readonly decimal _maximumSectorExposure;
private readonly PortfolioTargetCollection _targetsCollection;
/// <summary>
/// Initializes a new instance of the <see cref="MaximumSectorExposureRiskManagementModel"/> class
/// </summary>
/// <param name="maximumSectorExposure">The maximum exposure for any sector, defaults to 20% sector exposure.</param>
public MaximumSectorExposureRiskManagementModel(
decimal maximumSectorExposure = 0.20m
)
{
if (maximumSectorExposure <= 0)
{
throw new ArgumentOutOfRangeException("MaximumSectorExposureRiskManagementModel: the maximum sector exposure cannot be a non-positive value.");
}
_maximumSectorExposure = maximumSectorExposure;
_targetsCollection = new PortfolioTargetCollection();
}
/// <summary>
/// Manages the algorithm's risk at each time step
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="targets">The current portfolio targets to be assessed for risk</param>
public override IEnumerable<IPortfolioTarget> ManageRisk(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
var maximumSectorExposureValue = algorithm.Portfolio.TotalPortfolioValue * _maximumSectorExposure;
_targetsCollection.AddRange(targets);
// Group the securities by their sector
var groupBySector = algorithm.UniverseManager.ActiveSecurities
.Where(x => x.Value.Fundamentals != null && x.Value.Fundamentals.HasFundamentalData)
.GroupBy(x => x.Value.Fundamentals.CompanyReference.IndustryTemplateCode);
foreach (var securities in groupBySector)
{
// Compute the sector absolute holdings value
// If the construction model has created a target, we consider that
// value to calculate the security absolute holding value
var sectorAbsoluteHoldingsValue = 0m;
foreach (var security in securities)
{
var absoluteHoldingsValue = security.Value.Holdings.AbsoluteHoldingsValue;
IPortfolioTarget target;
if (_targetsCollection.TryGetValue(security.Value.Symbol, out target))
{
absoluteHoldingsValue = security.Value.Price * Math.Abs(target.Quantity) *
security.Value.SymbolProperties.ContractMultiplier *
security.Value.QuoteCurrency.ConversionRate;
}
sectorAbsoluteHoldingsValue += absoluteHoldingsValue;
}
// If the ratio between the sector absolute holdings value and the maximum sector exposure value
// exceeds the unity, it means we need to reduce each security of that sector by that ratio
// Otherwise, it means that the sector exposure is below the maximum and there is nothing to do.
var ratio = sectorAbsoluteHoldingsValue / maximumSectorExposureValue;
if (ratio > 1)
{
foreach (var security in securities)
{
var quantity = security.Value.Holdings.Quantity;
var symbol = security.Value.Symbol;
IPortfolioTarget target;
if (_targetsCollection.TryGetValue(symbol, out target))
{
quantity = target.Quantity;
}
if (quantity != 0)
{
yield return new PortfolioTarget(symbol, quantity / ratio);
}
}
}
}
}
/// <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)
{
var anyFundamentalData = algorithm.ActiveSecurities
.Any(kvp => kvp.Value.Fundamentals != null && kvp.Value.Fundamentals.HasFundamentalData);
if (!anyFundamentalData)
{
throw new Exception("MaximumSectorExposureRiskManagementModel.OnSecuritiesChanged: Please select a portfolio selection model that selects securities with fundamental data.");
}
}
}
}
@@ -0,0 +1,89 @@
# 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 itertools import groupby
class MaximumSectorExposureRiskManagementModel(RiskManagementModel):
'''Provides an implementation of IRiskManagementModel that that limits the sector exposure to the specified percentage'''
def __init__(self, maximum_sector_exposure = 0.20):
'''Initializes a new instance of the MaximumSectorExposureRiskManagementModel class
Args:
maximum_drawdown_percent: The maximum exposure for any sector, defaults to 20% sector exposure.'''
if maximum_sector_exposure <= 0:
raise ValueError('MaximumSectorExposureRiskManagementModel: the maximum sector exposure cannot be a non-positive value.')
self.maximum_sector_exposure = maximum_sector_exposure
self.targets_collection = PortfolioTargetCollection()
def manage_risk(self, algorithm, targets):
'''Manages the algorithm's risk at each time step
Args:
algorithm: The algorithm instance'''
maximum_sector_exposure_value = float(algorithm.portfolio.total_portfolio_value) * self.maximum_sector_exposure
self.targets_collection.add_range(targets)
risk_targets = list()
# Group the securities by their sector
filtered = list(filter(lambda x: x.value.fundamentals is not None and x.value.fundamentals.has_fundamental_data, algorithm.universe_manager.active_securities))
filtered.sort(key = lambda x: x.value.fundamentals.company_reference.industry_template_code)
group_by_sector = groupby(filtered, lambda x: x.value.fundamentals.company_reference.industry_template_code)
for code, securities in group_by_sector:
# Compute the sector absolute holdings value
# If the construction model has created a target, we consider that
# value to calculate the security absolute holding value
quantities = {}
sector_absolute_holdings_value = 0
for security in securities:
symbol = security.value.symbol
quantities[symbol] = security.value.holdings.quantity
absolute_holdings_value = security.value.holdings.absolute_holdings_value
if self.targets_collection.contains_key(symbol):
quantities[symbol] = self.targets_collection[symbol].quantity
absolute_holdings_value = (security.value.price * abs(quantities[symbol]) *
security.value.symbol_properties.contract_multiplier *
security.value.quote_currency.conversion_rate)
sector_absolute_holdings_value += absolute_holdings_value
# If the ratio between the sector absolute holdings value and the maximum sector exposure value
# exceeds the unity, it means we need to reduce each security of that sector by that ratio
# Otherwise, it means that the sector exposure is below the maximum and there is nothing to do.
ratio = float(sector_absolute_holdings_value) / maximum_sector_exposure_value
if ratio > 1:
for symbol, quantity in quantities.items():
if quantity != 0:
risk_targets.append(PortfolioTarget(symbol, float(quantity) / ratio))
return risk_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'''
any_fundamental_data = any([
kvp.value.fundamentals is not None and
kvp.value.fundamentals.has_fundamental_data for kvp in algorithm.active_securities
])
if not any_fundamental_data:
raise Exception("MaximumSectorExposureRiskManagementModel.on_securities_changed: Please select a portfolio selection model that selects securities with fundamental data.")
@@ -0,0 +1,73 @@
/*
* 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 QuantConnect.Algorithm.Framework.Portfolio;
namespace QuantConnect.Algorithm.Framework.Risk
{
/// <summary>
/// Provides an implementation of <see cref="IRiskManagementModel"/> that limits the unrealized profit
/// per holding to the specified percentage
/// </summary>
public class MaximumUnrealizedProfitPercentPerSecurity : RiskManagementModel
{
private readonly decimal _maximumUnrealizedProfitPercent;
/// <summary>
/// Initializes a new instance of the <see cref="MaximumUnrealizedProfitPercentPerSecurity"/> class
/// </summary>
/// <param name="maximumUnrealizedProfitPercent">The maximum percentage unrealized profit allowed for any single security holding,
/// defaults to 5% drawdown per security</param>
public MaximumUnrealizedProfitPercentPerSecurity(
decimal maximumUnrealizedProfitPercent = 0.05m
)
{
_maximumUnrealizedProfitPercent = Math.Abs(maximumUnrealizedProfitPercent);
}
/// <summary>
/// Manages the algorithm's risk at each time step
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="targets">The current portfolio targets to be assessed for risk</param>
public override IEnumerable<IPortfolioTarget> ManageRisk(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
foreach (var kvp in algorithm.Securities)
{
var security = kvp.Value;
if (!security.Invested)
{
continue;
}
var pnl = security.Holdings.UnrealizedProfitPercent;
if (pnl > _maximumUnrealizedProfitPercent)
{
var symbol = security.Symbol;
// Cancel insights
algorithm.Insights.Cancel(new[] { symbol });
// liquidate
yield return new PortfolioTarget(symbol, 0);
}
}
}
}
}
@@ -0,0 +1,47 @@
# 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 MaximumUnrealizedProfitPercentPerSecurity(RiskManagementModel):
'''Provides an implementation of IRiskManagementModel that limits the unrealized profit per holding to the specified percentage'''
def __init__(self, maximum_unrealized_profit_percent = 0.05):
'''Initializes a new instance of the MaximumUnrealizedProfitPercentPerSecurity class
Args:
maximum_unrealized_profit_percent: The maximum percentage unrealized profit allowed for any single security holding, defaults to 5% drawdown per security'''
self.maximum_unrealized_profit_percent = abs(maximum_unrealized_profit_percent)
def manage_risk(self, algorithm, targets):
'''Manages the algorithm's risk at each time step
Args:
algorithm: The algorithm instance
targets: The current portfolio targets to be assessed for risk'''
targets = []
for kvp in algorithm.securities:
security = kvp.value
if not security.invested:
continue
pnl = security.holdings.unrealized_profit_percent
if pnl > self.maximum_unrealized_profit_percent:
symbol = security.symbol
# Cancel insights
algorithm.insights.cancel([ symbol ]);
# liquidate
targets.append(PortfolioTarget(symbol, 0))
return targets
@@ -0,0 +1,111 @@
/*
* 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 QuantConnect.Algorithm.Framework.Portfolio;
namespace QuantConnect.Algorithm.Framework.Risk
{
/// <summary>
/// Provides an implementation of <see cref="IRiskManagementModel"/> that limits the maximum possible loss
/// measured from the highest unrealized profit
/// </summary>
public class TrailingStopRiskManagementModel : RiskManagementModel
{
private readonly decimal _maximumDrawdownPercent;
private readonly Dictionary<Symbol, HoldingsState> _trailingAbsoluteHoldingsState = new Dictionary<Symbol, HoldingsState>();
/// <summary>
/// Initializes a new instance of the <see cref="TrailingStopRiskManagementModel"/> class
/// </summary>
/// <param name="maximumDrawdownPercent">The maximum percentage relative drawdown allowed for algorithm portfolio compared with the highest unrealized profit, defaults to 5% drawdown per security</param>
public TrailingStopRiskManagementModel(decimal maximumDrawdownPercent = 0.05m)
{
_maximumDrawdownPercent = Math.Abs(maximumDrawdownPercent);
}
/// <summary>
/// Manages the algorithm's risk at each time step
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="targets">The current portfolio targets to be assessed for risk</param>
public override IEnumerable<IPortfolioTarget> ManageRisk(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
foreach (var kvp in algorithm.Securities)
{
var symbol = kvp.Key;
var security = kvp.Value;
// Remove if not invested
if (!security.Invested)
{
_trailingAbsoluteHoldingsState.Remove(symbol);
continue;
}
var position = security.Holdings.IsLong ? PositionSide.Long : PositionSide.Short;
var absoluteHoldingsValue = security.Holdings.AbsoluteHoldingsValue;
HoldingsState trailingAbsoluteHoldingsState;
// Add newly invested security (if doesn't exist) or reset holdings state (if position changed)
if (!_trailingAbsoluteHoldingsState.TryGetValue(symbol, out trailingAbsoluteHoldingsState) ||
position != trailingAbsoluteHoldingsState.Position)
{
_trailingAbsoluteHoldingsState[symbol] = trailingAbsoluteHoldingsState = new HoldingsState(position, security.Holdings.AbsoluteHoldingsCost);
}
var trailingAbsoluteHoldingsValue = trailingAbsoluteHoldingsState.AbsoluteHoldingsValue;
// Check for new max (for long position) or min (for short position) absolute holdings value
if ((position == PositionSide.Long && trailingAbsoluteHoldingsValue < absoluteHoldingsValue) ||
(position == PositionSide.Short && trailingAbsoluteHoldingsValue > absoluteHoldingsValue))
{
trailingAbsoluteHoldingsState.AbsoluteHoldingsValue = absoluteHoldingsValue;
continue;
}
var drawdown = Math.Abs((trailingAbsoluteHoldingsValue - absoluteHoldingsValue) / trailingAbsoluteHoldingsValue);
if (_maximumDrawdownPercent < drawdown)
{
// Cancel insights
algorithm.Insights.Cancel(new[] { symbol });
_trailingAbsoluteHoldingsState.Remove(symbol);
// liquidate
yield return new PortfolioTarget(symbol, 0);
}
}
}
/// <summary>
/// Helper class used to store holdings state for the <see cref="TrailingStopRiskManagementModel"/>
/// in <see cref="ManageRisk"/>
/// </summary>
private class HoldingsState
{
public PositionSide Position;
public decimal AbsoluteHoldingsValue;
public HoldingsState(PositionSide position, decimal absoluteHoldingsValue)
{
Position = position;
AbsoluteHoldingsValue = absoluteHoldingsValue;
}
}
}
}
@@ -0,0 +1,73 @@
# 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 TrailingStopRiskManagementModel(RiskManagementModel):
'''Provides an implementation of IRiskManagementModel that limits the maximum possible loss
measured from the highest unrealized profit'''
def __init__(self, maximum_drawdown_percent = 0.05):
'''Initializes a new instance of the TrailingStopRiskManagementModel class
Args:
maximum_drawdown_percent: The maximum percentage drawdown allowed for algorithm portfolio compared with the highest unrealized profit, defaults to 5% drawdown'''
self.maximum_drawdown_percent = abs(maximum_drawdown_percent)
self.trailing_absolute_holdings_state = dict()
def manage_risk(self, algorithm, targets):
'''Manages the algorithm's risk at each time step
Args:
algorithm: The algorithm instance
targets: The current portfolio targets to be assessed for risk'''
risk_adjusted_targets = list()
for kvp in algorithm.securities:
symbol = kvp.key
security = kvp.value
# Remove if not invested
if not security.invested:
self.trailing_absolute_holdings_state.pop(symbol, None)
continue
position = PositionSide.LONG if security.holdings.is_long else PositionSide.SHORT
absolute_holdings_value = security.holdings.absolute_holdings_value
trailing_absolute_holdings_state = self.trailing_absolute_holdings_state.get(symbol)
# Add newly invested security (if doesn't exist) or reset holdings state (if position changed)
if trailing_absolute_holdings_state == None or position != trailing_absolute_holdings_state.position:
self.trailing_absolute_holdings_state[symbol] = trailing_absolute_holdings_state = self.HoldingsState(position, security.holdings.absolute_holdings_cost)
trailing_absolute_holdings_value = trailing_absolute_holdings_state.absolute_holdings_value
# Check for new max (for long position) or min (for short position) absolute holdings value
if ((position == PositionSide.LONG and trailing_absolute_holdings_value < absolute_holdings_value) or
(position == PositionSide.SHORT and trailing_absolute_holdings_value > absolute_holdings_value)):
self.trailing_absolute_holdings_state[symbol].absolute_holdings_value = absolute_holdings_value
continue
drawdown = abs((trailing_absolute_holdings_value - absolute_holdings_value) / trailing_absolute_holdings_value)
if self.maximum_drawdown_percent < drawdown:
# Cancel insights
algorithm.insights.cancel([ symbol ]);
self.trailing_absolute_holdings_state.pop(symbol, None)
# liquidate
risk_adjusted_targets.append(PortfolioTarget(symbol, 0))
return risk_adjusted_targets
class HoldingsState:
def __init__(self, position, absolute_holdings_value):
self.position = position
self.absolute_holdings_value = absolute_holdings_value
@@ -0,0 +1,71 @@
/*
* 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 Python.Runtime;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Portfolio selection model that uses coarse selectors. For US equities only.
/// </summary>
public class CoarseFundamentalUniverseSelectionModel : FundamentalUniverseSelectionModel
{
private readonly Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> _coarseSelector;
/// <summary>
/// Initializes a new instance of the <see cref="CoarseFundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="coarseSelector">Selects symbols from the provided coarse data set</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public CoarseFundamentalUniverseSelectionModel(
Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> coarseSelector,
UniverseSettings universeSettings = null
)
: base(false, universeSettings)
{
_coarseSelector = coarseSelector;
}
/// <summary>
/// Initializes a new instance of the <see cref="CoarseFundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="coarseSelector">Selects symbols from the provided coarse data set</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public CoarseFundamentalUniverseSelectionModel(
PyObject coarseSelector,
UniverseSettings universeSettings = null
)
: base(false, universeSettings)
{
if (coarseSelector.TrySafeAs<Func<IEnumerable<CoarseFundamental>, object>>(out var func))
{
_coarseSelector = func.ConvertToUniverseSelectionSymbolDelegate();
}
}
/// <inheritdoc />
public override IEnumerable<Symbol> SelectCoarse(QCAlgorithm algorithm, IEnumerable<CoarseFundamental> coarse)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(SelectCoarse), out IEnumerable<Symbol> result, algorithm, coarse))
{
return result;
}
return _coarseSelector(coarse);
}
}
}
@@ -0,0 +1,128 @@
/*
* 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 System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Universe selection model that selects the constituents of an ETF.
/// </summary>
public class ETFConstituentsUniverseSelectionModel : UniverseSelectionModel
{
private readonly Symbol _etfSymbol;
private readonly UniverseSettings _universeSettings;
private readonly Func<IEnumerable<ETFConstituentUniverse>, IEnumerable<Symbol>> _universeFilterFunc;
private Universe _universe;
/// <summary>
/// Initializes a new instance of the <see cref="ETFConstituentsUniverseSelectionModel"/> class
/// </summary>
/// <param name="etfSymbol">Symbol of the ETF to get constituents for</param>
/// <param name="universeSettings">Universe settings</param>
/// <param name="universeFilterFunc">Function to filter universe results</param>
public ETFConstituentsUniverseSelectionModel(
Symbol etfSymbol,
UniverseSettings universeSettings,
Func<IEnumerable<ETFConstituentUniverse>, IEnumerable<Symbol>> universeFilterFunc)
{
_etfSymbol = etfSymbol;
_universeSettings = universeSettings;
_universeFilterFunc = universeFilterFunc;
}
/// <summary>
/// Initializes a new instance of the <see cref="ETFConstituentsUniverseSelectionModel"/> class
/// </summary>
/// <param name="etfSymbol">Symbol of the ETF to get constituents for</param>
/// <param name="universeFilterFunc">Function to filter universe results</param>
public ETFConstituentsUniverseSelectionModel(
Symbol etfSymbol,
Func<IEnumerable<ETFConstituentUniverse>, IEnumerable<Symbol>> universeFilterFunc)
: this(etfSymbol, null, universeFilterFunc)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ETFConstituentsUniverseSelectionModel"/> class
/// </summary>
/// <param name="etfSymbol">Symbol of the ETF to get constituents for</param>
/// <param name="universeSettings">Universe settings</param>
/// <param name="universeFilterFunc">Function to filter universe results</param>
public ETFConstituentsUniverseSelectionModel(
Symbol etfSymbol,
UniverseSettings universeSettings = null,
PyObject universeFilterFunc = null) :
this(etfSymbol, universeSettings, universeFilterFunc.ConvertPythonUniverseFilterFunction<ETFConstituentUniverse>())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ETFConstituentsUniverseSelectionModel"/> class
/// </summary>
/// <param name="etfTicker">The string ETF ticker symbol</param>
/// <param name="universeSettings">Universe settings</param>
/// <param name="universeFilterFunc">Function to filter universe results</param>
public ETFConstituentsUniverseSelectionModel(
string etfTicker,
UniverseSettings universeSettings,
Func<IEnumerable<ETFConstituentUniverse>, IEnumerable<Symbol>> universeFilterFunc)
{
_etfSymbol = SymbolCache.TryGetSymbol(etfTicker, out var symbol)
&& symbol.SecurityType == SecurityType.Equity
? symbol : Symbol.Create(etfTicker, SecurityType.Equity, Market.USA);
_universeSettings = universeSettings;
_universeFilterFunc = universeFilterFunc;
}
/// <summary>
/// Initializes a new instance of the <see cref="ETFConstituentsUniverseSelectionModel"/> class
/// </summary>
/// <param name="etfTicker">The string ETF ticker symbol</param>
/// <param name="universeFilterFunc">Function to filter universe results</param>
public ETFConstituentsUniverseSelectionModel(
string etfTicker,
Func<IEnumerable<ETFConstituentUniverse>, IEnumerable<Symbol>> universeFilterFunc)
: this(etfTicker, null, universeFilterFunc)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ETFConstituentsUniverseSelectionModel"/> class
/// </summary>
/// <param name="etfTicker">The string ETF ticker symbol</param>
/// <param name="universeSettings">Universe settings</param>
/// <param name="universeFilterFunc">Function to filter universe results</param>
public ETFConstituentsUniverseSelectionModel(
string etfTicker,
UniverseSettings universeSettings = null,
PyObject universeFilterFunc = null) :
this(etfTicker, universeSettings, universeFilterFunc.ConvertPythonUniverseFilterFunction<ETFConstituentUniverse>())
{ }
/// <summary>
/// Creates a new ETF constituents universe using this class's selection function
/// </summary>
/// <param name="algorithm">The algorithm instance to create universes for</param>
/// <returns>The universe defined by this model</returns>
public override IEnumerable<Universe> CreateUniverses(QCAlgorithm algorithm)
{
_universe ??= algorithm?.Universe.ETF(_etfSymbol, _universeSettings, _universeFilterFunc);
return new[] { _universe };
}
}
}
@@ -0,0 +1,50 @@
# 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 Selection.UniverseSelectionModel import UniverseSelectionModel
class ETFConstituentsUniverseSelectionModel(UniverseSelectionModel):
'''Universe selection model that selects the constituents of an ETF.'''
def __init__(self,
etf_symbol,
universe_settings = None,
universe_filter_func = None):
'''Initializes a new instance of the ETFConstituentsUniverseSelectionModel class
Args:
etfSymbol: Symbol of the ETF to get constituents for
universeSettings: Universe settings
universeFilterFunc: Function to filter universe results'''
if type(etf_symbol) is str:
symbol = SymbolCache.try_get_symbol(etf_symbol, None)
if symbol[0] and symbol[1].security_type == SecurityType.EQUITY:
self.etf_symbol = symbol[1]
else:
self.etf_symbol = Symbol.create(etf_symbol, SecurityType.EQUITY, Market.USA)
else:
self.etf_symbol = etf_symbol
self.universe_settings = universe_settings
self.universe_filter_function = universe_filter_func
self.universe = None
def create_universes(self, algorithm: QCAlgorithm) -> list[Universe]:
'''Creates a new ETF constituents universe using this class's selection function
Args:
algorithm: The algorithm instance to create universes for
Returns:
The universe defined by this model'''
if self.universe is None:
self.universe = algorithm.universe.etf(self.etf_symbol, self.universe_settings, self.universe_filter_function)
return [self.universe]
@@ -0,0 +1,106 @@
/*
* 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.Concurrent;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Provides an implementation of <see cref="FundamentalUniverseSelectionModel"/> that subscribes
/// to symbols with the larger delta by percentage between the two exponential moving average
/// </summary>
public class EmaCrossUniverseSelectionModel : FundamentalUniverseSelectionModel
{
private const decimal _tolerance = 0.01m;
private readonly int _fastPeriod;
private readonly int _slowPeriod;
private readonly int _universeCount;
// holds our coarse fundamental indicators by symbol
private readonly ConcurrentDictionary<Symbol, SelectionData> _averages;
/// <summary>
/// Initializes a new instance of the <see cref="EmaCrossUniverseSelectionModel"/> class
/// </summary>
/// <param name="fastPeriod">Fast EMA period</param>
/// <param name="slowPeriod">Slow EMA period</param>
/// <param name="universeCount">Maximum number of members of this universe selection</param>
/// <param name="universeSettings">The settings used when adding symbols to the algorithm, specify null to use algorithm.UniverseSettings</param>
public EmaCrossUniverseSelectionModel(
int fastPeriod = 100,
int slowPeriod = 300,
int universeCount = 500,
UniverseSettings universeSettings = null)
: base(false, universeSettings)
{
_fastPeriod = fastPeriod;
_slowPeriod = slowPeriod;
_universeCount = universeCount;
_averages = new ConcurrentDictionary<Symbol, SelectionData>();
}
/// <summary>
/// Defines the coarse fundamental selection function.
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="coarse">The coarse fundamental data used to perform filtering</param>
/// <returns>An enumerable of symbols passing the filter</returns>
public override IEnumerable<Symbol> SelectCoarse(QCAlgorithm algorithm, IEnumerable<CoarseFundamental> coarse)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(SelectCoarse), out IEnumerable<Symbol> result, algorithm, coarse))
{
return result;
}
return (from cf in coarse
// grab th SelectionData instance for this symbol
let avg = _averages.GetOrAdd(cf.Symbol, sym => new SelectionData(_fastPeriod, _slowPeriod))
// Update returns true when the indicators are ready, so don't accept until they are
where avg.Update(cf.EndTime, cf.AdjustedPrice)
// only pick symbols who have their _fastPeriod-day ema over their _slowPeriod-day ema
where avg.Fast > avg.Slow * (1 + _tolerance)
// prefer symbols with a larger delta by percentage between the two averages
orderby avg.ScaledDelta descending
// we only need to return the symbol and return 'Count' symbols
select cf.Symbol).Take(_universeCount);
}
// class used to improve readability of the coarse selection function
private class SelectionData
{
public readonly ExponentialMovingAverage Fast;
public readonly ExponentialMovingAverage Slow;
public SelectionData(int fastPeriod, int slowPeriod)
{
Fast = new ExponentialMovingAverage(fastPeriod);
Slow = new ExponentialMovingAverage(slowPeriod);
}
// computes an object score of how much large the fast is than the slow
public decimal ScaledDelta => (Fast - Slow) / ((Fast + Slow) / 2m);
// updates the EMAFast and EMASlow indicators, returning true when they're both ready
public bool Update(DateTime time, decimal value) => Fast.Update(time, value) & Slow.Update(time, value);
}
}
}
@@ -0,0 +1,89 @@
# 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 Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel
class EmaCrossUniverseSelectionModel(FundamentalUniverseSelectionModel):
'''Provides an implementation of FundamentalUniverseSelectionModel that subscribes to
symbols with the larger delta by percentage between the two exponential moving average'''
def __init__(self,
fastPeriod = 100,
slowPeriod = 300,
universeCount = 500,
universeSettings = None):
'''Initializes a new instance of the EmaCrossUniverseSelectionModel class
Args:
fastPeriod: Fast EMA period
slowPeriod: Slow EMA period
universeCount: Maximum number of members of this universe selection
universeSettings: The settings used when adding symbols to the algorithm, specify null to use algorithm.UniverseSettings'''
super().__init__(False, universeSettings)
self.fast_period = fastPeriod
self.slow_period = slowPeriod
self.universe_count = universeCount
self.tolerance = 0.01
# holds our coarse fundamental indicators by symbol
self.averages = {}
def select_coarse(self, algorithm: QCAlgorithm, fundamental: list[Fundamental]) -> list[Symbol]:
'''Defines the coarse fundamental selection function.
Args:
algorithm: The algorithm instance
fundamental: The coarse fundamental data used to perform filtering</param>
Returns:
An enumerable of symbols passing the filter'''
filtered = []
for cf in fundamental:
if cf.symbol not in self.averages:
self.averages[cf.symbol] = self.SelectionData(cf.symbol, self.fast_period, self.slow_period)
# grab th SelectionData instance for this symbol
avg = self.averages.get(cf.symbol)
# Update returns true when the indicators are ready, so don't accept until they are
# and only pick symbols who have their fastPeriod-day ema over their slowPeriod-day ema
if avg.update(cf.end_time, cf.adjusted_price) and avg.fast > avg.slow * (1 + self.tolerance):
filtered.append(avg)
# prefer symbols with a larger delta by percentage between the two averages
filtered = sorted(filtered, key=lambda avg: avg.scaled_delta, reverse = True)
# we only need to return the symbol and return 'universeCount' symbols
return [x.symbol for x in filtered[:self.universe_count]]
# class used to improve readability of the coarse selection function
class SelectionData:
def __init__(self, symbol, fast_period, slow_period):
self.symbol = symbol
self.fast_ema = ExponentialMovingAverage(fast_period)
self.slow_ema = ExponentialMovingAverage(slow_period)
@property
def fast(self):
return float(self.fast_ema.current.value)
@property
def slow(self):
return float(self.slow_ema.current.value)
# computes an object score of how much large the fast is than the slow
@property
def scaled_delta(self):
return (self.fast - self.slow) / ((self.fast + self.slow) / 2)
# updates the EMAFast and EMASlow indicators, returning true when they're both ready
def update(self, time, value):
return self.slow_ema.update(time, value) & self.fast_ema.update(time, value)
@@ -0,0 +1,78 @@
/*
* 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;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Universe Selection Model that adds the following Energy ETFs at their inception date
/// 1998-12-22 XLE Energy Select Sector SPDR Fund
/// 2000-06-16 IYE iShares U.S. Energy ETF
/// 2004-09-29 VDE Vanguard Energy ETF
/// 2006-04-10 USO United States Oil Fund
/// 2006-06-22 XES SPDR S&amp;P Oil &amp; Gas Equipment &amp; Services ETF
/// 2006-06-22 XOP SPDR S&amp;P Oil &amp; Gas Exploration &amp; Production ETF
/// 2007-04-18 UNG United States Natural Gas Fund
/// 2008-06-25 ICLN iShares Global Clean Energy ETF
/// 2008-11-06 ERX Direxion Daily Energy Bull 3X Shares
/// 2008-11-06 ERY Direxion Daily Energy Bear 3x Shares
/// 2008-11-25 SCO ProShares UltraShort Bloomberg Crude Oil
/// 2008-11-25 UCO ProShares Ultra Bloomberg Crude Oil
/// 2009-06-02 AMJ JPMorgan Alerian MLP Index ETN
/// 2010-06-02 BNO United States Brent Oil Fund
/// 2010-08-25 AMLP Alerian MLP ETF
/// 2011-12-21 OIH VanEck Vectors Oil Services ETF
/// 2012-02-08 DGAZ VelocityShares 3x Inverse Natural Gas
/// 2012-02-08 UGAZ VelocityShares 3x Long Natural Gas
/// 2012-02-15 TAN Invesco Solar ETF
/// </summary>
public class EnergyETFUniverse : InceptionDateUniverseSelectionModel
{
/// <summary>
/// Initializes a new instance of the EnergyETFUniverse class
/// </summary>
public EnergyETFUniverse() :
base(
"qc-energy-etf-basket",
new Dictionary<string, DateTime>()
{
{"XLE", new DateTime(1998, 12, 22)},
{"IYE", new DateTime(2000, 6, 16)},
{"VDE", new DateTime(2004, 9, 29)},
{"USO", new DateTime(2006, 4, 10)},
{"XES", new DateTime(2006, 6, 22)},
{"XOP", new DateTime(2006, 6, 22)},
{"UNG", new DateTime(2007, 4, 18)},
{"ICLN", new DateTime(2008, 6, 25)},
{"ERX", new DateTime(2008, 11, 6)},
{"ERY", new DateTime(2008, 11, 6)},
{"SCO", new DateTime(2008, 11, 25)},
{"UCO", new DateTime(2008, 11, 25)},
{"AMJ", new DateTime(2009, 6, 2)},
{"BNO", new DateTime(2010, 6, 2)},
{"AMLP", new DateTime(2010, 8, 25)},
{"OIH", new DateTime(2011, 12, 21)},
{"DGAZ", new DateTime(2012, 2, 8)},
{"UGAZ", new DateTime(2012, 2, 8)},
{"TAN", new DateTime(2012, 2, 15)}
}
)
{
}
}
}
@@ -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.
*/
using System;
using System.Collections.Generic;
using Python.Runtime;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Portfolio selection model that uses coarse/fine selectors. For US equities only.
/// </summary>
public class FineFundamentalUniverseSelectionModel : FundamentalUniverseSelectionModel
{
private readonly Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> _coarseSelector;
private readonly Func<IEnumerable<FineFundamental>, IEnumerable<Symbol>> _fineSelector;
/// <summary>
/// Initializes a new instance of the <see cref="FineFundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="coarseSelector">Selects symbols from the provided coarse data set</param>
/// <param name="fineSelector">Selects symbols from the provided fine data set (this set has already been filtered according to the coarse selection)</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public FineFundamentalUniverseSelectionModel(
Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> coarseSelector,
Func<IEnumerable<FineFundamental>, IEnumerable<Symbol>> fineSelector,
UniverseSettings universeSettings = null)
: base(true, universeSettings)
{
_coarseSelector = coarseSelector;
_fineSelector = fineSelector;
}
/// <summary>
/// Initializes a new instance of the <see cref="FineFundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="coarseSelector">Selects symbols from the provided coarse data set</param>
/// <param name="fineSelector">Selects symbols from the provided fine data set (this set has already been filtered according to the coarse selection)</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public FineFundamentalUniverseSelectionModel(
PyObject coarseSelector,
PyObject fineSelector,
UniverseSettings universeSettings = null
)
: base(true, universeSettings)
{
Func<IEnumerable<FineFundamental>, object> fineFunc;
Func<IEnumerable<CoarseFundamental>, object> coarseFunc;
if (fineSelector.TrySafeAs(out fineFunc) &&
coarseSelector.TrySafeAs(out coarseFunc))
{
_fineSelector = fineFunc.ConvertToUniverseSelectionSymbolDelegate();
_coarseSelector = coarseFunc.ConvertToUniverseSelectionSymbolDelegate();
}
}
/// <inheritdoc />
public override IEnumerable<Symbol> SelectCoarse(QCAlgorithm algorithm, IEnumerable<CoarseFundamental> coarse)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(SelectCoarse), out IEnumerable<Symbol> result, algorithm, coarse))
{
return result;
}
return _coarseSelector(coarse);
}
/// <inheritdoc />
public override IEnumerable<Symbol> SelectFine(QCAlgorithm algorithm, IEnumerable<FineFundamental> fine)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(SelectFine), out IEnumerable<Symbol> result, algorithm, fine))
{
return result;
}
return _fineSelector(fine);
}
}
}
@@ -0,0 +1,290 @@
/*
* 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 Python.Runtime;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Provides a base class for defining equity coarse/fine fundamental selection models
/// </summary>
public class FundamentalUniverseSelectionModel : UniverseSelectionModel
{
private readonly string _market;
private readonly bool _fundamentalData;
private readonly bool _filterFineData;
private readonly UniverseSettings _universeSettings;
private readonly Func<IEnumerable<Fundamental>, IEnumerable<Symbol>> _selector;
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseSelectionModel"/> class
/// </summary>
public FundamentalUniverseSelectionModel()
: this(Market.USA, null)
{
_fundamentalData = true;
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="market">The target market</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public FundamentalUniverseSelectionModel(string market, UniverseSettings universeSettings)
{
_market = market;
_fundamentalData = true;
_universeSettings = universeSettings;
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public FundamentalUniverseSelectionModel(UniverseSettings universeSettings)
: this(Market.USA, universeSettings)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="market">The target market</param>
/// <param name="selector">Selects symbols from the provided fundamental data set</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public FundamentalUniverseSelectionModel(string market, Func<IEnumerable<Fundamental>, IEnumerable<Symbol>> selector, UniverseSettings universeSettings = null)
{
_market = market;
_selector = selector;
_fundamentalData = true;
_universeSettings = universeSettings;
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="selector">Selects symbols from the provided fundamental data set</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public FundamentalUniverseSelectionModel(Func<IEnumerable<Fundamental>, IEnumerable<Symbol>> selector, UniverseSettings universeSettings = null)
: this(Market.USA, selector, universeSettings)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="market">The target market</param>
/// <param name="selector">Selects symbols from the provided fundamental data set</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public FundamentalUniverseSelectionModel(string market, PyObject selector, UniverseSettings universeSettings = null) : this(universeSettings)
{
_market = market;
Func<IEnumerable<Fundamental>, object> selectorFunc;
if (selector.TrySafeAs(out selectorFunc))
{
_selector = selectorFunc.ConvertToUniverseSelectionSymbolDelegate();
}
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="selector">Selects symbols from the provided fundamental data set</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public FundamentalUniverseSelectionModel(PyObject selector, UniverseSettings universeSettings = null)
: this(Market.USA, selector, universeSettings)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="filterFineData">True to also filter using fine fundamental data, false to only filter on coarse data</param>
[Obsolete("Fine and Coarse selection are merged, please use 'FundamentalUniverseSelectionModel()'")]
protected FundamentalUniverseSelectionModel(bool filterFineData)
: this(filterFineData, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseSelectionModel"/> class
/// </summary>
/// <param name="filterFineData">True to also filter using fine fundamental data, false to only filter on coarse data</param>
/// <param name="universeSettings">The settings used when adding symbols to the algorithm, specify null to use algorithm.UniverseSettings</param>
[Obsolete("Fine and Coarse selection are merged, please use 'FundamentalUniverseSelectionModel(UniverseSettings)'")]
protected FundamentalUniverseSelectionModel(bool filterFineData, UniverseSettings universeSettings)
{
_market = Market.USA;
_filterFineData = filterFineData;
_universeSettings = universeSettings;
}
/// <summary>
/// Creates a new fundamental universe using this class's selection functions
/// </summary>
/// <param name="algorithm">The algorithm instance to create universes for</param>
/// <returns>The universe defined by this model</returns>
public override IEnumerable<Universe> CreateUniverses(QCAlgorithm algorithm)
{
if (_fundamentalData)
{
var universeSettings = _universeSettings ?? algorithm.UniverseSettings;
yield return new FundamentalUniverseFactory(_market, universeSettings, fundamental => Select(algorithm, fundamental));
}
else
{
// for backwards compatibility
var universe = CreateCoarseFundamentalUniverse(algorithm);
if (_filterFineData)
{
if (universe.UniverseSettings.Asynchronous.HasValue && universe.UniverseSettings.Asynchronous.Value)
{
throw new ArgumentException("Asynchronous universe setting is not supported for coarse & fine selections, please use the new Fundamental single pass selection");
}
#pragma warning disable CS0618 // Type or member is obsolete
universe = new FineFundamentalFilteredUniverse(universe, fine => SelectFine(algorithm, fine));
#pragma warning restore CS0618 // Type or member is obsolete
}
yield return universe;
}
}
/// <summary>
/// Creates the coarse fundamental universe object.
/// This is provided to allow more flexibility when creating coarse universe.
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <returns>The coarse fundamental universe</returns>
public virtual Universe CreateCoarseFundamentalUniverse(QCAlgorithm algorithm)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(CreateCoarseFundamentalUniverse), out Universe result, algorithm))
{
return result;
}
var universeSettings = _universeSettings ?? algorithm.UniverseSettings;
return new CoarseFundamentalUniverse(universeSettings, coarse =>
{
// if we're using fine fundamental selection than exclude symbols without fine data
if (_filterFineData)
{
coarse = coarse.Where(c => c.HasFundamentalData);
}
#pragma warning disable CS0618 // Type or member is obsolete
return SelectCoarse(algorithm, coarse);
#pragma warning restore CS0618 // Type or member is obsolete
});
}
/// <summary>
/// Defines the fundamental selection function.
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="fundamental">The fundamental data used to perform filtering</param>
/// <returns>An enumerable of symbols passing the filter</returns>
public virtual IEnumerable<Symbol> Select(QCAlgorithm algorithm, IEnumerable<Fundamental> fundamental)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(Select), out IEnumerable<Symbol> result, algorithm, fundamental))
{
return result;
}
if (_selector == null)
{
throw new NotImplementedException("If inheriting, please override the 'Select' fundamental function, else provide it as a constructor parameter");
}
return _selector(fundamental);
}
/// <summary>
/// Defines the coarse fundamental selection function.
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="coarse">The coarse fundamental data used to perform filtering</param>
/// <returns>An enumerable of symbols passing the filter</returns>
[Obsolete("Fine and Coarse selection are merged, please use 'Select(QCAlgorithm, IEnumerable<Fundamental>)'")]
public virtual IEnumerable<Symbol> SelectCoarse(QCAlgorithm algorithm, IEnumerable<CoarseFundamental> coarse)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(SelectCoarse), out IEnumerable<Symbol> result, algorithm, coarse))
{
return result;
}
throw new NotImplementedException("Please override the 'Select' fundamental function");
}
/// <summary>
/// Defines the fine fundamental selection function.
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="fine">The fine fundamental data used to perform filtering</param>
/// <returns>An enumerable of symbols passing the filter</returns>
[Obsolete("Fine and Coarse selection are merged, please use 'Select(QCAlgorithm, IEnumerable<Fundamental>)'")]
public virtual IEnumerable<Symbol> SelectFine(QCAlgorithm algorithm, IEnumerable<FineFundamental> fine)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(SelectFine), out IEnumerable<Symbol> result, algorithm, fine))
{
return result;
}
// default impl performs no filtering of fine data
return fine.Select(f => f.Symbol);
}
/// <summary>
/// Convenience method for creating a selection model that uses only coarse data
/// </summary>
/// <param name="coarseSelector">Selects symbols from the provided coarse data set</param>
/// <returns>A new universe selection model that will select US equities according to the selection function specified</returns>
[Obsolete("Fine and Coarse selection are merged, please use 'Fundamental(Func<IEnumerable<Fundamental>, IEnumerable<Symbol>>)'")]
public static IUniverseSelectionModel Coarse(Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> coarseSelector)
{
return new CoarseFundamentalUniverseSelectionModel(coarseSelector);
}
/// <summary>
/// Convenience method for creating a selection model that uses coarse and fine data
/// </summary>
/// <param name="coarseSelector">Selects symbols from the provided coarse data set</param>
/// <param name="fineSelector">Selects symbols from the provided fine data set (this set has already been filtered according to the coarse selection)</param>
/// <returns>A new universe selection model that will select US equities according to the selection functions specified</returns>
[Obsolete("Fine and Coarse selection are merged, please use 'Fundamental(Func<IEnumerable<Fundamental>, IEnumerable<Symbol>>)'")]
public static IUniverseSelectionModel Fine(Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> coarseSelector, Func<IEnumerable<FineFundamental>, IEnumerable<Symbol>> fineSelector)
{
return new FineFundamentalUniverseSelectionModel(coarseSelector, fineSelector);
}
/// <summary>
/// Convenience method for creating a selection model that uses fundamental data
/// </summary>
/// <param name="selector">Selects symbols from the provided fundamental data set</param>
/// <returns>A new universe selection model that will select US equities according to the selection functions specified</returns>
public static IUniverseSelectionModel Fundamental(Func<IEnumerable<Fundamental>, IEnumerable<Symbol>> selector)
{
return new FundamentalUniverseSelectionModel(selector);
}
}
}
@@ -0,0 +1,115 @@
# 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 FundamentalUniverseSelectionModel:
'''Provides a base class for defining equity coarse/fine fundamental selection models'''
def __init__(self,
filter_fine_data = None,
universe_settings = None):
'''Initializes a new instance of the FundamentalUniverseSelectionModel class
Args:
filter_fine_data: [Obsolete] Fine and Coarse selection are merged
universeSettings: The settings used when adding symbols to the algorithm, specify null to use algorithm.universe_settings'''
self.filter_fine_data = filter_fine_data
if self.filter_fine_data == None:
self.fundamental_data = True
else:
self.fundamental_data = False
self.market = Market.USA
self.universe_settings = universe_settings
def create_universes(self, algorithm: QCAlgorithm) -> list[Universe]:
'''Creates a new fundamental universe using this class's selection functions
Args:
algorithm: The algorithm instance to create universes for
Returns:
The universe defined by this model'''
if self.fundamental_data:
universe_settings = algorithm.universe_settings if self.universe_settings is None else self.universe_settings
# handle both 'Select' and 'select' for backwards compatibility
selection = lambda fundamental: self.select(algorithm, fundamental)
if hasattr(self, "Select") and callable(self.Select):
selection = lambda fundamental: self.Select(algorithm, fundamental)
universe = FundamentalUniverseFactory(self.market, universe_settings, selection)
return [universe]
else:
universe = self.create_coarse_fundamental_universe(algorithm)
if self.filter_fine_data:
if universe.universe_settings.asynchronous:
raise ValueError("Asynchronous universe setting is not supported for coarse & fine selections, please use the new Fundamental single pass selection")
selection = lambda fine: self.select_fine(algorithm, fine)
if hasattr(self, "SelectFine") and callable(self.SelectFine):
selection = lambda fine: self.SelectFine(algorithm, fine)
universe = FineFundamentalFilteredUniverse(universe, selection)
return [universe]
def create_coarse_fundamental_universe(self, algorithm: QCAlgorithm) -> Universe:
'''Creates the coarse fundamental universe object.
This is provided to allow more flexibility when creating coarse universe.
Args:
algorithm: The algorithm instance
Returns:
The coarse fundamental universe'''
universe_settings = algorithm.universe_settings if self.universe_settings is None else self.universe_settings
return CoarseFundamentalUniverse(universe_settings, lambda coarse: self.filtered_select_coarse(algorithm, coarse))
def filtered_select_coarse(self, algorithm: QCAlgorithm, fundamental: list[Fundamental]) -> list[Symbol]:
'''Defines the coarse fundamental selection function.
If we're using fine fundamental selection than exclude symbols without fine data
Args:
algorithm: The algorithm instance
coarse: The coarse fundamental data used to perform filtering
Returns:
An enumerable of symbols passing the filter'''
if self.filter_fine_data:
fundamental = filter(lambda c: c.has_fundamental_data, fundamental)
if hasattr(self, "SelectCoarse") and callable(self.SelectCoarse):
# handle both 'select_coarse' and 'SelectCoarse' for backwards compatibility
return self.SelectCoarse(algorithm, fundamental)
return self.select_coarse(algorithm, fundamental)
def select(self, algorithm: QCAlgorithm, fundamental: list[Fundamental]) -> list[Symbol]:
'''Defines the fundamental selection function.
Args:
algorithm: The algorithm instance
fundamental: The fundamental data used to perform filtering
Returns:
An enumerable of symbols passing the filter'''
raise NotImplementedError("Please overrride the 'select' fundamental function")
def select_coarse(self, algorithm: QCAlgorithm, fundamental: list[Fundamental]) -> list[Symbol]:
'''Defines the coarse fundamental selection function.
Args:
algorithm: The algorithm instance
coarse: The coarse fundamental data used to perform filtering
Returns:
An enumerable of symbols passing the filter'''
raise NotImplementedError("Please overrride the 'select' fundamental function")
def select_fine(self, algorithm: QCAlgorithm, fundamental: list[Fundamental]) -> list[Symbol]:
'''Defines the fine fundamental selection function.
Args:
algorithm: The algorithm instance
fine: The fine fundamental data used to perform filtering
Returns:
An enumerable of symbols passing the filter'''
return [f.symbol for f in fundamental]
@@ -0,0 +1,160 @@
/*
* 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 QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
using Python.Runtime;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Provides an implementation of <see cref="IUniverseSelectionModel"/> that subscribes to future chains
/// </summary>
public class FutureUniverseSelectionModel : UniverseSelectionModel
{
private DateTime _nextRefreshTimeUtc;
private readonly TimeSpan _refreshInterval;
private readonly UniverseSettings _universeSettings;
private readonly Func<DateTime, IEnumerable<Symbol>> _futureChainSymbolSelector;
/// <summary>
/// Gets the next time the framework should invoke the `CreateUniverses` method to refresh the set of universes.
/// </summary>
public override DateTime GetNextRefreshTimeUtc() => _nextRefreshTimeUtc;
/// <summary>
/// Creates a new instance of <see cref="FutureUniverseSelectionModel"/>
/// </summary>
/// <param name="refreshInterval">Time interval between universe refreshes</param>
/// <param name="futureChainSymbolSelector">Selects symbols from the provided future chain</param>
public FutureUniverseSelectionModel(TimeSpan refreshInterval, Func<DateTime, IEnumerable<Symbol>> futureChainSymbolSelector)
: this(refreshInterval, futureChainSymbolSelector, null)
{
}
/// <summary>
/// Creates a new instance of <see cref="FutureUniverseSelectionModel"/>
/// </summary>
/// <param name="refreshInterval">Time interval between universe refreshes</param>
/// <param name="futureChainSymbolSelector">Selects symbols from the provided future chain</param>\
public FutureUniverseSelectionModel(TimeSpan refreshInterval, PyObject futureChainSymbolSelector)
: this(refreshInterval, futureChainSymbolSelector.SafeAs<Func<DateTime, IEnumerable<Symbol>>>(), null)
{
}
/// <summary>
/// Creates a new instance of <see cref="FutureUniverseSelectionModel"/>
/// </summary>
/// <param name="refreshInterval">Time interval between universe refreshes</param>
/// <param name="futureChainSymbolSelector">Selects symbols from the provided future chain</param>\
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public FutureUniverseSelectionModel(TimeSpan refreshInterval, PyObject futureChainSymbolSelector, UniverseSettings universeSettings)
: this(refreshInterval, futureChainSymbolSelector.SafeAs<Func<DateTime, IEnumerable<Symbol>>>(), universeSettings)
{
}
/// <summary>
/// Creates a new instance of <see cref="FutureUniverseSelectionModel"/>
/// </summary>
/// <param name="refreshInterval">Time interval between universe refreshes</param>
/// <param name="futureChainSymbolSelector">Selects symbols from the provided future chain</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public FutureUniverseSelectionModel(
TimeSpan refreshInterval,
Func<DateTime, IEnumerable<Symbol>> futureChainSymbolSelector,
UniverseSettings universeSettings
)
{
_nextRefreshTimeUtc = DateTime.MinValue;
_refreshInterval = refreshInterval;
_universeSettings = universeSettings;
_futureChainSymbolSelector = futureChainSymbolSelector;
}
/// <summary>
/// Creates the universes for this algorithm. Called once after <see cref="IAlgorithm.Initialize"/>
/// </summary>
/// <param name="algorithm">The algorithm instance to create universes for</param>
/// <returns>The universes to be used by the algorithm</returns>
public override IEnumerable<Universe> CreateUniverses(QCAlgorithm algorithm)
{
_nextRefreshTimeUtc = algorithm.UtcTime + _refreshInterval;
var uniqueSymbols = new HashSet<Symbol>();
foreach (var futureSymbol in _futureChainSymbolSelector(algorithm.UtcTime))
{
if (futureSymbol.SecurityType != SecurityType.Future)
{
throw new ArgumentException("FutureChainSymbolSelector must return future symbols.");
}
// prevent creating duplicate future chains -- one per symbol
if (uniqueSymbols.Add(futureSymbol))
{
foreach (var universe in algorithm.CreateFutureChain(futureSymbol, Filter, _universeSettings))
{
yield return universe;
}
}
}
}
/// <summary>
/// Defines the future chain universe filter
/// </summary>
protected virtual FutureFilterUniverse Filter(FutureFilterUniverse filter)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(Filter), out FutureFilterUniverse result, filter))
{
return result;
}
// NOP
return filter;
}
}
/// <summary>
/// Provides an implementation of <see cref="IUniverseSelectionModel"/> that subscribes to future chains
/// </summary>
public class FuturesUniverseSelectionModel : FutureUniverseSelectionModel
{
/// <summary>
/// Creates a new instance of <see cref="FutureUniverseSelectionModel"/>
/// </summary>
/// <param name="refreshInterval">Time interval between universe refreshes</param>
/// <param name="futureChainSymbolSelector">Selects symbols from the provided future chain</param>
public FuturesUniverseSelectionModel(TimeSpan refreshInterval, Func<DateTime, IEnumerable<Symbol>> futureChainSymbolSelector)
: base(refreshInterval, futureChainSymbolSelector)
{
}
/// <summary>
/// Creates a new instance of <see cref="FutureUniverseSelectionModel"/>
/// </summary>
/// <param name="refreshInterval">Time interval between universe refreshes</param>
/// <param name="futureChainSymbolSelector">Selects symbols from the provided future chain</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public FuturesUniverseSelectionModel(TimeSpan refreshInterval,
Func<DateTime, IEnumerable<Symbol>> futureChainSymbolSelector,
UniverseSettings universeSettings)
: base(refreshInterval, futureChainSymbolSelector, universeSettings)
{
}
}
}
@@ -0,0 +1,64 @@
# 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 Selection.UniverseSelectionModel import UniverseSelectionModel
class FutureUniverseSelectionModel(UniverseSelectionModel):
'''Provides an implementation of IUniverseSelectionMode that subscribes to future chains'''
def __init__(self,
refreshInterval,
futureChainSymbolSelector,
universeSettings = None):
'''Creates a new instance of FutureUniverseSelectionModel
Args:
refreshInterval: Time interval between universe refreshes</param>
futureChainSymbolSelector: Selects symbols from the provided future chain
universeSettings: Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed'''
self.next_refresh_time_utc = datetime.min
self.refresh_interval = refreshInterval
self.future_chain_symbol_selector = futureChainSymbolSelector
self.universe_settings = universeSettings
def get_next_refresh_time_utc(self):
'''Gets the next time the framework should invoke the `CreateUniverses` method to refresh the set of universes.'''
return self.next_refresh_time_utc
def create_universes(self, algorithm: QCAlgorithm) -> list[Universe]:
'''Creates a new fundamental universe using this class's selection functions
Args:
algorithm: The algorithm instance to create universes for
Returns:
The universe defined by this model'''
self.next_refresh_time_utc = algorithm.utc_time + self.refresh_interval
unique_symbols = set()
for future_symbol in self.future_chain_symbol_selector(algorithm.utc_time):
if future_symbol.SecurityType != SecurityType.FUTURE:
raise ValueError("futureChainSymbolSelector must return future symbols.")
# prevent creating duplicate future chains -- one per symbol
if future_symbol not in unique_symbols:
unique_symbols.add(future_symbol)
selection = self.filter
if hasattr(self, "Filter") and callable(self.Filter):
selection = self.Filter
for universe in Extensions.create_future_chain(algorithm, future_symbol, selection, self.universe_settings):
yield universe
def filter(self, filter):
'''Defines the future chain universe filter'''
# NOP
return filter
@@ -0,0 +1,83 @@
/*
* 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.Data.UniverseSelection;
using System;
using System.Collections.Generic;
using Python.Runtime;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Inception Date Universe that accepts a Dictionary of DateTime keyed by String that represent
/// the Inception date for each ticker
/// </summary>
public class InceptionDateUniverseSelectionModel : CustomUniverseSelectionModel
{
private readonly Queue<KeyValuePair<string, DateTime>> _queue;
private readonly List<string> _symbols;
/// <summary>
/// Initializes a new instance of the <see cref="InceptionDateUniverseSelectionModel"/> class
/// </summary>
/// <param name="name">A unique name for this universe</param>
/// <param name="tickersByDate">Dictionary of DateTime keyed by String that represent the Inception date for each ticker</param>
public InceptionDateUniverseSelectionModel(string name, Dictionary<string, DateTime> tickersByDate) :
base(name, (Func<DateTime, IEnumerable<string>>)null)
{
_queue = new Queue<KeyValuePair<string, DateTime>>(tickersByDate);
_symbols = new List<string>();
}
/// <summary>
/// Initializes a new instance of the <see cref="InceptionDateUniverseSelectionModel"/> class
/// </summary>
/// <param name="name">A unique name for this universe</param>
/// <param name="tickersByDate">Dictionary of DateTime keyed by String that represent the Inception date for each ticker</param>
public InceptionDateUniverseSelectionModel(string name, PyObject tickersByDate) :
this(name, tickersByDate.ConvertToDictionary<string, DateTime>())
{
}
/// <summary>
/// Returns all tickers that are trading at current algorithm Time
/// </summary>
public override IEnumerable<string> Select(QCAlgorithm algorithm, DateTime date)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(Select), out IEnumerable<string> result, algorithm, date))
{
return result;
}
// Move Symbols that are trading from the queue to a list
var added = new List<string>();
while (_queue.TryPeek(out var keyValuePair) && keyValuePair.Value <= date)
{
added.Add(_queue.Dequeue().Key);
}
// If no pending for addition found, return Universe Unchanged
// Otherwise adds to list of current tickers and return it
if (added.Count == 0)
{
return Universe.Unchanged;
}
_symbols.AddRange(added);
return _symbols;
}
}
}
@@ -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.Linq;
using System.Collections.Generic;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Universe Selection Model that adds the following ETFs at their inception date
/// </summary>
public class LiquidETFUniverse : InceptionDateUniverseSelectionModel
{
/// <summary>
/// Represents the Energy ETF Category which can be used to access the list of Long and Inverse symbols
/// </summary>
public static readonly Grouping Energy = new Grouping(
new[]
{
"VDE", "USO", "XES", "XOP", "UNG", "ICLN", "ERX",
"UCO", "AMJ", "BNO", "AMLP", "UGAZ", "TAN"
},
new[] {"ERY", "SCO", "DGAZ" }
);
/// <summary>
/// Represents the Metals ETF Category which can be used to access the list of Long and Inverse symbols
/// </summary>
public static readonly Grouping Metals = new Grouping(
new[] {"GLD", "IAU", "SLV", "GDX", "AGQ", "PPLT", "NUGT", "USLV", "UGLD", "JNUG"},
new[] {"DUST", "JDST"}
);
/// <summary>
/// Represents the Technology ETF Category which can be used to access the list of Long and Inverse symbols
/// </summary>
public static readonly Grouping Technology = new Grouping(
new[] {"QQQ", "IGV", "QTEC", "FDN", "FXL", "TECL", "SOXL", "SKYY", "KWEB"},
new[] {"TECS", "SOXS"}
);
/// <summary>
/// Represents the Treasuries ETF Category which can be used to access the list of Long and Inverse symbols
/// </summary>
public static readonly Grouping Treasuries = new Grouping(
new[]
{
"IEF", "SHY", "TLT", "IEI", "TLH", "BIL", "SPTL",
"TMF", "SCHO", "SCHR", "SPTS", "GOVT"
},
new[] {"SHV", "TBT", "TBF", "TMV"}
);
/// <summary>
/// Represents the Volatility ETF Category which can be used to access the list of Long and Inverse symbols
/// </summary>
public static readonly Grouping Volatility = new Grouping(
new[] {"TVIX", "VIXY", "SPLV", "UVXY", "EEMV", "EFAV", "USMV"},
new[] {"SVXY"}
);
/// <summary>
/// Represents the SP500 Sectors ETF Category which can be used to access the list of Long and Inverse symbols
/// </summary>
public static readonly Grouping SP500Sectors = new Grouping(
new[] {"XLB", "XLE", "XLF", "XLI", "XLK", "XLP", "XLU", "XLV", "XLY"},
new string[0]
);
/// <summary>
/// Initializes a new instance of the LiquidETFUniverse class
/// </summary>
public LiquidETFUniverse() :
base(
"qc-liquid-etf-basket",
SP500Sectors
.Concat(Energy)
.Concat(Metals)
.Concat(Technology)
.Concat(Treasuries)
.Concat(Volatility)
// Convert the concatenated list of Symbol into a Dictionary of DateTime keyed by Symbol
// For equities, Symbol.ID is the first date the security is traded.
.ToDictionary(x => x.Value, x => x.ID.Date)
)
{
}
/// <summary>
/// Represent a collection of ETF symbols that is grouped according to a given criteria
/// </summary>
public class Grouping : List<Symbol>
{
/// <summary>
/// List of Symbols that follow the components direction
/// </summary>
public List<Symbol> Long { get; init; }
/// <summary>
/// List of Symbols that follow the components inverse direction
/// </summary>
public List<Symbol> Inverse { get; init; }
/// <summary>
/// Creates a new instance of <see cref="Grouping"/>.
/// </summary>
/// <param name="longTickers">List of tickers of ETFs that follows the components direction</param>
/// <param name="inverseTickers">List of tickers of ETFs that follows the components inverse direction</param>
public Grouping(IEnumerable<string> longTickers, IEnumerable<string> inverseTickers)
{
Long = longTickers.Select(x => Symbol.Create(x, SecurityType.Equity, Market.USA)).ToList();
Inverse = inverseTickers.Select(x => Symbol.Create(x, SecurityType.Equity, Market.USA)).ToList();
AddRange(Long);
AddRange(Inverse);
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
public override string ToString()
{
if (Count == 0)
{
return "No Symbols";
}
var longSymbols = Long.Count == 0
? string.Empty
: $" Long: {string.Join(",", Long.Select(x => x.Value))}";
var inverseSymbols = Inverse.Count == 0
? string.Empty
: $" Inverse: {string.Join(",", Inverse.Select(x => x.Value))}";
return $"{Count} symbols:{longSymbols}{inverseSymbols}";
}
}
}
}
@@ -0,0 +1,66 @@
/*
* 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;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Universe Selection Model that adds the following Metals ETFs at their inception date
/// 2004-11-18 GLD SPDR Gold Trust
/// 2005-01-28 IAU iShares Gold Trust
/// 2006-04-28 SLV iShares Silver Trust
/// 2006-05-22 GDX VanEck Vectors Gold Miners ETF
/// 2008-12-04 AGQ ProShares Ultra Silver
/// 2009-11-11 GDXJ VanEck Vectors Junior Gold Miners ETF
/// 2010-01-08 PPLT Aberdeen Standard Platinum Shares ETF
/// 2010-12-08 NUGT Direxion Daily Gold Miners Bull 3X Shares
/// 2010-12-08 DUST Direxion Daily Gold Miners Bear 3X Shares
/// 2011-10-17 USLV VelocityShares 3x Long Silver ETN
/// 2011-10-17 UGLD VelocityShares 3x Long Gold ETN
/// 2013-10-03 JNUG Direxion Daily Junior Gold Miners Index Bull 3x Shares
/// 2013-10-03 JDST Direxion Daily Junior Gold Miners Index Bear 3X Shares
/// </summary>
public class MetalsETFUniverse : InceptionDateUniverseSelectionModel
{
/// <summary>
/// Initializes a new instance of the MetalsETFUniverse class
/// </summary>
public MetalsETFUniverse() :
base(
"qc-metals-etf-basket",
new Dictionary<string, DateTime>()
{
{"GLD", new DateTime(2004, 11, 18)},
{"IAU", new DateTime(2005, 1, 28)},
{"SLV", new DateTime(2006, 4, 28)},
{"GDX", new DateTime(2006, 5, 22)},
{"AGQ", new DateTime(2008, 12, 4)},
{"GDXJ", new DateTime(2009, 11, 11)},
{"PPLT", new DateTime(2010, 1, 8)},
{"NUGT", new DateTime(2010, 12, 8)},
{"DUST", new DateTime(2010, 12, 8)},
{"USLV", new DateTime(2011, 10, 17)},
{"UGLD", new DateTime(2011, 10, 17)},
{"JNUG", new DateTime(2013, 10, 3)},
{"JDST", new DateTime(2013, 10, 3)}
}
)
{
}
}
}
@@ -0,0 +1,166 @@
/*
* 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 NodaTime;
using Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Selects contracts in a futures universe, sorted by open interest. This allows the selection to identifiy current
/// active contract.
/// </summary>
public class OpenInterestFutureUniverseSelectionModel : FutureUniverseSelectionModel
{
private readonly int? _chainContractsLookupLimit;
private readonly IAlgorithm _algorithm;
private readonly int? _resultsLimit;
private readonly MarketHoursDatabase _marketHoursDatabase;
/// <summary>
/// Creates a new instance of <see cref="OpenInterestFutureUniverseSelectionModel" />
/// </summary>
/// <param name="algorithm">Algorithm</param>
/// <param name="futureChainSymbolSelector">Selects symbols from the provided future chain</param>
/// <param name="chainContractsLookupLimit">Limit on how many contracts to query for open interest</param>
/// <param name="resultsLimit">Limit on how many contracts will be part of the universe</param>
public OpenInterestFutureUniverseSelectionModel(IAlgorithm algorithm, Func<DateTime, IEnumerable<Symbol>> futureChainSymbolSelector, int? chainContractsLookupLimit = 6,
int? resultsLimit = 1) : base(TimeSpan.FromDays(1), futureChainSymbolSelector)
{
_marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
if (algorithm == null)
{
throw new ArgumentNullException(nameof(algorithm));
}
_algorithm = algorithm;
_resultsLimit = resultsLimit;
_chainContractsLookupLimit = chainContractsLookupLimit;
}
/// <summary>
/// Creates a new instance of <see cref="OpenInterestFutureUniverseSelectionModel" />
/// </summary>
/// <param name="algorithm">Algorithm</param>
/// <param name="futureChainSymbolSelector">Selects symbols from the provided future chain</param>
/// <param name="chainContractsLookupLimit">Limit on how many contracts to query for open interest</param>
/// <param name="resultsLimit">Limit on how many contracts will be part of the universe</param>
public OpenInterestFutureUniverseSelectionModel(IAlgorithm algorithm, PyObject futureChainSymbolSelector, int? chainContractsLookupLimit = 6,
int? resultsLimit = 1) : this(algorithm, ConvertFutureChainSymbolSelectorToFunc(futureChainSymbolSelector), chainContractsLookupLimit, resultsLimit)
{
}
/// <summary>
/// Defines the future chain universe filter
/// </summary>
protected override FutureFilterUniverse Filter(FutureFilterUniverse filter)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(Filter), out FutureFilterUniverse result, filter))
{
return result;
}
// Remove duplicated keys
return filter.Contracts(FilterByOpenInterest(
filter.DistinctBy(x => x).ToDictionary(x => x.Symbol, x => _marketHoursDatabase.GetEntry(x.ID.Market, x, x.ID.SecurityType))));
}
/// <summary>
/// Filters a set of contracts based on open interest.
/// </summary>
/// <param name="contracts">Contracts to filter</param>
/// <returns>Filtered set</returns>
public IEnumerable<Symbol> FilterByOpenInterest(IReadOnlyDictionary<Symbol, MarketHoursDatabase.Entry> contracts)
{
var symbols = new List<Symbol>(_chainContractsLookupLimit.HasValue ? contracts.Keys.OrderBy(x => x.ID.Date).Take(_chainContractsLookupLimit.Value) : contracts.Keys);
var openInterest = symbols.GroupBy(x => contracts[x]).SelectMany(g => GetOpenInterest(g.Key, g.Select(i => i))).ToDictionary(x => x.Key, x => x.Value);
if (openInterest.Count == 0)
{
_algorithm.Error(
$"{nameof(OpenInterestFutureUniverseSelectionModel)}.{nameof(FilterByOpenInterest)}: Failed to get historical open interest, no symbol will be selected."
);
return Enumerable.Empty<Symbol>();
}
var filtered = openInterest.OrderByDescending(x => x.Value).ThenBy(x => x.Key.ID.Date).Select(x => x.Key);
if (_resultsLimit.HasValue)
{
filtered = filtered.Take(_resultsLimit.Value);
}
return filtered;
}
private Dictionary<Symbol, decimal> GetOpenInterest(MarketHoursDatabase.Entry marketHours, IEnumerable<Symbol> symbols)
{
var current = _algorithm.UtcTime;
var exchangeHours = marketHours.ExchangeHours;
var endTime = Instant.FromDateTimeUtc(_algorithm.UtcTime).InZone(exchangeHours.TimeZone).ToDateTimeUnspecified();
var previousDay = Time.GetStartTimeForTradeBars(exchangeHours, endTime, Time.OneDay, 1, true, marketHours.DataTimeZone);
var requests = symbols.Select(
symbol => new HistoryRequest(
previousDay,
current,
typeof(Tick),
symbol,
Resolution.Tick,
exchangeHours,
exchangeHours.TimeZone,
null,
true,
false,
DataNormalizationMode.Raw,
TickType.OpenInterest
)
)
.ToArray();
return _algorithm.HistoryProvider.GetHistory(requests, exchangeHours.TimeZone)
.Where(s => s.HasData && s.Ticks.Keys.Count > 0)
.SelectMany(s => s.Ticks.Select(x => new Tuple<Symbol, Tick>(x.Key, x.Value.LastOrDefault())))
.GroupBy(x => x.Item1)
.ToDictionary(x => x.Key, x => x.OrderByDescending(i => i.Item2.Time).LastOrDefault().Item2.Value);
}
/// <summary>
/// Converts future chain symbol selector, provided as a Python lambda function, to a managed func
/// </summary>
/// <param name="futureChainSymbolSelector">Python lambda function that selects symbols from the provided future chain</param>
/// <returns>Given Python future chain symbol selector as a func objet</returns>
/// <exception cref="ArgumentException"></exception>
private static Func<DateTime, IEnumerable<Symbol>> ConvertFutureChainSymbolSelectorToFunc(PyObject futureChainSymbolSelector)
{
if (futureChainSymbolSelector.TrySafeAs(out Func<DateTime, IEnumerable<Symbol>> futureSelector))
{
return futureSelector;
}
else
{
using (Py.GIL())
{
throw new ArgumentException($"FutureUniverseSelectionModel.ConvertFutureChainSymbolSelectorToFunc: {futureChainSymbolSelector.Repr()} is not a valid argument.");
}
}
}
}
}
@@ -0,0 +1,130 @@
/*
* 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 Python.Runtime;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Provides an implementation of <see cref="IUniverseSelectionModel"/> that subscribes to option chains
/// </summary>
public class OptionUniverseSelectionModel : UniverseSelectionModel
{
private DateTime _nextRefreshTimeUtc;
private readonly TimeSpan _refreshInterval;
private readonly UniverseSettings _universeSettings;
private readonly Func<DateTime, IEnumerable<Symbol>> _optionChainSymbolSelector;
/// <summary>
/// Gets the next time the framework should invoke the `CreateUniverses` method to refresh the set of universes.
/// </summary>
public override DateTime GetNextRefreshTimeUtc() => _nextRefreshTimeUtc;
/// <summary>
/// Creates a new instance of <see cref="OptionUniverseSelectionModel"/>
/// </summary>
/// <param name="refreshInterval">Time interval between universe refreshes</param>
/// <param name="optionChainSymbolSelector">Selects symbols from the provided option chain</param>
public OptionUniverseSelectionModel(TimeSpan refreshInterval, Func<DateTime, IEnumerable<Symbol>> optionChainSymbolSelector)
: this(refreshInterval, optionChainSymbolSelector, null)
{
}
/// <summary>
/// Creates a new instance of <see cref="OptionUniverseSelectionModel"/>
/// </summary>
/// <param name="refreshInterval">Time interval between universe refreshes</param>
/// <param name="optionChainSymbolSelector">Selects symbols from the provided option chain</param>
public OptionUniverseSelectionModel(TimeSpan refreshInterval, PyObject optionChainSymbolSelector)
: this(refreshInterval, optionChainSymbolSelector.SafeAs<Func<DateTime, IEnumerable<Symbol>>>(), null)
{
}
/// <summary>
/// Creates a new instance of <see cref="OptionUniverseSelectionModel"/>
/// </summary>
/// <param name="refreshInterval">Time interval between universe refreshes</param>
/// <param name="optionChainSymbolSelector">Selects symbols from the provided option chain</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public OptionUniverseSelectionModel(TimeSpan refreshInterval, PyObject optionChainSymbolSelector, UniverseSettings universeSettings)
: this(refreshInterval, optionChainSymbolSelector.SafeAs<Func<DateTime, IEnumerable<Symbol>>>(), universeSettings)
{
}
/// <summary>
/// Creates a new instance of <see cref="OptionUniverseSelectionModel"/>
/// </summary>
/// <param name="refreshInterval">Time interval between universe refreshes</param>
/// <param name="optionChainSymbolSelector">Selects symbols from the provided option chain</param>
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
public OptionUniverseSelectionModel(
TimeSpan refreshInterval,
Func<DateTime, IEnumerable<Symbol>> optionChainSymbolSelector,
UniverseSettings universeSettings
)
{
_nextRefreshTimeUtc = DateTime.MinValue;
_refreshInterval = refreshInterval;
_universeSettings = universeSettings;
_optionChainSymbolSelector = optionChainSymbolSelector;
}
/// <summary>
/// Creates the universes for this algorithm. Called once after <see cref="IAlgorithm.Initialize"/>
/// </summary>
/// <param name="algorithm">The algorithm instance to create universes for</param>
/// <returns>The universes to be used by the algorithm</returns>
public override IEnumerable<Universe> CreateUniverses(QCAlgorithm algorithm)
{
_nextRefreshTimeUtc = algorithm.UtcTime + _refreshInterval;
var uniqueUnderlyingSymbols = new HashSet<Symbol>();
foreach (var optionSymbol in _optionChainSymbolSelector(algorithm.UtcTime))
{
if (!optionSymbol.SecurityType.IsOption())
{
throw new ArgumentException("optionChainSymbolSelector must return option, index options, or futures options symbols.");
}
// prevent creating duplicate option chains -- one per underlying
if (uniqueUnderlyingSymbols.Add(optionSymbol.Underlying))
{
yield return algorithm.CreateOptionChain(optionSymbol, Filter, _universeSettings);
}
}
}
/// <summary>
/// Defines the option chain universe filter
/// </summary>
protected virtual OptionFilterUniverse Filter(OptionFilterUniverse filter)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(Filter), out OptionFilterUniverse result, filter))
{
return result;
}
// NOP
return filter;
}
}
}
@@ -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 *
from Selection.UniverseSelectionModel import UniverseSelectionModel
class OptionUniverseSelectionModel(UniverseSelectionModel):
'''Provides an implementation of IUniverseSelectionMode that subscribes to option chains'''
def __init__(self,
refreshInterval,
optionChainSymbolSelector,
universeSettings = None):
'''Creates a new instance of OptionUniverseSelectionModel
Args:
refreshInterval: Time interval between universe refreshes</param>
optionChainSymbolSelector: Selects symbols from the provided option chain
universeSettings: Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed'''
self.next_refresh_time_utc = datetime.min
self.refresh_interval = refreshInterval
self.option_chain_symbol_selector = optionChainSymbolSelector
self.universe_settings = universeSettings
def get_next_refresh_time_utc(self):
'''Gets the next time the framework should invoke the `CreateUniverses` method to refresh the set of universes.'''
return self.next_refresh_time_utc
def create_universes(self, algorithm: QCAlgorithm) -> list[Universe]:
'''Creates a new fundamental universe using this class's selection functions
Args:
algorithm: The algorithm instance to create universes for
Returns:
The universe defined by this model'''
self.next_refresh_time_utc = (algorithm.utc_time + self.refresh_interval).date()
uniqueUnderlyingSymbols = set()
for option_symbol in self.option_chain_symbol_selector(algorithm.utc_time):
if not Extensions.is_option(option_symbol.security_type):
raise ValueError("optionChainSymbolSelector must return option, index options, or futures options symbols.")
# prevent creating duplicate option chains -- one per underlying
if option_symbol.underlying not in uniqueUnderlyingSymbols:
uniqueUnderlyingSymbols.add(option_symbol.underlying)
selection = self.filter
if hasattr(self, "Filter") and callable(self.Filter):
selection = self.Filter
yield Extensions.create_option_chain(algorithm, option_symbol, selection, self.universe_settings)
def filter(self, filter):
'''Defines the option chain universe filter'''
# NOP
return filter
@@ -0,0 +1,151 @@
/*
* 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.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Defines the QC500 universe as a universe selection model for framework algorithm
/// For details: https://github.com/QuantConnect/Lean/pull/1663
/// </summary>
public class QC500UniverseSelectionModel : FundamentalUniverseSelectionModel
{
private const int _numberOfSymbolsCoarse = 1000;
private const int _numberOfSymbolsFine = 500;
// rebalances at the start of each month
private int _lastMonth = -1;
private readonly Dictionary<Symbol, double> _dollarVolumeBySymbol = new();
/// <summary>
/// Initializes a new default instance of the <see cref="QC500UniverseSelectionModel"/>
/// </summary>
public QC500UniverseSelectionModel()
: base(true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="QC500UniverseSelectionModel"/>
/// </summary>
/// <param name="universeSettings">Universe settings defines what subscription properties will be applied to selected securities</param>
public QC500UniverseSelectionModel(UniverseSettings universeSettings)
: base(true, universeSettings)
{
}
/// <summary>
/// Performs coarse selection for the QC500 constituents.
/// The stocks must have fundamental data
/// The stock must have positive previous-day close price
/// The stock must have positive volume on the previous trading day
/// </summary>
public override IEnumerable<Symbol> SelectCoarse(QCAlgorithm algorithm, IEnumerable<CoarseFundamental> coarse)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(SelectCoarse), out IEnumerable<Symbol> result, algorithm, coarse))
{
return result;
}
if (algorithm.Time.Month == _lastMonth)
{
return Universe.Unchanged;
}
var sortedByDollarVolume =
(from x in coarse
where x.HasFundamentalData && x.Volume > 0 && x.Price > 0
orderby x.DollarVolume descending
select x).Take(_numberOfSymbolsCoarse).ToList();
_dollarVolumeBySymbol.Clear();
foreach (var x in sortedByDollarVolume)
{
_dollarVolumeBySymbol[x.Symbol] = x.DollarVolume;
}
// If no security has met the QC500 criteria, the universe is unchanged.
// A new selection will be attempted on the next trading day as _lastMonth is not updated
if (_dollarVolumeBySymbol.Count == 0)
{
return Universe.Unchanged;
}
return _dollarVolumeBySymbol.Keys;
}
/// <summary>
/// Performs fine selection for the QC500 constituents
/// The company's headquarter must in the U.S.
/// The stock must be traded on either the NYSE or NASDAQ
/// At least half a year since its initial public offering
/// The stock's market cap must be greater than 500 million
/// </summary>
public override IEnumerable<Symbol> SelectFine(QCAlgorithm algorithm, IEnumerable<FineFundamental> fine)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(SelectFine), out IEnumerable<Symbol> result, algorithm, fine))
{
return result;
}
var filteredFine =
(from x in fine
where x.CompanyReference.CountryId == "USA" &&
(x.CompanyReference.PrimaryExchangeID == "NYS" || x.CompanyReference.PrimaryExchangeID == "NAS") &&
(algorithm.Time - x.SecurityReference.IPODate).Days > 180 &&
x.MarketCap > 500000000m
select x).ToList();
var count = filteredFine.Count;
// If no security has met the QC500 criteria, the universe is unchanged.
// A new selection will be attempted on the next trading day as _lastMonth is not updated
if (count == 0)
{
return Universe.Unchanged;
}
// Update _lastMonth after all QC500 criteria checks passed
_lastMonth = algorithm.Time.Month;
var percent = _numberOfSymbolsFine / (double)count;
// select stocks with top dollar volume in every single sector
var topFineBySector =
(from x in filteredFine
// Group by sector
group x by x.CompanyReference.IndustryTemplateCode into g
let y = from item in g
orderby _dollarVolumeBySymbol[item.Symbol] descending
select item
let c = (int)Math.Ceiling(y.Count() * percent)
select new { g.Key, Value = y.Take(c) }
).ToDictionary(x => x.Key, x => x.Value);
return topFineBySector.SelectMany(x => x.Value)
.OrderByDescending(x => _dollarVolumeBySymbol[x.Symbol])
.Take(_numberOfSymbolsFine)
.Select(x => x.Symbol);
}
}
}
@@ -0,0 +1,85 @@
# 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 Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel
from itertools import groupby
from math import ceil
class QC500UniverseSelectionModel(FundamentalUniverseSelectionModel):
'''Defines the QC500 universe as a universe selection model for framework algorithm
For details: https://github.com/QuantConnect/Lean/pull/1663'''
def __init__(self, filterFineData = True, universeSettings = None):
'''Initializes a new default instance of the QC500UniverseSelectionModel'''
super().__init__(filterFineData, universeSettings)
self.number_of_symbols_coarse = 1000
self.number_of_symbols_fine = 500
self.dollar_volume_by_symbol = {}
self.last_month = -1
def select_coarse(self, algorithm: QCAlgorithm, fundamental: list[Fundamental]):
'''Performs coarse selection for the QC500 constituents.
The stocks must have fundamental data
The stock must have positive previous-day close price
The stock must have positive volume on the previous trading day'''
if algorithm.time.month == self.last_month:
return Universe.UNCHANGED
sorted_by_dollar_volume = sorted([x for x in fundamental if x.has_fundamental_data and x.volume > 0 and x.price > 0],
key = lambda x: x.dollar_volume, reverse=True)[:self.number_of_symbols_coarse]
self.dollar_volume_by_symbol = {x.Symbol:x.dollar_volume for x in sorted_by_dollar_volume}
# If no security has met the QC500 criteria, the universe is unchanged.
# A new selection will be attempted on the next trading day as self.lastMonth is not updated
if len(self.dollar_volume_by_symbol) == 0:
return Universe.UNCHANGED
# return the symbol objects our sorted collection
return list(self.dollar_volume_by_symbol.keys())
def select_fine(self, algorithm: QCAlgorithm, fundamental: list[Fundamental]):
'''Performs fine selection for the QC500 constituents
The company's headquarter must in the U.S.
The stock must be traded on either the NYSE or NASDAQ
At least half a year since its initial public offering
The stock's market cap must be greater than 500 million'''
sorted_by_sector = sorted([x for x in fundamental if x.company_reference.country_id == "USA"
and x.company_reference.primary_exchange_id in ["NYS","NAS"]
and (algorithm.time - x.security_reference.ipo_date).days > 180
and x.market_cap > 5e8],
key = lambda x: x.company_reference.industry_template_code)
count = len(sorted_by_sector)
# If no security has met the QC500 criteria, the universe is unchanged.
# A new selection will be attempted on the next trading day as self.lastMonth is not updated
if count == 0:
return Universe.UNCHANGED
# Update self.lastMonth after all QC500 criteria checks passed
self.last_month = algorithm.time.month
percent = self.number_of_symbols_fine / count
sorted_by_dollar_volume = []
# select stocks with top dollar volume in every single sector
for code, g in groupby(sorted_by_sector, lambda x: x.company_reference.industry_template_code):
y = sorted(g, key = lambda x: self.dollar_volume_by_symbol[x.Symbol], reverse = True)
c = ceil(len(y) * percent)
sorted_by_dollar_volume.extend(y[:c])
sorted_by_dollar_volume = sorted(sorted_by_dollar_volume, key = lambda x: self.dollar_volume_by_symbol[x.Symbol], reverse=True)
return [x.Symbol for x in sorted_by_dollar_volume[:self.number_of_symbols_fine]]
@@ -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;
using System.Collections.Generic;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Universe Selection Model that adds the following SP500 Sectors ETFs at their inception date
/// 1998-12-22 XLB Materials Select Sector SPDR ETF
/// 1998-12-22 XLE Energy Select Sector SPDR Fund
/// 1998-12-22 XLF Financial Select Sector SPDR Fund
/// 1998-12-22 XLI Industrial Select Sector SPDR Fund
/// 1998-12-22 XLK Technology Select Sector SPDR Fund
/// 1998-12-22 XLP Consumer Staples Select Sector SPDR Fund
/// 1998-12-22 XLU Utilities Select Sector SPDR Fund
/// 1998-12-22 XLV Health Care Select Sector SPDR Fund
/// 1998-12-22 XLY Consumer Discretionary Select Sector SPDR Fund
/// </summary>
public class SP500SectorsETFUniverse : InceptionDateUniverseSelectionModel
{
/// <summary>
/// Initializes a new instance of the SP500SectorsETFUniverse class
/// </summary>
public SP500SectorsETFUniverse() :
base(
"qc-sp500-sectors-etf-basket",
new Dictionary<string, DateTime>()
{
{"XLB", new DateTime(1998, 12, 22)},
{"XLE", new DateTime(1998, 12, 22)},
{"XLF", new DateTime(1998, 12, 22)},
{"XLI", new DateTime(1998, 12, 22)},
{"XLK", new DateTime(1998, 12, 22)},
{"XLP", new DateTime(1998, 12, 22)},
{"XLU", new DateTime(1998, 12, 22)},
{"XLV", new DateTime(1998, 12, 22)},
{"XLY", new DateTime(1998, 12, 22)}
}
)
{
}
}
}
@@ -0,0 +1,118 @@
/*
* 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 NodaTime;
using Python.Runtime;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Defines a universe selection model that invokes a selector function on a specific scheduled given by an <see cref="IDateRule"/> and an <see cref="ITimeRule"/>
/// </summary>
public class ScheduledUniverseSelectionModel : UniverseSelectionModel
{
private readonly IDateRule _dateRule;
private readonly ITimeRule _timeRule;
private readonly Func<DateTime, IEnumerable<Symbol>> _selector;
private readonly DateTimeZone _timeZone;
private readonly UniverseSettings _settings;
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledUniverseSelectionModel"/> class using the algorithm's time zone
/// </summary>
/// <param name="dateRule">Date rule defines what days the universe selection function will be invoked</param>
/// <param name="timeRule">Time rule defines what times on each day selected by date rule the universe selection function will be invoked</param>
/// <param name="selector">Selector function accepting the date time firing time and returning the universe selected symbols</param>
/// <param name="settings">Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings</param>
public ScheduledUniverseSelectionModel(IDateRule dateRule, ITimeRule timeRule, Func<DateTime, IEnumerable<Symbol>> selector, UniverseSettings settings = null)
{
_dateRule = dateRule;
_timeRule = timeRule;
_selector = selector;
_settings = settings;
}
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledUniverseSelectionModel"/> class
/// </summary>
/// <param name="timeZone">The time zone the date/time rules are in</param>
/// <param name="dateRule">Date rule defines what days the universe selection function will be invoked</param>
/// <param name="timeRule">Time rule defines what times on each day selected by date rule the universe selection function will be invoked</param>
/// <param name="selector">Selector function accepting the date time firing time and returning the universe selected symbols</param>
/// <param name="settings">Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings</param>
public ScheduledUniverseSelectionModel(DateTimeZone timeZone, IDateRule dateRule, ITimeRule timeRule, Func<DateTime, IEnumerable<Symbol>> selector, UniverseSettings settings = null)
{
_timeZone = timeZone;
_dateRule = dateRule;
_timeRule = timeRule;
_selector = selector;
_settings = settings;
}
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledUniverseSelectionModel"/> class using the algorithm's time zone
/// </summary>
/// <param name="dateRule">Date rule defines what days the universe selection function will be invoked</param>
/// <param name="timeRule">Time rule defines what times on each day selected by date rule the universe selection function will be invoked</param>
/// <param name="selector">Selector function accepting the date time firing time and returning the universe selected symbols</param>
/// <param name="settings">Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings</param>
public ScheduledUniverseSelectionModel(IDateRule dateRule, ITimeRule timeRule, PyObject selector, UniverseSettings settings = null)
: this(null, dateRule, timeRule, selector, settings)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledUniverseSelectionModel"/> class
/// </summary>
/// <param name="timeZone">The time zone the date/time rules are in</param>
/// <param name="dateRule">Date rule defines what days the universe selection function will be invoked</param>
/// <param name="timeRule">Time rule defines what times on each day selected by date rule the universe selection function will be invoked</param>
/// <param name="selector">Selector function accepting the date time firing time and returning the universe selected symbols</param>
/// <param name="settings">Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings</param>
public ScheduledUniverseSelectionModel(DateTimeZone timeZone, IDateRule dateRule, ITimeRule timeRule, PyObject selector, UniverseSettings settings = null)
{
Func<DateTime, object> func;
selector.TrySafeAs(out func);
_timeZone = timeZone;
_dateRule = dateRule;
_timeRule = timeRule;
_selector = func.ConvertSelectionSymbolDelegate();
_settings = settings;
}
/// <summary>
/// Creates the universes for this algorithm. Called once after <see cref="IAlgorithm.Initialize"/>
/// </summary>
/// <param name="algorithm">The algorithm instance to create universes for</param>
/// <returns>The universes to be used by the algorithm</returns>
public override IEnumerable<Universe> CreateUniverses(QCAlgorithm algorithm)
{
yield return new ScheduledUniverse(
// by default ITimeRule yields in UTC
_timeZone ?? TimeZones.Utc,
_dateRule,
_timeRule,
_selector,
_settings ?? algorithm.UniverseSettings
);
}
}
}
@@ -0,0 +1,72 @@
/*
* 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;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Universe Selection Model that adds the following Technology ETFs at their inception date
/// 1998-12-22 XLK Technology Select Sector SPDR Fund
/// 1999-03-10 QQQ Invesco QQQ
/// 2001-07-13 SOXX iShares PHLX Semiconductor ETF
/// 2001-07-13 IGV iShares Expanded Tech-Software Sector ETF
/// 2004-01-30 VGT Vanguard Information Technology ETF
/// 2006-04-25 QTEC First Trust NASDAQ 100 Technology
/// 2006-06-23 FDN First Trust Dow Jones Internet Index
/// 2007-05-10 FXL First Trust Technology AlphaDEX Fund
/// 2008-12-17 TECL Direxion Daily Technology Bull 3X Shares
/// 2008-12-17 TECS Direxion Daily Technology Bear 3X Shares
/// 2010-03-11 SOXL Direxion Daily Semiconductor Bull 3x Shares
/// 2010-03-11 SOXS Direxion Daily Semiconductor Bear 3x Shares
/// 2011-07-06 SKYY First Trust ISE Cloud Computing Index Fund
/// 2011-12-21 SMH VanEck Vectors Semiconductor ETF
/// 2013-08-01 KWEB KraneShares CSI China Internet ETF
/// 2013-10-24 FTEC Fidelity MSCI Information Technology Index ETF
/// </summary>
public class TechnologyETFUniverse : InceptionDateUniverseSelectionModel
{
/// <summary>
/// Initializes a new instance of the TechnologyETFUniverse class
/// </summary>
public TechnologyETFUniverse() :
base(
"qc-technology-etf-basket",
new Dictionary<string, DateTime>()
{
{"XLK", new DateTime(1998, 12, 22)},
{"QQQ", new DateTime(1999, 3, 10)},
{"SOXX", new DateTime(2001, 7, 13)},
{"IGV", new DateTime(2001, 7, 13)},
{"VGT", new DateTime(2004, 1, 30)},
{"QTEC", new DateTime(2006, 4, 25)},
{"FDN", new DateTime(2006, 6, 23)},
{"FXL", new DateTime(2007, 5, 10)},
{"TECL", new DateTime(2008, 12, 17)},
{"TECS", new DateTime(2008, 12, 17)},
{"SOXL", new DateTime(2010, 3, 11)},
{"SOXS", new DateTime(2010, 3, 11)},
{"SKYY", new DateTime(2011, 7, 6)},
{"SMH", new DateTime(2011, 12, 21)},
{"KWEB", new DateTime(2013, 8, 1)},
{"FTEC", new DateTime(2013, 10, 24)}
}
)
{
}
}
}
@@ -0,0 +1,80 @@
/*
* 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;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Universe Selection Model that adds the following US Treasuries ETFs at their inception date
/// 2002-07-26 IEF iShares 7-10 Year Treasury Bond ETF
/// 2002-07-26 SHY iShares 1-3 Year Treasury Bond ETF
/// 2002-07-26 TLT iShares 20+ Year Treasury Bond ETF
/// 2007-01-11 SHV iShares Short Treasury Bond ETF
/// 2007-01-11 IEI iShares 3-7 Year Treasury Bond ETF
/// 2007-01-11 TLH iShares 10-20 Year Treasury Bond ETF
/// 2007-12-10 EDV Vanguard Ext Duration Treasury ETF
/// 2007-05-30 BIL SPDR Barclays 1-3 Month T-Bill ETF
/// 2007-05-30 SPTL SPDR Portfolio Long Term Treasury ETF
/// 2008-05-01 TBT UltraShort Barclays 20+ Year Treasury
/// 2009-04-16 TMF Direxion Daily 20-Year Treasury Bull 3X
/// 2009-04-16 TMV Direxion Daily 20-Year Treasury Bear 3X
/// 2009-08-20 TBF ProShares Short 20+ Year Treasury
/// 2009-11-23 VGSH Vanguard Short-Term Treasury ETF
/// 2009-11-23 VGIT Vanguard Intermediate-Term Treasury ETF
/// 2009-11-24 VGLT Vanguard Long-Term Treasury ETF
/// 2010-08-06 SCHO Schwab Short-Term U.S. Treasury ETF
/// 2010-08-06 SCHR Schwab Intermediate-Term U.S. Treasury ETF
/// 2011-12-01 SPTS SPDR Portfolio Short Term Treasury ETF
/// 2012-02-24 GOVT iShares U.S. Treasury Bond ETF
/// </summary>
public class USTreasuriesETFUniverse : InceptionDateUniverseSelectionModel
{
/// <summary>
/// Initializes a new instance of the USTreasuriesETFUniverse class
/// </summary>
public USTreasuriesETFUniverse() :
base(
"qc-us-treasuries-etf-basket",
new Dictionary<string, DateTime>()
{
{"IEF", new DateTime(2002, 7, 26)},
{"SHY", new DateTime(2002, 7, 26)},
{"TLT", new DateTime(2002, 7, 26)},
{"IEI", new DateTime(2007, 1, 11)},
{"SHV", new DateTime(2007, 1, 11)},
{"TLH", new DateTime(2007, 1, 11)},
{"EDV", new DateTime(2007, 12, 10)},
{"BIL", new DateTime(2007, 5, 30)},
{"SPTL", new DateTime(2007, 5, 30)},
{"TBT", new DateTime(2008, 5, 1)},
{"TMF", new DateTime(2009, 4, 16)},
{"TMV", new DateTime(2009, 4, 16)},
{"TBF", new DateTime(2009, 8, 20)},
{"VGSH", new DateTime(2009, 11, 23)},
{"VGIT", new DateTime(2009, 11, 23)},
{"VGLT", new DateTime(2009, 11, 24)},
{"SCHO", new DateTime(2010, 8, 6)},
{"SCHR", new DateTime(2010, 8, 6)},
{"SPTS", new DateTime(2011, 12, 1)},
{"GOVT", new DateTime(2012, 2, 24)}
}
)
{
}
}
}
@@ -0,0 +1,60 @@
/*
* 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;
namespace QuantConnect.Algorithm.Framework.Selection
{
/// <summary>
/// Universe Selection Model that adds the following Volatility ETFs at their inception date
/// 2010-02-11 SQQQ ProShares UltraPro ShortQQQ
/// 2010-02-11 TQQQ ProShares UltraProQQQ
/// 2010-11-30 TVIX VelocityShares Daily 2x VIX Short Term ETN
/// 2011-01-04 VIXY ProShares VIX Short-Term Futures ETF
/// 2011-05-05 SPLV Invesco S&amp;P 500® Low Volatility ETF
/// 2011-10-04 SVXY ProShares Short VIX Short-Term Futures
/// 2011-10-04 UVXY ProShares Ultra VIX Short-Term Futures
/// 2011-10-20 EEMV iShares Edge MSCI Min Vol Emerging Markets ETF
/// 2011-10-20 EFAV iShares Edge MSCI Min Vol EAFE ETF
/// 2011-10-20 USMV iShares Edge MSCI Min Vol USA ETF
/// </summary>
public class VolatilityETFUniverse : InceptionDateUniverseSelectionModel
{
/// <summary>
/// Initializes a new instance of the VolatilityETFUniverse class
/// </summary>
public VolatilityETFUniverse() :
base(
"qc-volatility-etf-basket",
new Dictionary<string, DateTime>()
{
{"SQQQ", new DateTime(2010, 2, 11)},
{"TQQQ", new DateTime(2010, 2, 11)},
{"TVIX", new DateTime(2010, 11, 30)},
{"VIXY", new DateTime(2011, 1, 4)},
{"SPLV", new DateTime(2011, 5, 5)},
{"SVXY", new DateTime(2011, 10, 4)},
{"UVXY", new DateTime(2011, 10, 4)},
{"EEMV", new DateTime(2011, 10, 20)},
{"EFAV", new DateTime(2011, 10, 20)},
{"USMV", new DateTime(2011, 10, 20)}
}
)
{
}
}
}