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

Asset Classes

Future Options

Introduction

This page explains how to get historical data for Future Options. Some of the data you can get include prices and indicator data.

Trades

To get historical trade data, call the History<TradeBar> method with a security's Symbol.

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.

public class FutureOptionsTradeBarHistoryAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        SetStartDate(2024, 12, 19);
        // Add a FOP universe.
        var future = AddFuture(Futures.Indices.SP500EMini);
        future.SetFilter(universe => universe.FrontMonth());
        AddFutureOption(future.Symbol, universe => universe.FrontMonth().Strikes(-1, 0).CallsOnly());
    }

    // Get trailing data whenever a new FOP contract enters the universe.
    public override void OnSecuritiesChanged(SecurityChanges changes)
    {
        foreach (var security in changes.AddedSecurities)
        {
            if (security.Type == SecurityType.FutureOption)
            {
                // Get the 3 trailing daily TradeBar objects of the security. 
                var history = History<TradeBar>(security.Symbol, 3, Resolution.Daily);
                // Iterate through each TradeBar and calculate its dollar volume.
                foreach (var bar in history)
                {
                    var t = bar.EndTime;
                    var dollarVolume = bar.Close * bar.Volume;
                }
            }
        }
    }
}
class FutureOptionsTradeBarHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        # Add a FOP universe.
        future = self.add_future(Futures.Indices.SP_500_E_MINI)
        future.set_filter(lambda universe: universe.front_month())
        self.add_future_option(future.symbol, lambda universe: universe.front_month().strikes(-1, 0).calls_only())

    # Get trailing data whenever a new FOP contract enters the universe.
    def on_securities_changed(self, changes):
        for security in changes.added_securities:
            if security.type == SecurityType.FUTURE_OPTION:
                # Get the 3 trailing daily TradeBar objects of the security in DataFrame format. 
                history = self.history(TradeBar, security.symbol, 3, Resolution.DAILY)
closehighlowopenvolume
expirystriketypesymboltime
2024-12-205870.01ES 32NKVT5YYX1US|ES YOGVNNAOI1OH2024-12-16 17:00:002.152.151.451.80227.0
2024-12-17 17:00:002.353.052.302.3014.0
2024-12-18 17:00:0038.7543.751.751.85399.0
# Calculate the daily returns.
daily_returns = history.close.pct_change().iloc[1:]
expiry      strike  type  symbol                            time               
2024-12-20  5870.0  1     ES 32NKVT5YYX1US|ES YOGVNNAOI1OH  2024-12-17 17:00:00     0.093023
                                                            2024-12-18 17:00:00    15.489362
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 3 trailing daily TradeBar objects of the security in TradeBar format. 
history = self.history[TradeBar](symbol, 3, 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

Request minute, hour, or daily resolution data. Otherwise, the history request won't return any data.

Quotes

To get historical quote data, call the History<QuoteBar> method with a security's Symbol.

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.

public class FutureOptionsQuoteBarHistoryAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        SetStartDate(2024, 12, 19);
        // Add a FOP universe.
        var future = AddFuture(Futures.Indices.SP500EMini);
        future.SetFilter(universe => universe.FrontMonth());
        AddFutureOption(future.Symbol, universe => universe.FrontMonth().Strikes(-1, 0).CallsOnly());
    }

    // Get trailing data whenever a new FOP contract enters the universe.
    public override void OnSecuritiesChanged(SecurityChanges changes)
    {
        foreach (var security in changes.AddedSecurities)
        {
            if (security.Type == SecurityType.FutureOption)
            {
                // Get the 3 trailing daily QuoteBar objects of the security. 
                var history = History<QuoteBar>(security.Symbol, 3, Resolution.Daily);
                // Iterate through the QuoteBar objects and calculate the spread.
                foreach (var bar in history)
                {
                    var t = bar.EndTime;
                    var spread = bar.Ask.Close - bar.Bid.Close;
                }
            }
        }
    }
}
class FutureOptionsQuoteBarHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        # Add a FOP universe.
        future = self.add_future(Futures.Indices.SP_500_E_MINI)
        future.set_filter(lambda universe: universe.front_month())
        self.add_future_option(future.symbol, lambda universe: universe.front_month().strikes(-1, 0).calls_only())

    # Get trailing data whenever a new FOP contract enters the universe.
    def on_securities_changed(self, changes):
        for security in changes.added_securities:
            if security.type == SecurityType.FUTURE_OPTION:
                # Get the 3 trailing daily QuoteBar objects of the security in DataFrame format. 
                history = self.history(QuoteBar, security.symbol, 3, Resolution.DAILY)
