chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user