# Handling Securities Changes and Updating Trading Universes Dynamically in QuantConnect

> Dynamically update trading universes and handle securities changes in QuantConnect. Learn to combine AddUniverse, OnSecuritiesChanged, and rebalancing flags for efficient portfolio management.

- Repository: [Papers With Backtest/awesome-systematic-trading](https://github.com/paperswithbacktest/awesome-systematic-trading)
- Tags: how-to-guide
- Published: 2026-07-31

---

**You can handle securities changes and update trading universes dynamically in QuantConnect by combining `AddUniverse` with coarse/fine selection filters, the `OnSecuritiesChanged` event handler for security-level initialization, and a scheduled flag to control monthly rebalancing cycles.**

The *awesome-systematic-trading* repository demonstrates production-grade systematic strategies using QuantConnect's `QCAlgorithm` framework. Handling securities changes and updating trading universes dynamically is critical for factor-based strategies that must rotate holdings based on liquidity screens, market-cap filters, and momentum or value rankings.

## The Three-Pillar Pattern for Dynamic Universe Management

The repository implements a consistent architectural pattern across every strategy file. This pattern separates universe selection from security initialization and trade execution through three distinct mechanisms.

### Pillar 1: Universe Registration with Coarse and Fine Selection

The `AddUniverse` method registers a two-stage filter that runs on a schedule. In [`value-book-to-market-factor.py`](https://github.com/paperswithbacktest/awesome-systematic-trading/blob/main/value-book-to-market-factor.py), the algorithm calls `self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)` during initialization to establish a monthly refresh cycle.

The **coarse selector** filters the initial universe by liquidity metrics (price thresholds and dollar volume), while the **fine selector** applies fundamental data filters (exchange listing, market-cap ranks) and computes factor-specific scores. This separation prevents expensive fundamental data lookups on illiquid symbols.

### Pillar 2: The `OnSecuritiesChanged` Lifecycle Hook

When the universe adds or removes symbols, QuantConnect triggers `OnSecuritiesChanged(self, changes)`. According to the implementation in [`trend-following-effect-in-stocks.py`](https://github.com/paperswithbacktest/awesome-systematic-trading/blob/main/trend-following-effect-in-stocks.py), this method performs one-time initialization for every security entering the universe.

The typical implementation iterates over `changes.AddedSecurities` to attach custom fee models, set leverage limits, and instantiate technical indicators. For example, the trend-following strategy initializes an ATR indicator for each new symbol to calculate position sizing volatility adjustments.

### Pillar 3: Scheduled Selection Flags to Minimize Churn

To avoid daily universe churn and unnecessary computation, strategies use a boolean `selection_flag` controlled by a monthly `Schedule.On` event. As shown in [`momentum-factor-effect-in-stocks.py`](https://github.com/paperswithbacktest/awesome-systematic-trading/blob/main/momentum-factor-effect-in-stocks.py), the coarse selector returns `Universe.Unchanged` when the flag is `False`, effectively pausing the selection pipeline until the next rebalance date.

This flag-based gating ensures that `OnData` only executes trades immediately following a universe refresh, maintaining clean separation between market data handling and portfolio rebalancing.

## Step-by-Step Implementation Guide

The following workflow demonstrates how these components integrate into a complete trading system.

### Initialize the Universe Framework

During `Initialize`, set the universe parameters and schedule the monthly selection trigger:

```python
def Initialize(self):
    self.SetStartDate(2020, 1, 1)
    self.SetCash(100000)
    
    # Universe parameters

    self.coarse_count = 100
    self.selection_flag = False
    
    # Reference symbol for scheduling

    self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
    
    # Register universe with coarse and fine selectors

    self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
    
    # Schedule monthly universe refresh

    self.Schedule.On(self.DateRules.MonthEnd(self.symbol),
                    self.TimeRules.AfterMarketOpen(self.symbol, 5),
                    self.Selection)
                    
def Selection(self):
    self.selection_flag = True

```

### Implement Coarse Selection Logic

The coarse function filters by liquidity and checks the selection flag to determine if a full refresh should run:

```python
def CoarseSelectionFunction(self, coarse):
    # Prevent unnecessary churn on non-selection days

    if not self.selection_flag:
        return Universe.Unchanged
    
    # Filter for price > $5 and available fundamental data

    selected = [x for x in coarse if x.HasFundamentalData and x.Price > 5]
    
    # Sort by dollar volume and take top N

    selected = sorted(selected, key=lambda x: x.DollarVolume, reverse=True)
    return [x.Symbol for x in selected[:self.coarse_count]]

```

### Apply Fine Fundamental Filters

The fine selection function applies strategy-specific ranking based on fundamental data:

```python
def FineSelectionFunction(self, fine):
    # Filter by exchange and market cap

    filtered = [x for x in fine if x.MarketCap > 1e9]
    
    # Example: Value factor ranking by P/B ratio

    sorted_by_pb = sorted(filtered, key=lambda x: x.ValuationRatios.PBRatio)
    
    # Select bottom quartile (value) and top quartile (growth) for long/short

    count = len(sorted_by_pb)
    self.long = [x.Symbol for x in sorted_by_pb[:count//4]]
    self.short = [x.Symbol for x in sorted_by_pb[-count//4:]]
    
    return self.long + self.short

```

### Configure Security-Level Settings

Handle initialization for newly added securities using the change event:

```python
def OnSecuritiesChanged(self, changes):
    for security in changes.AddedSecurities:
        # Set transaction cost model

        security.SetFeeModel(CustomFeeModel())
        
        # Configure leverage per strategy requirements

        security.SetLeverage(5)
        
        # Initialize per-security indicators (e.g., ATR for sizing)

        if security.Symbol not in self.atr:
            self.atr[security.Symbol] = self.ATR(
                security.Symbol, 
                10, 
                Resolution.Daily
            )
    
    # Optional: Clean up removed securities

    for security in changes.RemovedSecurities:
        if security.Symbol in self.atr:
            del self.atr[security.Symbol]

```

### Execute Trades and Reset State

Process trades in `OnData` only when the selection flag indicates a fresh universe:

```python
def OnData(self, data):
    if not self.selection_flag:
        return
    
    # Reset flag immediately to prevent duplicate execution

    self.selection_flag = False
    
    # Liquidate symbols no longer in universe

    for symbol in self.Portfolio.Keys:
        if symbol not in self.long + self.short:
            if self.Portfolio[symbol].Invested:
                self.Liquidate(symbol)
    
    # Set holdings for long positions

    for symbol in self.long:
        if symbol in data:
            weight = 1.0 / len(self.long)
            self.SetHoldings(symbol, weight)
    
    # Set holdings for short positions

    for symbol in self.short:
        if symbol in data:
            weight = -1.0 / len(self.short)
            self.SetHoldings(symbol, weight)
    
    # Clear temporary storage

    self.long.clear()
    self.short.clear()

```

## Key Design Benefits

This architecture provides specific advantages for systematic trading strategies:

- **Low Turnover**: Returning `Universe.Unchanged` on non-selection days eliminates data subscription churn and reduces transaction costs.
- **Separation of Concerns**: Coarse selection handles liquidity screens, fine selection handles factor logic, and `OnSecuritiesChanged` centralizes security-specific configuration.
- **Extensibility**: Adding new factors only requires modifying the `FineSelectionFunction` ranking logic; the universe management scaffolding remains unchanged.

## Summary

- **Register dynamic universes** using `AddUniverse` with separate coarse and fine selection functions to handle liquidity pre-filtering and fundamental ranking.
- **Handle security lifecycle events** through `OnSecuritiesChanged` to set fee models, leverage, and initialize indicators like ATR as shown in [`trend-following-effect-in-stocks.py`](https://github.com/paperswithbacktest/awesome-systematic-trading/blob/main/trend-following-effect-in-stocks.py).
- **Control rebalancing frequency** with a `selection_flag` toggled by `Schedule.On` monthly events, returning `Universe.Unchanged` on non-rebalance days.
- **Execute trades atomically** in `OnData` immediately following universe refreshes, then reset state to prevent drift.

## Frequently Asked Questions

### How does `OnSecuritiesChanged` differ from manual symbol addition?

`OnSecuritiesChanged` is an event-driven callback that QuantConnect invokes automatically whenever the universe adds or removes symbols. Unlike manual `AddEquity` calls, this method provides a `SecurityChanges` object containing `AddedSecurities` and `RemovedSecurities` collections, allowing batch initialization of fee models and indicators for all new entries simultaneously.

### Why return `Universe.Unchanged` instead of an empty list?

Returning `Universe.Unchanged` signals the engine to maintain the current universe composition without unsubscribing from data feeds. Returning an empty list would remove all securities and cancel all subscriptions, causing unnecessary transaction costs and data re-subscription delays when the same symbols re-enter the universe on the next cycle.

### Where should I initialize custom indicators for universe members?

Initialize custom indicators inside `OnSecuritiesChanged` within the loop over `changes.AddedSecurities`, as demonstrated in [`trend-following-effect-in-stocks.py`](https://github.com/paperswithbacktest/awesome-systematic-trading/blob/main/trend-following-effect-in-stocks.py). This ensures indicators only exist for actively traded securities and prevents memory overhead from symbols that fail fine-fundamental filters.

### Can I modify leverage per security dynamically?

Yes. Within `OnSecuritiesChanged`, call `security.SetLeverage(target_leverage)` for each added security. The repository shows this pattern in [`value-book-to-market-factor.py`](https://github.com/paperswithbacktest/awesome-systematic-trading/blob/main/value-book-to-market-factor.py) where leverage is set based on strategy-specific risk parameters, allowing different leverage limits for long versus short positions or volatility-adjusted sizing.