askcloseaskhighasklowaskopenasksizebidclosebidhighbidlowbidopenbidsizeclosehighlowopen
expirystriketypesymboltime
2024-12-205870.01ES 32NKVT5YYX1US|ES YOGVNNAOI1OH2024-12-16 17:00:002.52.551.451.852.02.352.401.351.7546.02.4252.4751.41.800
2024-12-17 17:00:002.73.152.153.05179.02.453.052.052.9046.02.5753.1002.12.975
2024-12-18 17:00:0040.0215.501.552.303.036.5060.500.052.152.038.250138.0000.82.225
# Calculate the spread.
spread = history.askclose - history.bidclose
expiry      strike  type  symbol                            time               
2024-12-20  5870.0  1     ES 32NKVT5YYX1US|ES YOGVNNAOI1OH  2024-12-16 17:00:00    0.15
                                                            2024-12-17 17:00:00    0.25
                                                            2024-12-18 17:00:00    3.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 3 trailing daily QuoteBar objects of the security in QuoteBar format. 
history = self.history[QuoteBar](symbol, 3, Resolution.DAILY)
# 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

Request minute, hour, or daily resolution data. Otherwise, the history request won't return any data.

Slices

To get historical Slice data, call the Historyhistory 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.

public class SliceHistoryAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        SetStartDate(2024, 12, 19);
        // Add some securities and datasets.
        var future = AddFuture(Futures.Indices.SP500EMini);
        future.SetFilter(universe => universe.FrontMonth());
        AddFutureOption(future.Symbol, universe => universe.FrontMonth().Strikes(-1, 0).CallsOnly());
        // Add a Scheduled Event that runs at the start of each month.
        Schedule.On(DateRules.MonthStart(future.Symbol), TimeRules.AfterMarketOpen(future.Symbol, 60), Trade);
    }

    public void Trade()
    {
        // Get the historical Slice objects over the last 30 minutes for all the subcriptions in your algorithm.
        var history = History(30, Resolution.Minute);
        // Iterate through each historical Slice.
        foreach (var slice in history)
        {
            // Iterate through each TradeBar in this Slice.
            foreach (var kvp in slice.Bars)
            {
                var symbol = kvp.Key;
                var bar = kvp.Value;
            }
        }
    }
}
class SliceHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        # Add some securities and datasets.
        future = self.add_future(Futures.Indices.SP_500_E_MINI)
        future.set_filter(lambda universe: universe.front_month())
        self.add_future_option(future.symbol, lambda universe: universe.front_month().strikes(-1, 0).calls_only())
        # Add a Scheduled Event that runs at the start of each month.
        self.schedule.on(self.date_rules.month_start(future.symbol), self.time_rules.after_market_open(future.symbol, 60), self._trade)

    def _trade(self):
        # Get the historical Slice objects over the last 30 minutes for all the subcriptions in your algorithm.
        for slice_ in self.history(30, Resolution.MINUTE):
            # Iterate through each TradeBar in this Slice.
            for symbol, trade_bar in slice_.bars.items():
                close = trade_bar.close

Indicators

To get historical indicator values, call the IndicatorHistoryindicator_history method with an indicator and the security's Symbol.

public class FutureOptionIndicatorHistoryAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        SetStartDate(2024, 12, 19);
        // Add a FOP universe.
        var future = AddFuture(Futures.Indices.SP500EMini);
        future.SetFilter(universe => universe.FrontMonth());
        AddFutureOption(future.Symbol, universe => universe.FrontMonth().Strikes(0, 0).CallsOnly());
    }

    public override void OnSecuritiesChanged(SecurityChanges changes)
    {
        foreach (var security in changes.AddedSecurities)
        {
            if (security.Type != SecurityType.FutureOption)
            {
                continue;
            }
            // Get the 21-day SMA values of the contract for the last 5 trading days. 
            var history = IndicatorHistory(new SimpleMovingAverage(21), security.Symbol, 5, Resolution.Daily);
            // Get the maximum of the SMA values.
            var maxSMA = history.Max(indicatorDataPoint => indicatorDataPoint.Current.Value);
        }
    }
}
class FutureOptionIndicatorHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        # Add a FOP universe.
        future = self.add_future(Futures.Indices.SP_500_E_MINI)
        future.set_filter(lambda universe: universe.front_month())
        self.add_future_option(future.symbol, lambda universe: universe.front_month().strikes(0, 0).calls_only())

    def on_securities_changed(self, changes):
        for security in changes.added_securities:
            if security.type != SecurityType.FUTURE_OPTION:
                continue
            # Get the 21-day SMA values of the contract for the last 5 trading days. 
            history = self.indicator_history(SimpleMovingAverage(21), security.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
currentrollingsum
2024-12-12 17:00:00131.4047622759.500
2024-12-13 17:00:00132.1011902774.125
2024-12-16 17:00:00135.7797622851.375
2024-12-17 17:00:00138.0476192899.000
2024-12-18 17:00:00134.0952382816.000
# Get the maximum of the SMA values.
sma_max = history_df.current.max()

The IndicatorHistoryindicator_history method resets your indicator, makes a history request, and updates the indicator with the historical data. Just like with regular history requests, the IndicatorHistoryindicator_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 IndicatorHistoryindicator_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 contract's volume.
var history = IndicatorHistory(indicator, symbol, TimeSpan.FromDays(30), selector: Field.Volume);
# Get the historical values of an indicator over the last 30 days, applying the indicator to the contract's volume.
history = self.indicator_history(indicator, symbol, timedelta(30), selector=Field.VOLUME)

Some indicators require the prices of multiple securities to compute their value (for example, the indicators for the Greeks and implied volatility). In this case, pass a list of the Symbol objects to the method.

public class FutureOptionMultiAssetIndicatorHistoryAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        SetStartDate(2024, 12, 19);
        // Add a FOP universe.
        var future = AddFuture(Futures.Indices.SP500EMini);
        future.SetFilter(universe => universe.FrontMonth());
        AddFutureOption(future.Symbol, universe => universe.FrontMonth().Strikes(0, 0).CallsOnly());
    }

    public override void OnSecuritiesChanged(SecurityChanges changes)
    {
        foreach (var security in changes.AddedSecurities)
        {
            if (security.Type != SecurityType.FutureOption)
            {
                continue;
            }
            var option = security.Symbol;
            // Get the Symbol of the mirror contract.
            var mirror = QuantConnect.Symbol.CreateOption(
                option.Underlying, option.ID.Market, option.ID.OptionStyle,
                option.ID.OptionRight == OptionRight.Put ? OptionRight.Call : OptionRight.Put,
                option.ID.StrikePrice, option.ID.Date
            );
            // Create the indicator.
            var indicator = new ImpliedVolatility(option, RiskFreeInterestRateModel, new ConstantDividendYieldModel(0), mirror, OptionPricingModelType.ForwardTree);
            // Get the historical values of the indicator over the last 10 trading minutes.
            var history = IndicatorHistory(indicator, new[] {option.Underlying, option, mirror}, 10, Resolution.Minute);
            // Get the average IV value.
            var avgIV = history.Average(indicatorDataPoint => indicatorDataPoint.Current.Value);
        }
    }
}
class FutureOptionMultiAssetIndicatorHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        # Add a FOP universe.
        future = self.add_future(Futures.Indices.SP_500_E_MINI)
        future.set_filter(lambda universe: universe.front_month())
        self.add_future_option(future.symbol, lambda universe: universe.front_month().strikes(0, 0).calls_only())

    def on_securities_changed(self, changes):
        for security in changes.added_securities:
            if security.type != SecurityType.FUTURE_OPTION:
                continue           
            option = security.symbol
            # Get the Symbol of the mirror contract.
            mirror = Symbol.create_option(
                option.underlying, option.id.market, option.id.option_style, 
                OptionRight.Call if option.id.option_right == OptionRight.PUT else OptionRight.PUT,
                option.id.strike_price, option.id.date
            )
            # Create the indicator.
            indicator = ImpliedVolatility(
                option, self.risk_free_interest_rate_model, ConstantDividendYieldModel(0),
                mirror, OptionPricingModelType.FORWARD_TREE
            )
            # Get the historical values of the indicator over the last 10 minutes.
            history = self.indicator_history(indicator, [option.underlying, option, mirror], 10, Resolution.MINUTE)
            # Get the average IV value.
            iv_avg = history.data_frame.current.mean()

