Risk Management
Key Concepts
Introduction
The Risk Management model seeks to manage risk on the PortfolioTarget
collection it receives from the Portfolio Construction model before the targets reach the Execution model. There are many creative ways to manage risk. Some examples of risk management include the following:
- "Trailing Stop Risk Management Model"
Create and manage trailing stop-loss orders for open positions. - "Option Hedging Risk Management Model"
Purchase options to hedge large equity exposures. - "Sector Exposure Risk Management Model"
Reduce position sizes when overexposed to sectors or individual assets, keeping the portfolio within diversification requirements. - "Flash Crash Detection Risk Management Model"
Scan for strange market situations that might be precursors to a flash crash and attempt to protect the portfolio when they are detected.
Add Models
To set a Risk Management model, in the Initialize
initialize
method, call the AddRiskManagement
method.
# Add the null risk management model doesn't affect the portfolio targets. self.add_risk_management(NullRiskManagementModel())
// Add the null risk management model doesn't affect the portfolio targets. AddRiskManagement(new NullRiskManagementModel());
To view all the pre-built Risk Management models, see Supported Models.
Multi-Model Algorithms
To add multiple Risk Management models, in the Initialize
initialize
method, call the AddRiskManagement
method multiple times.
// Add multiple Risk Management models to sequentially adjust portfolio targets, with each model refining the targets passed from the previous model, managing risk across individual securities and sectors. AddRiskManagement(new MaximumDrawdownPercentPerSecurity()); AddRiskManagement(new MaximumSectorExposureRiskManagementModel());
# Add multiple Risk Management models to sequentially adjust portfolio targets, with each model refining the targets passed from the previous model, managing risk across individual securities and sectors. self.add_risk_management(MaximumDrawdownPercentPerSecurity()) self.add_risk_management(MaximumSectorExposureRiskManagementModel())
If you add multiple Risk Management models, the original collection of PortfolioTarget
objects from the Portfolio Construction model is passed to the first Risk Management model. The risk-adjusted targets from the first Risk Management model are passed to the second Risk Management model. The process continues sequentially until all of the Risk Management models have had an opportunity to adjust the targets.
Model Structure
Risk Management models should extend the RiskManagementModel
class. Extensions of the RiskManagementModel
class must implement the ManageRisk
manage_risk
method, which receives an array of PortfolioTarget
objects from the Portfolio Construction model at every time step and should return an array of risk-adjusted PortfolioTarget
objects. The method should only return the adjusted targets, not all of targets. If the method creates a PortfolioTarget
object to liquidate a security, cancel the security's insights to avoid re-entering the position.
// Extend the RiskManagementModel class by implementing the ManageRisk method, which receives PortfolioTarget objects from the Portfolio Construction model and returns only the risk-adjusted targets. class MyRiskManagementModel : RiskManagementModel { // Adjust the portfolio targets and return them. If no changes emit nothing. public override List<PortfolioTarget> ManageRisk(QCAlgorithm algorithm, PortfolioTarget[] targets) { return new List<PortfolioTarget>(); } // Optional: Be notified when securities change public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes) { // Security additions and removals are pushed here. // This can be used for setting up algorithm state. // changes.AddedSecurities // changes.RemovedSecurities } }
# Extend the RiskManagementModel class by implementing the manage_risk method, which receives PortfolioTarget objects from the Portfolio Construction model and returns only the risk-adjusted targets. class MyRiskManagementModel(RiskManagementModel): # Adjust the portfolio targets and return them. If no changes emit nothing. def manage_risk(self, algorithm: QCAlgorithm, targets: List[PortfolioTarget]) -> List[PortfolioTarget]: return [] # Optional: Be notified when securities change def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None: # Security additions and removals are pushed here. # This can be used for setting up algorithm state. # changes.added_securities # changes.removed_securities pass
The algorithm
argument that the methods receive is an instance of the base QCAlgorithm
class, not your subclass of it.
To view a full example of a RiskManagementModel
subclass, see the MaximumDrawdownPercentPerSecurityMaximumDrawdownPercentPerSecurity in the LEAN GitHub repository.
Track Security Changes
The Universe Selection model may select a dynamic universe of assets, so you should not assume a fixed set of assets in the Risk Management model. When the Universe Selection model adds and removes assets from the universe, it triggers an OnSecuritiesChanged
on_securities_changed
event. In the OnSecuritiesChanged
on_securities_changed
event handler, you can initialize the security-specific state or load any history required for your Risk Management model. If you need to save data for individual securities, add custom members to the respective Security
objectcast the Security
object to a dynamic
object and then save custom members to it.
class MyRiskManagementModel : RiskManagementModel{ private List<Security> _securities = new List<Security>(); public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes) { base.OnSecuritiesChanged(algorithm, changes); foreach (var security in changes.AddedSecurities) { // Store and manage Symbol-specific data var dynamicSecurity = security as dynamic; dynamicSecurity.Sma = SMA(security.Symbol, 20); _securities.Add(security); } foreach (var security in changes.RemovedSecurities) { if (_securities.Contains(security)) { algorithm.DeregisterIndicator((security as dynamic).Sma); _securities.Remove(security); } } } }
class MyRiskManagementModel(RiskManagementModel): _securities = [] def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None: super().on_securities_changed(algorithm, changes) for security in changes.added_securities:: # Store and manage Symbol-specific data security.indicator = algorithm.sma(security.symbol, 20) algorithm.warm_up_indicator(security.symbol, security.indicator) self._securities.append(security) for security in changes.removed_securities: if security in self.securities: algorithm.deregister_indicator(security.indicator) self._securities.remove(security)
Portfolio Target Collection
The PortfolioTargetCollection
class is a helper class to manage PortfolioTarget
objects. The class manages an internal dictionary that has the security Symbol
as the key and a PortfolioTarget
as the value.
Add Portfolio Targets
To add a PortfolioTarget
to the PortfolioTargetCollection
, call the Add
add
method.
_targetsCollection.Add(portfolioTarget);
self.targets_collection.add(portfolio_target)
To add a list of PortfolioTarget
objects, call the AddRange
add_range
method.
_targetsCollection.AddRange(portfolioTargets);
self.targets_collection.add_range(portfolio_targets)
Check Membership
To check if a PortfolioTarget
exists in the PortfolioTargetCollection
, call the Contains
contains
method.
var targetInCollection = _targetsCollection.Contains(portfolioTarget);
target_in_collection = self.targets_collection.contains(portfolio_target)
To check if a Symbol exists in the PortfolioTargetCollection
, call the ContainsKey
contains_key
method.
var symbolInCollection = _targetsCollection.ContainsKey(symbol);
symbol_in_collection = self.targets_collection.contains_key(symbol)
To get all the Symbol objects, use the Keys
keys
property.
var symbols = _targetsCollection.Keys;
symbols = self.targets_collection.keys
Access Portfolio Targets
To access the PortfolioTarget
objects for a Symbol, index the PortfolioTargetCollection
with the Symbol.
var portfolioTarget = _targetsCollection[symbol];
portfolio_target = self.targets_collection[symbol]
To iterate through the PortfolioTargetCollection
, call the GetEnumerator
get_enumerator
method.
var enumerator = _targetsCollection.GetEnumerator();
enumerator = self.targets_collection.get_enumerator()
To get all the PortfolioTarget
objects, use the Values
values
property
var portfolioTargets = _targetsCollection.Values;
portfolio_targets = self.targets_collection.values
Order Portfolio Targets by Margin Impact
To get an enumerable where position reducing orders are executed first and the remaining orders are executed in decreasing order value, call the OrderByMarginImpact
order_by_margin_impact
method.
foreach (var target in _targetsCollection.OrderByMarginImpact(algorithm)) { // Place order }
for target in self.targets_collection.order_by_margin_impact(algorithm): # Place order
This method won't return targets for securities that have no data yet. This method also won't return targets for which the sum of the current holdings and open orders quantity equals the target quantity.
Remove Portfolio Targets
To remove a PortfolioTarget
from the PortfolioTargetCollection
, call the Remove
remove
method.
removeSuccessful = _targetsCollection.Remove(symbol);
remove_successful = self.targets_collection.remove(symbol)
To remove all the PortfolioTarget
objects, call the Clear
clear
method.
_targetsCollection.Clear();
self.targets_collection.clear()
To remove all the PortfolioTarget
objects that have been fulfilled, call the ClearFulfilled
clear_fulfilled
method.
_targetsCollection.ClearFulfilled(algorithm);
self.targets_collection.clear_fulfilled(algorithm)
Universe Timing Considerations
If the Risk Management model manages some indicators or consolidators for securities in the universe and the universe selection runs during the indicator sampling period or the consolidator aggregation period, the indicators and consolidators might be missing some data. For example, take the following scenario:
- The security resolution is minute
- You have a consolidator that aggregates the security data into daily bars to update the indicator
- The universe selection runs at noon
In this scenario, you create and warm-up the indicator at noon. Since it runs at noon, the history request that gathers daily data to warm up the indicator won't contain any data from the current day and the consolidator that updates the indicator also won't aggregate any data from before noon. This process doesn't cause issues if the indicator only uses the close price to calculate the indicator value (like the simple moving average indicator) because the first consolidated bar that updates the indicator will have the correct close price. However, if the indicator uses more than just the close price to calculate its value (like the True Range indicator), the open, high, and low values of the first consolidated bar may be incorrect, causing the initial indicator values to be incorrect.