Problem Description
Solution
To automatically sell a stock when it is removed from your universe in QuantConnect, you should utilize the on_securities_changed event handler. This method is triggered whenever the universe selection logic updates (e.g., daily at midnight for coarse selection).
Here is the specific logic required:
- Override the
on_securities_changedmethod. - Iterate through the
changes.removed_securitieslist. - Check if your portfolio currently holds the security (
security.invested). - Call
self.liquidate()to close the position.
Implementation Example
Below is a complete, runnable algorithm demonstrating this behavior. It uses a Coarse Universe to select the top 5 liquid stocks. When the composition of the top 5 changes, the algorithm automatically liquidates the stocks that fell off the list.
# region imports
from AlgorithmImports import *
# endregion
class UniverseRemovalLiquidation(QCAlgorithm):
def initialize(self):
self.set_start_date(2023, 1, 1)
self.set_end_date(2023, 6, 1)
self.set_cash(100000)
# Add a Coarse Selection Universe to dynamically select stocks
self.add_universe(self.coarse_selection_function)
self.universe_settings.resolution = Resolution.DAILY
def coarse_selection_function(self, coarse):
# Select top 5 stocks by Dollar Volume
sorted_by_dollar_volume = sorted(coarse, key=lambda x: x.dollar_volume, reverse=True)
return [x.symbol for x in sorted_by_dollar_volume[:5]]
def on_data(self, data: Slice):
# Simple entry logic: Invest in active securities if not already invested
# This is just to populate the portfolio so we have something to liquidate later
for security in self.active_securities.values():
if not security.invested and security.symbol in data:
self.set_holdings(security.symbol, 0.1)
def on_securities_changed(self, changes: SecurityChanges):
# 1. Handle Added Securities (Optional logging)
for security in changes.added_securities:
self.log(f"Added to Universe: {security.symbol}")
# 2. Handle Removed Securities (The logic you requested)
for security in changes.removed_securities:
# Check if we currently have a position
if security.invested:
self.log(f"Removed from Universe: {security.symbol} - Liquidating position.")
# Liquidate the specific symbol.
# You can pass a tag to the order for clarity in the logs.
self.liquidate(security.symbol, tag="Universe Removal")
Key Components Explained
changes.removed_securities: This property contains a list ofSecurityobjects that were in the universe during the previous step but are no longer selected by your universe function.security.invested: It is best practice to check this boolean before sending an order. It prevents sending unnecessary "sell" orders for stocks you were tracking but didn't actually own.self.liquidate(symbol): This helper method automatically calculates the quantity needed to close your position (whether long or short) and sends a market order to flatten the position.
Q&A: Universe Management in QuantConnect
Q: Does on_securities_changed run before or after on_data?
A: In the LEAN engine event flow, on_securities_changed runs before on_data. This ensures that your universe is updated and any removal logic (like liquidation) is processed before the algorithm attempts to generate new entry signals for the current time step.
Q: Will liquidate work if I have open limit orders on that stock?
A: self.liquidate() cancels all open orders for the specific symbol first, and then submits a market order to close the remaining position. This ensures you don't end up with a "stuck" position due to pending orders.
Q: Can I use this for Manual Universes?
A: Yes. If you manually add securities using self.add_equity() and later remove them using self.remove_security(), the on_securities_changed event will still fire, adding the removed stock to the removed_securities list.
Q: What happens to the data subscription when a security is removed?
A: When a security is removed from the universe, LEAN automatically removes the data subscription for that asset unless you still hold a position or have open orders. However, once you liquidate the position inside on_securities_changed, the data subscription will be terminated shortly after to save resources.