🚀 AI One-Click Generation of quantconnect Strategy Code

Automated Margin Call Handling for Your Trading Algorithm

quantconnect | Published on: 2025/11/27
The following content is generated by EasyQuant .

Problem Description

How can I make my trading bot automatically reduce its position size to avoid a margin call?

Solution

To automatically reduce your position size to avoid a margin call in QuantConnect, you should utilize two specific event handlers provided by the LEAN engine:

  1. on_margin_call_warning: This is your proactive defense. It triggers when your remaining margin drops below 5%. You can use this to liquidate a portion of your portfolio to free up buying power before a forced liquidation occurs.
  2. on_margin_call: This is your reactive defense. It triggers when a margin call is actually issued. By default, LEAN liquidates positions to satisfy the margin requirement. You can override this to control which assets are sold (e.g., selling losing positions first).

Strategy Implementation

The following algorithm demonstrates how to implement both handlers. It simulates a high-leverage scenario to demonstrate how the bot reacts by reducing positions.

# region imports
from AlgorithmImports import *
# endregion

class MarginManagementAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2020, 1, 1)
        self.set_end_date(2021, 1, 1)
        self.set_cash(100000)

        # Add a volatile asset and a stable asset
        self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol
        self.tsla = self.add_equity("TSLA", Resolution.MINUTE).symbol

        # Set leverage high to increase the probability of margin issues for demonstration
        self.securities[self.spy].set_leverage(2.0)
        self.securities[self.tsla].set_leverage(2.0)

        # Schedule a buy event
        self.schedule.on(self.date_rules.every_day(self.spy),
                         self.time_rules.after_market_open(self.spy, 30),
                         self.rebalance)

    def rebalance(self):
        # Intentionally use high leverage (near 100% of buying power)
        if not self.portfolio.invested:
            self.set_holdings([
                PortfolioTarget(self.spy, 0.95), 
                PortfolioTarget(self.tsla, 0.95)
            ])

    def on_data(self, data: Slice):
        # Standard data processing
        pass

    def on_margin_call_warning(self):
        """
        Triggered when there is less than 5% margin remaining.
        We proactively reduce leverage here to avoid the actual margin call.
        """
        self.log(f"MARGIN WARNING at {self.time}: Margin remaining < 5%. Reducing positions.")

        # Strategy: Liquidate 10% of all invested positions to free up cash
        for symbol, holding in self.portfolio.items():
            if holding.invested:
                # Calculate 10% of the current quantity
                quantity_to_reduce = int(holding.quantity * 0.10)
                
                # Ensure we don't submit an order for 0 shares
                if quantity_to_reduce != 0:
                    self.market_order(symbol, -quantity_to_reduce)
                    self.log(f"Proactive Reduction: Sold {quantity_to_reduce} of {symbol}")

    def on_margin_call(self, requests: List[SubmitOrderRequest]) -> List[SubmitOrderRequest]:
        """
        Triggered when the portfolio has crossed the margin threshold.
        LEAN generates a list of 'requests' (orders) to satisfy the margin call.
        We can modify this list to choose WHICH assets to sell.
        """
        self.log(f"MARGIN CALL at {self.time}: Executing forced liquidation logic.")

        # Strategy: Sort the liquidation requests to sell the positions with the 
        # highest Unrealized Loss first (Stop the bleeding).
        
        # Helper function to get unrealized profit percent
        def get_unrealized_profit(request):
            if request.symbol in self.portfolio:
                return self.portfolio[request.symbol].unrealized_profit_percent
            return 0

        # Sort requests: Lowest profit (highest loss) first
        sorted_requests = sorted(requests, key=get_unrealized_profit)

        # Log our decision
        for order in sorted_requests:
            self.log(f"Margin Call Liquidation Priority: {order.symbol}")

        # Return the sorted list. LEAN will execute these orders until margin is satisfied.
        return sorted_requests

Key Components Explained

  1. on_margin_call_warning Logic:

    • This method checks self.portfolio.items() to find invested securities.
    • It calculates 10% of the current quantity (holding.quantity * 0.10) and places a market_order to close that portion.
    • Benefit: This often prevents the broker (or the engine) from forcefully closing your entire position at an unfavorable price.
  2. on_margin_call Logic:

    • This method receives requests, which is a list of SubmitOrderRequest objects that the LEAN engine has calculated are necessary to fix the margin deficit.
    • We do not create new orders here; instead, we sort the existing requests.
    • In the example, sorted(requests, key=get_unrealized_profit) ensures that the algorithm sells the assets performing the worst first.
    • Benefit: This allows you to keep your winning trades open while sacrificing losing trades to satisfy margin requirements.

Important Considerations

  • Execution Order: on_margin_call orders are executed synchronously immediately after the method returns.
  • Buying Power: Ensure you understand the specific BuyingPowerModel of the securities you are trading (e.g., PatternDayTradingMarginModel vs SecurityMarginModel), as this dictates when these events are triggered.
  • Live Trading: In live trading, brokerage margin calls can be severe. The on_margin_call_warning is a soft warning generated by LEAN's internal tracking, not necessarily a message from the broker. It relies on LEAN's data matching the broker's state accurately.

Q&A

Q: Can I use on_margin_call_warning to deposit virtual cash in a backtest?
A: No, you cannot inject cash during a backtest to solve a margin call. You must reduce holdings. However, in live trading, you could theoretically use the on_margin_call_warning to send an alert (email/SMS) to yourself to deposit funds, though the algorithm itself must still handle the immediate risk.

Q: What happens if I return an empty list in on_margin_call?
A: If you return an empty list or do not satisfy the margin requirement, LEAN will likely throw a runtime error or forcefully liquidate positions in subsequent steps to protect the brokerage model integrity. You must return enough orders to satisfy the margin debt.

Q: Does on_margin_call_warning work for crypto futures?
A: Yes, provided the BuyingPowerModel is correctly set for the crypto future. However, crypto markets are highly volatile; the gap between a 5% warning and liquidation can happen in milliseconds. It is safer to manage position sizing strictly in on_data rather than relying solely on the warning handler.