🚀 AI One-Click Generation of quantconnect Strategy Code

How to Use Pandas `unstack()` to Reshape Multi-Symbol History Data in QuantConnect

quantconnect | Published on: 2025/12/4
The following content is generated by EasyQuant .

Problem Description

How can I organize historical data for multiple symbols into columns for each symbol?

Solution

To organize historical data for multiple symbols into columns (where each column represents a specific symbol), you need to manipulate the pandas DataFrame returned by the self.history() method.

By default, self.history() returns a MultiIndex DataFrame where the indices are usually (Symbol, Time). To rearrange this so that Time is the index and Symbols are the columns, you must select the specific data column you want (e.g., 'close') and use the pandas .unstack() method.

Implementation Guide

Here is a complete, runnable algorithm demonstrating how to request history for multiple symbols and format the output into a clean DataFrame with symbols as columns.

# region imports
from AlgorithmImports import *
# endregion

class HistoryColumnOrganization(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 1, 1)
        self.set_end_date(2023, 2, 1)
        self.set_cash(100000)
        
        # 1. Define the list of tickers
        tickers = ["SPY", "AAPL", "MSFT", "GOOG"]
        
        # 2. Add equities and save their Symbol objects
        # We need the Symbol objects for the history request
        self.symbols = [self.add_equity(ticker, Resolution.DAILY).symbol for ticker in tickers]

    def on_end_of_algorithm(self):
        # 3. Request Historical Data
        # We request the last 30 days of daily data for all symbols in our list
        history_df = self.history(self.symbols, 30, Resolution.DAILY)

        if history_df.empty:
            self.log("History DataFrame is empty.")
            return

        # 4. Organize into Columns
        # The raw history_df has a MultiIndex: [symbol, time]
        # We select the 'close' column and unstack level 0 (the symbol level)
        # This pivots the table so Symbols become columns and Time remains the index
        close_prices = history_df['close'].unstack(level=0)

        # Example: Handling missing data (optional)
        # forward fill to propagate last known price, then drop remaining NaNs
        close_prices = close_prices.ffill().dropna()

        # Logging the result to demonstrate structure
        self.log(f"\nFormatted DataFrame Head:\n{close_prices.head()}")
        
        # Example of accessing specific column data
        spy_data = close_prices[self.symbols[0]] # Access by Symbol object
        # OR
        # spy_data = close_prices["SPY"] # Access by string (if unstack resulted in string columns)
        
        self.log(f"Latest SPY Close: {spy_data.iloc[-1]}")

Key Steps Explained

  1. self.history(self.symbols, ...): This fetches the data. The result is a pandas DataFrame stacked by Symbol and Time.
  2. ['close']: You must isolate a specific data property (Open, High, Low, Close, Volume) before unstacking. You cannot unstack the entire DataFrame at once if you want a single 2D matrix of prices.
  3. .unstack(level=0): This is the critical pandas command.
    • In QuantConnect, the MultiIndex level 0 is the Symbol.
    • Unstacking level 0 moves the Symbols from the index to the columns.
  4. Result: You get a DataFrame where:
    • Index: datetime objects.
    • Columns: Symbol objects (or ticker strings, depending on how the request was made, but usually Symbol objects in QC).
    • Values: The price data.

Q&A

Q: Why do I get a "Duplicate Index" error when unstacking?
A: This usually happens if you request data that includes multiple data types (e.g., TradeBars and QuoteBars) or if the resolution results in duplicate timestamps for the same symbol. Ensure you are selecting a specific column (like .close) before unstacking.

Q: Can I get Open, High, Low, and Close in columns simultaneously?
A: Not in a single 2D DataFrame. A 2D DataFrame can only represent 2 dimensions (Time vs Symbol). If you need OHLC for all symbols, you would either keep the MultiIndex format or create a dictionary of DataFrames (e.g., {'open': open_df, 'close': close_df}).

Q: How do I handle symbols with different start dates (NaN values)?
A: When you unstack, pandas will insert NaN for timestamps where a specific symbol has no data. You can use .fillna(method='ffill') to forward fill previous prices or .dropna() to remove rows where any symbol is missing data.

Q: Does this work for Minute resolution?
A: Yes. You simply change Resolution.DAILY to Resolution.MINUTE in the add_equity and history calls. Be aware that requesting large amounts of minute data for many symbols can be memory-intensive.