Problem Description
Solution
To create a custom risk management model in QuantConnect, you need to define a class that inherits from RiskManagementModel and implements the manage_risk method. This method analyzes the current portfolio state and incoming portfolio targets, returning a list of new targets to adjust positions (usually to reduce risk).
Below is a complete example of a custom Trailing Drawdown Risk Model. This model monitors every open position; if a position's unrealized profit percentage drops below a specific threshold (e.g., -5%), it forces a liquidation.
Custom Risk Management Strategy
# region imports
from AlgorithmImports import *
# endregion
class CustomRiskManagementAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2020, 1, 1)
self.set_end_date(2023, 1, 1)
self.set_cash(100000)
# 1. Add Equities
self.add_equity("SPY", Resolution.DAILY)
self.add_equity("QQQ", Resolution.DAILY)
# 2. Set the Custom Risk Management Model
# We pass 0.05 (5%) as the maximum allowed drawdown per position
self.set_risk_management(TrailingDrawdownRiskModel(maximum_drawdown_percent=0.05))
def on_data(self, data: Slice):
# Simple logic to enter positions for demonstration
if not self.portfolio.invested:
self.set_holdings("SPY", 0.5)
self.set_holdings("QQQ", 0.5)
# ------------------------------------------------------------------------------
# CUSTOM RISK MANAGEMENT MODEL
# ------------------------------------------------------------------------------
class TrailingDrawdownRiskModel(RiskManagementModel):
"""
Liquidates a position if its unrealized profit percentage drops below
the specified maximum drawdown threshold.
"""
def __init__(self, maximum_drawdown_percent=0.05):
"""
Args:
maximum_drawdown_percent: The negative return threshold to trigger liquidation (e.g. 0.05 for 5%)
"""
self.maximum_drawdown_percent = abs(maximum_drawdown_percent)
def manage_risk(self, algorithm, targets):
"""
Called by the framework to check for risk.
Args:
algorithm: The QCAlgorithm instance
targets: The list of PortfolioTarget objects from the Portfolio Construction Model
Returns:
A list of PortfolioTarget objects to be executed
"""
risk_adjusted_targets = []
# Loop through all securities in the algorithm's universe
for kvp in algorithm.securities:
security = kvp.value
symbol = security.symbol
# We only care about securities that are currently invested
if not security.invested:
continue
# Calculate the Unrealized Profit Percentage
# (Current Price - Average Price) / Average Price
pnl_percent = security.holdings.unrealized_profit_percent
# Check if the PnL is below our negative threshold (e.g., < -0.05)
if pnl_percent < -self.maximum_drawdown_percent:
# Log the event for debugging
algorithm.log(f"RISK ALERT: Liquidating {symbol} | PnL: {pnl_percent:.2%}")
# Create a target to liquidate the position (Quantity = 0)
risk_adjusted_targets.append(PortfolioTarget(symbol, 0))
return risk_adjusted_targets
Key Implementation Details
- Inheritance: The class must inherit from
RiskManagementModel. manage_riskMethod: This is the core entry point.- Input: It receives the
algorithminstance (giving access toself.portfolio,self.securities, etc.) andtargets(the desired trades coming from your Alpha/Portfolio models). - Output: It must return a list of
PortfolioTargetobjects.
- Input: It receives the
- Logic: In the example above, we iterate through
algorithm.securities. Ifunrealized_profit_percentviolates our limit, we create aPortfolioTarget(symbol, 0). This tells the Execution model to sell everything for that symbol immediately. - Integration: Use
self.set_risk_management(YourModel())in theinitializemethod of your algorithm.
How It Fits in the Framework
The Risk Management model runs after the Portfolio Construction model but before the Execution model.
- Alpha Model: Generates Insights (Predictions).
- Portfolio Construction: Converts Insights into Targets (Desired Quantities).
- Risk Management: (You are here) Reviews the Portfolio and Targets. It can cancel targets or create new ones to close positions (liquidate).
- Execution: Executes the final list of targets.
Q&A: Custom Risk Management in QuantConnect
Q: Can I use multiple risk models at the same time?
A: Yes. Instead of set_risk_management, you can use self.add_risk_management(Model1()) followed by self.add_risk_management(Model2()). The framework will run them sequentially, and if any model triggers a liquidation or adjustment, that target will be passed down.
Q: How do I prevent re-entry after a risk liquidation?
A: The manage_risk method only handles the immediate liquidation. To prevent re-entry, you usually need to maintain a state (e.g., a set of "cooldown" symbols) within your class. In manage_risk, you would check the incoming targets argument and remove any buy targets for symbols that are currently in your cooldown list.
Q: Does manage_risk run on every tick?
A: It runs every time the algorithm processes a "Time Slice." In backtesting, this is determined by your data resolution (e.g., every minute for Resolution.MINUTE). In live trading, it runs in real-time as data arrives.
Q: Can I modify the size of a position instead of liquidating it completely?
A: Yes. Instead of sending a target with quantity 0, you can calculate a reduced quantity. For example, if you want to cut exposure by half, you would return PortfolioTarget(symbol, current_holdings * 0.5).