If you already have a list of Slice objects, you can pass them to the IndicatorHistoryindicator_history method to avoid the internal history request.

var slices = History(new[] {symbol}, 30, Resolution.Daily);
var history = IndicatorHistory(indicator, slices);

Examples

The following examples demonstrate some common practices for trading Future Options with historical data.

Example 1: Trend Following on Front Month ATM Option Contract

This algorithm strategically trades the front month S&P 500 EMini Future Options by analyzing bid and ask volumes shortly after the market opens. Using scheduled events, it effectively executes trades based on historical quote data, optimizing decision-making. The algorithm aims for timely entries and exits, ensuring efficient capital management and quick adaptability in volatile markets.

public class FutureOptionsTradingAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        SetStartDate(2024, 1, 1);
        SetEndDate(2024, 12, 31); 
        SetCash(100000);
        SetSecurityInitializer(new BrokerageModelSecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices)));

        // Request ES option data for trading and signal generation.
        var future = AddFuture(Futures.Indices.SP500EMini);
        future.SetFilter(universe => universe.FrontMonth());
        // We are interested in ATM front-month options since they are the most popular.
        AddFutureOption(future.Symbol, universe => universe.Expiration(0, 90).Strikes(-3, 3));

        // Schedule event to enter and exit option contract position.
        Schedule.On(DateRules.EveryDay(future.Symbol), TimeRules.AfterMarketOpen(future.Symbol, 16), TradeOption);
        Schedule.On(DateRules.EveryDay(future.Symbol), TimeRules.BeforeMarketClose(future.Symbol, 15), () => Liquidate());
    }

    public void TradeOption()
    {
        // Get the option chain to trade.
        foreach (var chain in CurrentSlice.OptionChains.Values)
        {
            foreach (var option in chain)
            {
                // Request historical quote data for signal generation.
                var history = History<QuoteBar>(option.Symbol, 15, Resolution.Minute);
                // Calculate the total bid and ask for dollar volume to determine the capital directional force.
                var totalBidVolume = 0m;
                var totalAskVolume = 0m;
                foreach (var bar in history)
                {
                    if (bar.Bid != null)
                    {
                        totalBidVolume += bar.Bid.Close * bar.LastBidSize;
                    }
                    if (bar.Ask != null)
                    {
                        totalAskVolume += bar.Ask.Close * bar.LastAskSize;
                    }
                }

                // Follow the capital flow to trade.
                if (totalBidVolume > totalAskVolume)
                {
                    MarketOrder(option.Symbol, 1);
                }
                else
                {
                    MarketOrder(option.Symbol, -1);
                }
            }
        }
    }
}
class FutureOptionsTradingAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2024, 1, 1)
        self.set_end_date(2024, 12, 31)  # Limit to a single day for 0DTE
        self.set_cash(100000)  # Starting cash

        # Request ES option data for trading and signal generation.
        future = self.add_future(Futures.Indices.SP_500_E_MINI)
        future.set_filter(lambda universe: universe.front_month())
        # We are interested in ATM front-month options since they are the most popular.
        self.add_future_option(future.symbol, lambda u: u.expiration(0, 90).strikes(-3, 3))
        
        # Schedule event to enter and exit option contract position.
        self.schedule.on(self.date_rules.every_day(future.symbol), self.time_rules.after_market_open(future.symbol, 16), self.trade_option)
        self.schedule.on(self.date_rules.every_day(future.symbol), self.time_rules.before_market_close(future.symbol, 15), self.liquidate)

    def trade_option(self) -> None:
        # Get the option chain to trade.
        for option_chain in self.current_slice.option_chains.values():
            for option in option_chain:
                # Request historical quote data for signal generation.
                history = self.history(QuoteBar, option.symbol, 15, Resolution.Minute)
                if not history.empty:
                    # Calculate the total bid and ask for dollar volume to determine the capital directional force.
                    total_bid_volume = 0
                    total_ask_volume = 0
                    if 'bidclose' in history:
                        total_bid_volume += (history['bidclose'] * history['bidsize']).sum()
                    if 'askclose' in history:
                        total_ask_volume += (history['askclose'] * history['asksize']).sum()

                    # Follow the capital flow to trade.
                    if total_bid_volume > total_ask_volume:
                        self.market_order(option.symbol, 1)
                    else:
                        self.market_order(option.symbol, -1)

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: