chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -0,0 +1,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