🚀 AI One-Click Generation of quantconnect Strategy Code

How to Calculate Percentage Returns from Historical Data in QuantConnect

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

Problem Description

What's the easiest way to calculate daily returns for a stock from its price history?

Solution

The easiest way to calculate daily returns in QuantConnect depends on whether you need the data for real-time trading logic (streaming) or for historical analysis (batch processing).

Method 1: The ROC Indicator (Easiest for Real-Time)

For live trading or event-driven backtesting, the Rate Of Change (ROC) indicator is the most efficient method. It automatically updates with every new data point and handles the calculation logic for you.

  • How it works: It calculates the percentage change between the current price and the price $n$ periods ago.
  • Note: The ROC indicator returns values as a percentage (e.g., 1.5 for 1.5%), not a decimal.

Method 2: Pandas pct_change (Easiest for History/Warmup)

If you need to analyze a block of historical data (e.g., during initialize or for complex vector math), requesting a History DataFrame and using the Pandas pct_change() function is the standard approach.

Complete Strategy Example

The following code demonstrates both methods within a fully functional algorithm.

from AlgorithmImports import *

class DailyReturnsCalculator(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 1, 1)
        self.set_end_date(2023, 6, 1)
        self.set_cash(100000)

        # 1. Add the security
        self.symbol = self.add_equity("SPY", Resolution.DAILY).symbol

        # ------------------------------------------------------------
        # METHOD 1: Using the ROC (Rate of Change) Indicator
        # ------------------------------------------------------------
        # Arguments: Symbol, Period (1 day), Resolution
        # This will automatically calculate the 1-day percentage change.
        self.daily_roc = self.ROC(self.symbol, 1, Resolution.DAILY)

        # Optional: Warm up the indicator so it is ready immediately
        self.set_warm_up(1, Resolution.DAILY)

    def on_data(self, data: Slice):
        # Ensure data exists for the symbol
        if not data.contains_key(self.symbol) or data[self.symbol] is None:
            return

        # ------------------------------------------------------------
        # EXECUTION: Method 1 (Indicator)
        # ------------------------------------------------------------
        # Check if the indicator has enough data
        if self.daily_roc.is_ready:
            # .current.value returns the % change (e.g., 1.5 for +1.5%)
            indicator_return = self.daily_roc.current.value
            self.plot("Daily Returns", "Indicator (ROC)", indicator_return)

        # ------------------------------------------------------------
        # EXECUTION: Method 2 (Pandas History)
        # ------------------------------------------------------------
        # Request the last 2 days of data to calculate the change between them
        history = self.history(self.symbol, 2, Resolution.DAILY)
        
        if not history.empty and 'close' in history.columns and len(history) == 2:
            # pct_change() calculates the decimal change (e.g., 0.015 for +1.5%)
            # We take the last value [.iloc[-1]] which represents the most recent return
            pandas_return = history['close'].pct_change().iloc[-1]
            
            # Convert to percentage to match ROC for plotting
            self.plot("Daily Returns", "Pandas (pct_change)", pandas_return * 100)

            # Simple logic: Buy if return is positive, Liquidate if negative
            if pandas_return > 0:
                self.set_holdings(self.symbol, 1.0)
            else:
                self.liquidate(self.symbol)

Key Differences

Feature ROC Indicator Pandas pct_change()
Best For Live trading signals, on_data logic Initialization, complex math, vectorization
Speed Very fast (incremental updates) Slower (re-fetches data every time)
Output Percentage (e.g., 1.5) Decimal (e.g., 0.015)
Maintenance Automatic (LEAN engine updates it) Manual (You must call self.history)

Q&A

Q: Why does the ROC indicator return a different format than Pandas?
A: The QuantConnect ROC indicator is designed to return a "Rate of Change" scaled by 100 (Percentage), whereas the Pandas pct_change() function returns the raw decimal fraction. If you need the decimal value from the indicator, simply divide self.daily_roc.current.value by 100.

Q: Can I calculate log returns instead of simple returns?
A: Yes, but you must use the Pandas method or a custom indicator. For Pandas:

import numpy as np
history = self.history(self.symbol, 2, Resolution.DAILY)
log_returns = np.log(history['close'] / history['close'].shift(1))

Q: How do I handle dividends in return calculations?
A: By default, QuantConnect provides adjusted data. This means the prices in self.history and the inputs to self.ROC are already adjusted for splits and dividends, so the return calculation represents the Total Return. If you need raw price returns, you must change the data normalization mode:

self.securities[self.symbol].set_data_normalization_mode(DataNormalizationMode.RAW)