Asset Classes
Futures
Trades
To get historical trade data, call the history
method with the TradeBar
type and a security's Symbol
.
This method returns a DataFrame with columns for the open, high, low, close, and volume.
class FutureTradeBarHistoryAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 12, 19) # Get the Symbol of a security. future = self.add_future(Futures.Indices.SP_500_E_MINI) symbol = list(self.futures_chain(future.symbol))[0].symbol # Get the 5 trailing daily TradeBar objects of the security in DataFrame format. history = self.history(TradeBar, symbol, 5, Resolution.DAILY)
close | high | low | open | volume | |||
---|---|---|---|---|---|---|---|
expiry | symbol | time | |||||
2024-12-20 13:30:00 | ES YOGVNNAOI1OH | 2024-12-12 17:00:00 | 6058.75 | 6087.75 | 6055.50 | 6082.25 | 1152801.0 |
2024-12-13 17:00:00 | 6051.25 | 6084.75 | 6041.25 | 6077.25 | 1027682.0 | ||
2024-12-16 17:00:00 | 6076.50 | 6090.50 | 6064.75 | 6072.50 | 378263.0 | ||
2024-12-17 17:00:00 | 6052.00 | 6063.50 | 6040.75 | 6052.75 | 278531.0 | ||
2024-12-18 17:00:00 | 5872.25 | 6074.50 | 5840.00 | 6050.25 | 335048.0 |
# Calculate the daily returns. daily_returns = history.close.pct_change().iloc[1:]
expiry symbol time 2024-12-20 13:30:00 ES YOGVNNAOI1OH 2024-12-13 17:00:00 -0.001238 2024-12-16 17:00:00 0.004173 2024-12-17 17:00:00 -0.004032 2024-12-18 17:00:00 -0.029701 Name: close, dtype: float64
If you intend to use the data in the DataFrame to create TradeBar
objects, request that the history request returns the data type you need.
Otherwise, LEAN consumes unnecessary computational resources populating the DataFrame.
To get a list of TradeBar
objects instead of a DataFrame, call the history[TradeBar]
method.
# Get the 5 trailing daily TradeBar objects of the security in TradeBar format. history = self.history[TradeBar](symbol, 5, Resolution.DAILY) # Iterate through the TradeBar objects and access their volumes. for trade_bar in history: t = trade_bar.end_time volume = trade_bar.volume
Quotes
To get historical quote data, call the history
method with the QuoteBar
type and a security's Symbol
.
This method returns a DataFrame with columns for the open, high, low, close, and size of the bid and ask quotes.
The columns that don't start with "bid" or "ask" are the mean of the quote prices on both sides of the market.
class FutureQuoteBarHistoryAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 12, 19) # Get the Symbol of a security. future = self.add_future(Futures.Indices.SP_500_E_MINI) symbol = list(self.futures_chain(future.symbol))[0].symbol # Get the 5 trailing minute QuoteBar objects of the security in DataFrame format. history = self.history(QuoteBar, symbol, 5, Resolution.MINUTE)
askclose | askhigh | asklow | askopen | asksize | bidclose | bidhigh | bidlow | bidopen | bidsize | close | high | low | open | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
expiry | symbol | time | ||||||||||||||
2024-12-20 13:30:00 | ES YOGVNNAOI1OH | 2024-12-12 17:00:00 | 6058.75 | 6087.75 | 6055.75 | 6082.50 | 19.0 | 6058.50 | 6087.50 | 6055.50 | 6082.25 | 52.0 | 6058.625 | 6087.625 | 6055.625 | 6082.375 |
2024-12-13 17:00:00 | 6051.50 | 6085.00 | 6041.50 | 6077.50 | 59.0 | 6051.25 | 6084.75 | 6041.25 | 6077.25 | 1.0 | 6051.375 | 6084.875 | 6041.375 | 6077.375 | ||
2024-12-16 17:00:00 | 6076.75 | 6090.75 | 6064.75 | 6072.50 | 10.0 | 6076.50 | 6090.50 | 6064.50 | 6072.25 | 7.0 | 6076.625 | 6090.625 | 6064.625 | 6072.375 | ||
2024-12-17 17:00:00 | 6052.25 | 6063.50 | 6041.00 | 6053.00 | 12.0 | 6052.00 | 6063.25 | 6040.50 | 6052.75 | 1.0 | 6052.125 | 6063.375 | 6040.750 | 6052.875 | ||
2024-12-18 17:00:00 | 5872.75 | 6074.75 | 5840.25 | 6050.25 | 2.0 | 5872.25 | 6074.50 | 5840.00 | 6050.00 | 1.0 | 5872.500 | 6074.625 | 5840.125 | 6050.125 |
# Calculate the spread at each minute. spread = history.askclose - history.bidclose
expiry symbol time 2024-12-20 13:30:00 ES YOGVNNAOI1OH 2024-12-12 17:00:00 0.25 2024-12-13 17:00:00 0.25 2024-12-16 17:00:00 0.25 2024-12-17 17:00:00 0.25 2024-12-18 17:00:00 0.50 dtype: float64
If you intend to use the data in the DataFrame to create QuoteBar
objects, request that the history request returns the data type you need.
Otherwise, LEAN consumes unnecessary computational resources populating the DataFrame.
To get a list of QuoteBar
objects instead of a DataFrame, call the history[QuoteBar]
method.
# Get the 5 trailing minute QuoteBar objects of the security in QuoteBar format. history = self.history[QuoteBar](symbol, 5, Resolution.MINUTE) # Iterate through each QuoteBar and calculate the dollar volume on the bid. for quote_bar in history: t = quote_bar.end_time bid_dollar_volume = quote_bar.last_bid_size * quote_bar.bid.close
Ticks
To get historical tick data, call the history
method with a security's Symbol
and Resolution.TICK
.
This method returns a DataFrame that contains data on bids, asks, and last trade prices.
class FutureTickHistoryAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 12, 19) # Get the Symbol of a security. future = self.add_future(Futures.Indices.SP_500_E_MINI) symbol = list(self.futures_chain(future.symbol))[0].symbol # Get the trailing 2 days of ticks for the security in DataFrame format. history = self.history(symbol, timedelta(2), Resolution.TICK)
askprice | asksize | bidprice | bidsize | lastprice | openinterest | quantity | |||
---|---|---|---|---|---|---|---|---|---|
expiry | symbol | time | |||||||
2024-12-20 13:30:00 | ES YOGVNNAOI1OH | 2024-12-17 09:30:00.001 | NaN | NaN | NaN | NaN | 6052.75 | NaN | 1.0 |
2024-12-17 09:30:00.001 | 0.0 | 0.0 | 6052.75 | 33.0 | 6052.75 | NaN | 0.0 | ||
2024-12-17 09:30:00.001 | 6053.0 | 11.0 | 0.00 | 0.0 | 6053.00 | NaN | 0.0 | ||
2024-12-17 09:30:00.001 | 6053.0 | 10.0 | 0.00 | 0.0 | 6053.00 | NaN | 0.0 | ||
2024-12-17 09:30:00.001 | 6053.0 | 9.0 | 0.00 | 0.0 | 6053.00 | NaN | 0.0 |
# Select the rows in the DataFrame that represent trades. Drop the bid/ask columns since they are NaN. trade_ticks = history[history.quantity > 0].dropna(axis=1)
lastprice | quantity | |||
---|---|---|---|---|
expiry | symbol | time | ||
2024-12-20 13:30:00 | ES YOGVNNAOI1OH | 2024-12-17 09:30:00.001 | 6052.75 | 1.0 |
2024-12-17 09:30:00.002 | 6053.00 | 1.0 | ||
2024-12-17 09:30:00.002 | 6053.00 | 2.0 | ||
2024-12-17 09:30:00.003 | 6052.75 | 1.0 | ||
2024-12-17 09:30:00.003 | 6052.75 | 3.0 |
If you intend to use the data in the DataFrame to create Tick
objects, request that the history request returns the data type you need.
Otherwise, LEAN consumes unnecessary computational resources populating the DataFrame.
To get a list of Tick
objects instead of a DataFrame, call the history[Tick]
method.
# Get the trailing 2 days of ticks for the security in Tick format. history = self.history[Tick](symbol, timedelta(2), Resolution.TICK) # Iterate through each quote tick and calculate the quote size. for tick in history: if tick.tick_type == TickType.Quote: t = tick.end_time size = max(tick.bid_size, tick.ask_size)
Ticks are a sparse dataset, so request ticks over a trailing period of time or between start and end times.
Slices
To get historical Slice data, call the history
method without passing any Symbol
objects.
This method returns Slice
objects, which contain data points from all the datasets in your algorithm.
If you omit the resolution
argument, it uses the resolution that you set for each security and dataset when you created the subscriptions.
class SliceHistoryAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 12, 1) # Add some securities and datasets. future = self.add_future(Futures.Indices.SP_500_E_MINI) symbol = list(self.futures_chain(future.symbol))[0].symbol # Get the historical Slice objects over the last 5 days for all the subcriptions in your algorithm. history = self.history(5, Resolution.DAILY) # Iterate through each historical Slice. for slice_ in history: # Iterate through each TradeBar in this Slice. for symbol, trade_bar in slice_.bars.items(): close = trade_bar.close
Continuous Contract Prices
To get historical trade, quote, or tick data for the continuous contract, use the symbol
property of the Future
object when you make the history request.
class FutureContinuousContractHistoryAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 12, 19) # Add the Future and specify the contract rollover settings. future = self.add_future( Futures.Indices.SP_500_E_MINI, data_mapping_mode=DataMappingMode.OPEN_INTEREST, data_normalization_mode=DataNormalizationMode.BACKWARDS_RATIO, contract_depth_offset=0 ) # Get the 5 trailing daily TradeBar objects of the continuous contract. history = self.history(TradeBar, future.symbol, 5, Resolution.DAILY)
close | high | low | open | volume | |||
---|---|---|---|---|---|---|---|
expiry | symbol | time | |||||
1899-12-30 | /ES | 2024-12-12 17:00:00 | 6132.582256 | 6161.935651 | 6129.292651 | 6156.368628 | 1152801.0 |
2024-12-13 17:00:00 | 6124.990860 | 6158.899093 | 6114.869000 | 6151.307698 | 1027682.0 | ||
2024-12-16 17:00:00 | 6150.548558 | 6164.719163 | 6138.655372 | 6146.499814 | 378263.0 | ||
2024-12-17 17:00:00 | 6125.750000 | 6137.390140 | 6114.362907 | 6126.509140 | 278531.0 | ||
2024-12-18 17:00:00 | 5939.500000 | 6148.000000 | 5906.500000 | 6124.000000 | 2009987.0 |
# Calculate the daily returns. daily_returns = history.close.pct_change().iloc[1:]
expiry symbol time 1899-12-30 /ES 2024-12-13 17:00:00 -0.001238 2024-12-16 17:00:00 0.004173 2024-12-17 17:00:00 -0.004032 2024-12-18 17:00:00 -0.030404 Name: close, dtype: float64
Open Interest
To get historical open interest data, call the history
method with the OpenInterest
type and a security's Symbol
.
This method returns a DataFrame with a single column.
class FutureOpenInterestHistoryAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 12, 19) # Get the Symbol of a security. future = self.add_future(Futures.Indices.SP_500_E_MINI) symbol = list(self.futures_chain(future.symbol))[0].symbol # Get the 5 trailing daily OpenInterest objects of the security in DataFrame format. history = self.history(OpenInterest, symbol, 5, Resolution.DAILY)
openinterest | |||
---|---|---|---|
expiry | symbol | time | |
2024-12-20 13:30:00 | ES YOGVNNAOI1OH | 2024-12-14 19:00:00 | 1631020.0 |
2024-12-15 19:00:00 | 1625312.0 | ||
2024-12-16 19:00:00 | 965789.0 | ||
2024-12-17 19:00:00 | 687472.0 | ||
2024-12-18 19:00:00 | 556365.0 |
# Calculate the daily change in open interest. oi_delta = history.openinterest.diff().iloc[1:]
expiry symbol time 2024-12-20 13:30:00 ES YOGVNNAOI1OH 2024-12-15 19:00:00 -5708.0 2024-12-16 19:00:00 -659523.0 2024-12-17 19:00:00 -278317.0 2024-12-18 19:00:00 -131107.0 Name: openinterest, dtype: float64
If you intend to use the data in the DataFrame to create OpenInterest
objects, request that the history request returns the data type you need.
Otherwise, LEAN consumes unnecessary computational resources populating the DataFrame.
To get a list of OpenInterest
objects instead of a DataFrame, call the history[OpenInterest]
method.
# Get the 5 trailing daily OpenInterest objects of the security in OpenInterest format. history = self.history[OpenInterest](symbol, 5, Resolution.DAILY) # Iterate through the TradeBar objects and access their volumes. for oi in history: t = oi.end_time open_interest = oi.value
Contracts
To get historical Futures contracts, call the history
method with the FutureUniverse
type and the continuous contract Symbol
.
The data this method returns contains information on all the currently tradable contracts, not just the contracts that pass your filter.
If you pass flatten=True
, this method returns a DataFrame with columns for the data point attributes.
class FutureContractsHistoryAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 12, 19) # Add a Future. future = self.add_future(Futures.Indices.SP_500_E_MINI) # Get the daily contract data over the last two trading days. history = self.history(FutureUniverse, future.symbol, 2, flatten=True)
close | high | low | open | openinterest | value | volume | ||
---|---|---|---|---|---|---|---|---|
time | symbol | |||||||
2024-12-18 | ES YOGVNNAOI1OH | 6052.000 | 6063.500 | 6040.750 | 6052.750 | 687472.0 | 6052.000 | 278531.0 |
ES YQYHC5L1GPA9 | 6125.750 | 6138.250 | 6114.250 | 6127.500 | 1902280.0 | 6125.750 | 997849.0 | |
ES YTG30NVEFCW1 | 6185.250 | 6198.750 | 6173.500 | 6184.250 | 5044.0 | 6185.250 | 197.0 | |
ES YVXOP65RE0HT | 6238.250 | 6258.000 | 6229.375 | 6238.250 | 20.0 | 6238.250 | 0.0 | |
ES YYFADOG4CO3L | 6299.125 | 6313.000 | 6057.000 | 6071.500 | 1106.0 | 6299.125 | 0.0 | |
2024-12-19 | ES YOGVNNAOI1OH | 5872.250 | 6074.500 | 5840.000 | 6050.250 | 556365.0 | 5872.250 | 335048.0 |
ES YQYHC5L1GPA9 | 5939.500 | 6148.000 | 5906.500 | 6124.000 | 2064361.0 | 5939.500 | 2009987.0 | |
ES YTG30NVEFCW1 | 6001.250 | 6205.000 | 5966.000 | 6181.250 | 5096.0 | 6001.250 | 536.0 | |
ES YVXOP65RE0HT | 6054.250 | 6450.750 | 5934.375 | 6243.375 | 20.0 | 6054.250 | 0.0 | |
ES YYFADOG4CO3L | 6315.000 | 6315.000 | 6315.000 | 6315.000 | 1116.0 | 6315.000 | 10.0 | |
ES Z0WW26QHBBPD | 6171.375 | 6379.625 | 6142.000 | 6368.375 | 0.0 | 6171.375 | 0.0 |
# Select the contract with the largest open interest each day. most_oi = history.groupby('time').apply(lambda x: x.nlargest(1, 'openinterest')).reset_index(level=1, drop=True).openinterest
time symbol 2024-12-18 ES YQYHC5L1GPA9 1902280.0 2024-12-19 ES YQYHC5L1GPA9 2064361.0 Name: openinterest, dtype: float64
If you intend to use the data in the DataFrame to create FutureUniverse
objects, request that the history request returns the data type you need.
Otherwise, LEAN consumes unnecessary computational resources populating the DataFrame.
To get a list of FutureUniverse
objects instead of a DataFrame, call the history[FutureUniverse]
method.
# Get the 5 trailing daily FutureUniverse objects in FutureUniverse format. history = self.history[FutureUniverse](future.symbol, 5, Resolution.DAILY) # Iterate through the FutureUniverse objects and access their volumes. for future_universe in history: t = future_universe.end_time most_oi = sorted(future_universe, key=lambda contract: contract.open_interest)[-1]
Contango
To get historical contango data for VX Futures, call the history
method with the dataset Symbol
.
This method returns a DataFrame that contains the data point attributes.
class VIXContangoHistoryAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 12, 19) # Add the dataset and save a reference to its Symbol. symbol = self.add_data(VIXCentralContango, "VIX", Resolution.DAILY).symbol # Get the trailing 7 days of contango data in DataFrame format. history = self.history(dataset_symbol, 7, Resolution.DAILY)
contango_f2_minus_f1 | contango_f7_minus_f4 | contango_f7_minus_f4_div_3 | f1 | f2 | f3 | f4 | f5 | f6 | f7 | f8 | frontmonth | period | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
symbol | time | |||||||||||||
VIX.VIXCentralContango | 2024-12-13 | 0.1314 | 0.0498 | 0.0166 | 14.4062 | 16.2991 | 17.1553 | 17.9005 | 18.2460 | 18.5583 | 18.7923 | 18.9500 | 12 | 1 days |
2024-12-14 | 0.1212 | 0.0497 | 0.0166 | 14.5190 | 16.2782 | 17.1250 | 17.9474 | 18.2993 | 18.5750 | 18.8390 | 19.0500 | 12 | 1 days | |
2024-12-17 | 0.1299 | 0.0457 | 0.0152 | 14.6997 | 16.6091 | 17.4592 | 18.1963 | 18.4965 | 18.7411 | 19.0282 | 19.1884 | 12 | 1 days | |
2024-12-18 | 0.0765 | 0.0389 | 0.0130 | 15.7483 | 16.9531 | 17.6666 | 18.2998 | 18.4996 | 18.7185 | 19.0108 | 19.1500 | 12 | 1 days |
# Calculate the changes in contango. contango_diff = history.f1.diff().iloc[1:]
symbol time VIX.VIXCentralContango 2024-12-14 0.1128 2024-12-17 0.1807 2024-12-18 1.0486 Name: f1, dtype: float64
If you intend to use the data in the DataFrame to create VIXCentralContango
objects, request that the history request returns the data type you need.
Otherwise, LEAN consumes unnecessary computational resources populating the DataFrame.
To get a list of dataset objects instead of a DataFrame, call the history[VIXCentralContango]
method.
# Get the trailing 7 days of contango data in VIXCentralContango format. history = self.history[VIXCentralContango](symbol, 7, Resolution.DAILY) # Iterate through each VIXCentralContango object. for data_point in history: t = data_point.end_time value = data_point.contango_f_2_minus_f_1
Indicators
To get historical indicator values, call the indicator_history
method with an indicator and the security's Symbol
.
class FuturesIndicatorHistoryAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 12, 19) # Get the Symbol of a security. symbol = self.add_future(Futures.Indices.SP_500_E_MINI).symbol # Get the 21-day SMA values of the security for the last 5 trading days. history = self.indicator_history(SimpleMovingAverage(21), symbol, 5, Resolution.DAILY)
To organize the data into a DataFrame, use the data_frame
property of the result.
# Organize the historical indicator data into a DataFrame to enable pandas wrangling. history_df = history.data_frame
current | rollingsum | |
---|---|---|
2024-12-12 17:00:00 | 6150.577121 | 129162.119546 |
2024-12-13 17:00:00 | 6154.322490 | 129240.772299 |
2024-12-16 17:00:00 | 6162.919815 | 129421.316116 |
2024-12-17 17:00:00 | 6169.413410 | 129557.681602 |
2024-12-18 17:00:00 | 6166.050570 | 129487.061970 |
# Get the maximum of the SMA values. sma_max = history_df.current.max()
The indicator_history
method resets your indicator, makes a history request, and updates the indicator with the historical data.
Just like with regular history requests, the indicator_history
method supports time periods based on a trailing number of bars, a trailing period of time, or a defined period of time.
If you don't provide a resolution
argument, it defaults to match the resolution of the security subscription.
To make the indicator_history
method update the indicator with an alternative price field instead of the close (or mid-price) of each bar, pass a selector
argument.
# Get the historical values of an indicator over the last 30 days, applying the indicator to the security's volume. history = self.indicator_history(indicator, symbol, timedelta(30), selector=Field.VOLUME)
Some indicators require the prices of two securities to compute their value (for example, Beta).
In this case, pass a list of the Symbol
objects to the method.
class FuturesMultiAssetIndicatorHistoryAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 12, 19) # Add the target and reference securities. target_symbol = self.add_future(Futures.Metals.GOLD).symbol reference_symbol = self.add_future(Futures.Indices.SP_500_E_MINI).symbol # Create a 21-period Beta indicator. beta = Beta("", target_symbol, reference_symbol, 21) # Get the historical values of the indicator over the last 10 trading days. history = self.indicator_history(beta, [target_symbol, reference_symbol], 10, Resolution.DAILY) # Get the average Beta value. beta_avg = history.data_frame.mean()
Examples
The following examples demonstrate some common practices for trading Futures with historical data.
Example 1: Stock-Gold All-Weather Portfolio
The following algorithm constructs a monthly rebalance risk-parity portfolio using S&P 500 EMini and Micro Gold Future contracts. A risk Parity portfolio can dissipate the risk of each crypto pair to reduce overall drawdown. The position sizing can be optimized by 1-year historical daily return of the universe members.
from Portfolio.RiskParityPortfolioOptimizer import RiskParityPortfolioOptimizer class CryptoHistoricalDataAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 1, 1) self.set_end_date(2025, 1, 1) self.set_cash(100000000) # Select the future contracts representing various asset classes to diversify the portfolio. self._futures = [self.add_future( x, fill_forward=True, extended_market_hours=True, data_normalization_mode=DataNormalizationMode.BACKWARDS_RATIO, data_mapping_mode=DataMappingMode.OPEN_INTEREST, contract_depth_offset=0 ) for x in [Futures.Indices.SP_500_E_MINI, Futures.Metals.MICRO_GOLD]] # Instantiate the optimizer to perform risk-parity optimization. # Risk Parity portfolio can dissipate the risk of each crypto pair to reduce overall drawdown. self._optimizer = RiskParityPortfolioOptimizer(0.0, 1.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. history = self.history([x.symbol for x in self._futures], 253, Resolution.DAILY, extended_market_hours=True, fill_forward=True)\ .droplevel([0]).unstack(0).close.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(future.mapped, size) for future, size in zip(self._futures, weights)] self.set_holdings(targets, liquidate_existing_holdings=True)