chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+455
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+310
@@ -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 > 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&P Oil & Gas Equipment & Services ETF
|
||||
/// 2006-06-22 XOP SPDR S&P Oil & Gas Exploration & 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&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)}
|
||||
}
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user