Problem Description
Solution
To control which assets are liquidated during a margin call in QuantConnect, you must override the on_margin_call event handler in your algorithm.
How It Works
- Default Behavior: When your portfolio falls below the maintenance margin requirement, LEAN automatically generates a list of
SubmitOrderRequestobjects designed to liquidate positions to restore margin. - The Override: By defining
on_margin_call(self, requests), you intercept these requests. - Custom Logic: Inside this method, you can:
- Reorder the requests (e.g., sell losing positions first).
- Filter the requests (e.g., protect specific assets from being sold).
- Modify the quantity (e.g., sell more than required to create a buffer).
- Create New Requests: Ignore the engine's suggestions and create your own liquidation orders for specific symbols.
Strategy Implementation
The following example demonstrates a strategy that intentionally over-leverages to trigger a margin call. It implements custom logic to prioritize selling the asset with the lowest Unrealized Profit (selling losers first), rather than letting the engine decide arbitrarily.
# region imports
from AlgorithmImports import *
# endregion
class MarginCallControlAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2020, 1, 1)
self.set_end_date(2021, 1, 1)
self.set_cash(100000)
# 1. Add multiple assets to the portfolio
self.tickers = ["SPY", "TLT", "GLD", "QQQ"]
self.symbols = [self.add_equity(ticker, Resolution.DAILY).symbol for ticker in self.tickers]
# 2. Set a brokerage model that allows margin trading (e.g., Interactive Brokers)
self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
def on_data(self, data: Slice):
# 3. Intentionally over-leverage to trigger margin calls
# We invest 50% in EACH of the 4 assets (200% total leverage)
if not self.portfolio.invested:
for symbol in self.symbols:
self.set_holdings(symbol, 0.5)
def on_margin_call_warning(self):
# Optional: Log when we are close to a margin call (5% buffer remaining)
self.log(f"Margin Call Warning! Remaining Margin: {self.portfolio.margin_remaining}")
def on_margin_call(self, requests: List[SubmitOrderRequest]) -> List[SubmitOrderRequest]:
"""
This method is called when the portfolio breaches maintenance margin.
'requests' contains the orders LEAN suggests executing.
We will override this to prioritize selling assets with the worst performance.
"""
self.debug(f"Margin Call Triggered at {self.time}. Processing custom liquidation logic.")
# 1. Identify which symbols are in the default request list
# (These are the positions LEAN thinks we should close)
# 2. Sort the requests based on Unrealized Profit Percentage (Ascending)
# We want to sell the positions with the lowest (most negative) profit first.
# Logic: self.portfolio[symbol].unrealized_profit_percent
sorted_requests = sorted(
requests,
key=lambda x: self.portfolio[x.symbol].unrealized_profit_percent
)
# Log the order of liquidation for debugging
for req in sorted_requests:
pnl = self.portfolio[req.symbol].unrealized_profit_percent
self.log(f"Queuing liquidation for {req.symbol}. PnL: {pnl:.2%}")
# 3. Return the modified list.
# LEAN will execute these orders in the order provided until margin is satisfied.
return sorted_requests
def on_order_event(self, order_event):
if order_event.status == OrderStatus.FILLED:
self.debug(f"Order Filled: {order_event}")
Key Implementation Details
on_margin_callSignature: It must accept a list ofrequestsand return a list ofSubmitOrderRequest.SubmitOrderRequest: This object represents a potential order. It contains thesymbol,quantity, andorder_type.- Execution Flow: When you return the list
sorted_requests, the LEAN engine iterates through them and executes them one by one. It checks the margin status after every fill. If the margin requirement is met after the first order, the subsequent orders in the list might not be executed. This is why sorting the list effectively controls the priority of liquidation.
Q&A
Q: What happens if I return an empty list in on_margin_call?
A: If you return an empty list, no orders are executed immediately. However, the engine will detect that the margin requirement is still not met. Depending on the brokerage model, it may throw a runtime error or immediately trigger the margin call event again, potentially leading to an infinite loop or a crash. You must reduce leverage when this event fires.
Q: Can I change the quantity of the assets being sold?
A: Yes. You can modify the quantity property of the SubmitOrderRequest objects inside the list before returning them. For example, you could double the quantity to liquidate more than necessary, creating a "safety buffer" to prevent another margin call the next day.
Q: How is on_margin_call_warning different?
A: on_margin_call_warning is a notification that triggers when you have less than 5% margin remaining. It does not pass order requests and does not execute trades automatically. It is your chance to manually reduce leverage (e.g., self.liquidate()) before the forced liquidation of the actual on_margin_call occurs.