book
Checkout our new book! Hands on AI Trading with Python, QuantConnect, and AWS Learn More arrow

Historical Data

History Requests

Introduction

There are two ways to request historical data in your algorithms: direct historical data requests and indirect algorithm warm up. You can use a direct historical data request at any time throughout your algorithm. It returns all of the data you request as a single object.

Trailing Data Samples

To get historical data for a trailing time period, call history method with an an integer and a Resolution. For example, if you pass an asset Symbol, 5, and Resolution.MINUTE as the arguments, it returns the data of the asset during the most recent 5 minutes in the asset's market hours. These trailing minute bars can cross multiple trading days.

Select Language:
class TrailingDataSamplesHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        # Get the Symbol of an asset.
        symbol = self.add_equity('SPY').symbol
        # Get the minute-resolution TradeBar data of the asset over the trailing 5 minutes.
        history = self.history(TradeBar, symbol, 5, Resolution.MINUTE)
closehighlowopenvolume
symboltime
SPY2024-12-18 15:56:00588.63590.390588.57590.391053414.0
2024-12-18 15:57:00588.34588.910588.24588.61930643.0
2024-12-18 15:58:00588.11588.460588.07588.341138812.0
2024-12-18 15:59:00587.92588.325587.70588.101576391.0
2024-12-18 16:00:00586.28587.940585.89587.925865463.0

If you don't pass a Resolution, it defaults to the resolution of the security subscription. The following example returns 3 days of data for QQQ and 3 minutes of data for SPY:

Select Language:
class TrailingDataSamplesForMulitpleAssetsHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 20)
        # Add two assets with different resolutions.
        spy = self.add_equity('SPY', Resolution.MINUTE).symbol
        qqq = self.add_equity('QQQ', Resolution.DAILY).symbol
        # Get the trailing 3 bars of data for each asset.
        history = self.history(TradeBar, [spy, qqq], 3)
closehighlowopenvolume
symboltime
QQQ2024-12-17 16:00:00535.80537.49534.130536.41025048673.0
2024-12-18 16:00:00516.47536.87515.010535.11047016560.0
2024-12-19 16:00:00514.17521.75513.830521.11042192908.0
SPY2024-12-19 15:58:00586.40586.77586.365586.730872817.0
2024-12-19 15:59:00586.50586.83586.380586.4201524079.0
2024-12-19 16:00:00586.10586.53585.850586.4954342748.0

If there is no data for the time period you request, the result has fewer samples. For instance, say an illiquid asset has no trading activity during the last 15 minutes of the trading day and you request the 10 most recent minute bars at market close, 4 PM Eastern Standard Time (ET). In this case, you won't get any data because LEAN will try to fetch data from 3:50 PM ET to 4 PM ET since the market was open during that time, but there were no trades for the asset. For more information about missing data points, see Missing Data Points.

Trailing Time Periods

To get historical data for a trailing time period, pass a timedelta to the history method. The days of the timedelta represent calendar days.

Select Language:
class TrailingTimePeriodHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        # Get the Symbol of an asset.
        symbol = self.add_equity('SPY').symbol
        # Get the minute-resolution TradeBar data of the asset over the trailing 3 days.
        history = self.history(TradeBar, symbol, timedelta(3), Resolution.MINUTE)
closehighlowopenvolume
symboltime
SPY2024-12-16 09:31:00606.02606.400605.89606.00612650.0
2024-12-16 09:32:00606.01606.305605.94605.99132785.0
..................
2024-12-18 15:59:00587.92588.325587.70588.101576391.0
2024-12-18 16:00:00586.28587.940585.89587.925865463.0

If there is no data in the time period, the result is empty. For more information about missing data points, see Missing Data Points.

Date Ranges

To get historical data for a specific date range, call history method with start and end datetime objects. The datetime objects you provide are based in the algorithm time zone.

Select Language:
class DateRangeHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 1)
        # Get the Symbol of an asset.
        symbol = self.add_equity('SPY').symbol
        # Get the daily-resolution TradeBar data of the asset during 2020.
        history = self.history(TradeBar, symbol, datetime(2020, 1, 1), datetime(2021, 1, 1), Resolution.DAILY)
closehighlowopenvolume
symboltime
SPY2020-01-02 16:00:00301.194352301.194352299.029520299.99836352757344.0
2020-01-03 16:00:00298.913630300.053991297.708370297.78254065558742.0
2020-01-06 16:00:00300.053991300.137432297.013028297.08719847199709.0
2020-01-07 16:00:00299.210309299.956643298.756019299.47917537979987.0
2020-01-08 16:00:00300.804960302.038033299.163953299.45136162265971.0
..................
2020-12-24 13:00:00348.496921348.496921347.033045347.60915122897100.0
2020-12-28 16:00:00351.490783351.887446350.451904351.15078634765681.0
2020-12-29 16:00:00350.820234353.219101350.225239353.07743648584327.0
2020-12-30 16:00:00351.320785352.350220350.933566351.68911546030043.0
2020-12-31 16:00:00353.105769353.825429350.612458351.18856458258603.0

If there is no data for the date range you request, the result is empty. For more information about missing data points, see Missing Data Points.

Flat Universe DataFrames

Most history requests return a flat DataFrame by default, where there is one column for each data point attribute. Universe history requests return a Series, where the values in the Series are lists of the universe data objects. For example, the following code snippet returns a Series where each value is a List[ETFConstituentUniverse]:

class SeriesUniverseHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        # Add an ETF constituents universe for SPY so you can get its historical data.
        universe = self.add_universe(self.universe.etf('SPY'))
        # Get the trailing 5 days of universe data.
        history = self.history(universe, 5, Resolution.DAILY)
Pandas series where each value is a list of ETFConstituentsUniverse objects.

To get the data into a DataFrame instead, set the flatten argument to True. In this case, the DataFrame has one column for each data point attribute.

# Get the trailing 5 days of universe data in DataFrame format
# so you can perform DataFrame wrangling operations.
history = self.history(universe, 5, Resolution.DAILY, flatten=True)
lastupdateperiodsharesheldweight
timesymbol
2024-12-13A RPTMYV3VC57P2024-12-111 days3667879.00.000799
AAPL R735QTJ8XC9X2024-12-111 days190881735.00.072612
ABBV VCY032R250MD2024-12-111 days22169383.00.005966
ABNB XK8H247DY6W52024-12-111 days5515085.00.001182
ABT R735QTJ8XC9X2024-12-111 days21833687.00.003846
..................
2024-12-19XYL V18KR26TE3XH2024-12-171 days3065334.00.000572
YUM R735QTJ8XC9X2024-12-171 days3548979.00.000739
ZMH S6ZZPKTVDY052024-12-171 days2570208.00.000429
ZBRA R735QTJ8XC9X2024-12-171 days650402.00.000397
ZTS VDRJHVQ4FNFP2024-12-171 days5703558.00.001505

Default Values

The following table describes the assumptions of the History API:

ArgumentAssumption
resolutionLEAN guesses the resolution you request by looking at the securities you already have in your algorithm. If you have a security subscription in your algorithm with a matching Symbol, the history request uses the same resolution as the subscription. If you don't have a security subscription in your algorithm with a matching Symbol, Resolution.MINUTE is the default.

Additional Options

The history method accepts the following additional arguments:

ArgumentData TypeDescriptionDefault Value
fill_forwardbool/NoneTypeTrue to fill forward missing data. Otherwise, false. If you don't provide a value, it uses the fill forward mode of the security subscription.None
extended_market_hoursbool/NoneTypeTrue to include extended market hours data. Otherwise, false.None
data_mapping_modeDataMappingMode/NoneTypeThe contract mapping mode to use for the security history request.None
data_normalization_modeDataNormalizationMode/NoneTypeThe price scaling mode to use for US Equities or continuous Futures contracts. If you don't provide a value, it uses the data normalization mode of the security subscription.None
contract_depth_offsetint/NoneTypeThe desired offset from the current front month for continuous Futures contracts.None
Select Language:
self.future = self.add_future(Futures.Indices.SP_500_E_MINI)
history = self.history(
    symbols=[self.future.symbol], 
    start=self.time - timedelta(days=15), 
    end=self.time, 
    resolution=Resolution.MINUTE, 
    fill_forward=False, 
    extended_market_hours=False, 
    data_mapping_mode=DataMappingMode.LAST_TRADING_DAY, 
    data_normalization_mode=DataNormalizationMode.RAW, 
    contract_depth_offset=0
)

Examples

The following examples demonstrate some common practices for trading using historical requests.

Example 1: Mean-Variance Portfolio

The following algorithm constructs a monthly rebalance mean-variance portfolio using the top 20 liquid equities. The position sizing can be optimized by 1-year historical daily return of the universe members.

Select Language:
from Portfolio.MaximumSharpeRatioPortfolioOptimizer import MaximumSharpeRatioPortfolioOptimizer

class HistoricalRequestAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2020, 4, 1)
        self.set_end_date(2020, 9, 30)
        
        # Monthly renewal of the top 20 liquid universe to trade popular stocks.
        self.universe_settings.schedule.on(self.date_rules.month_start())
        self._universe = self.add_universe(self.universe.dollar_volume.top(20))

        # Instantiate the optimizer to perform mean-variance optimization.
        # Mean-variance optimization will not consider a risk-free rate, so we use 0.
        self._optimizer = MaximumSharpeRatioPortfolioOptimizer(0.0, 1.0, 0.0)

        # Set a scheduled event to rebalance the portfolio at the start of every month.
        self.schedule.on(
            self.date_rules.month_start(), 
            self.time_rules.at(9, 31),
            self.rebalance
        )

    def rebalance(self) -> None:
        # Historical data request to get 1-year data for optimization.
        symbols = self._universe.selected
        history = self.history(symbols, 253, Resolution.DAILY).close.unstack(0).dropna()
        # Daily return on the universe members to calculate the optimized weights.
        returns = history.pct_change().dropna()

        # Calculate the optimized weights.
        weights = self._optimizer.optimize(returns)

        # Rebalance the portfolio according to the optimized weights.
        targets = [PortfolioTarget(symbol, size) for symbol, size in zip(symbols, weights)]
        self.set_holdings(targets, liquidate_existing_holdings=True)

Other Examples

For more examples, see the following algorithms:

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: