Time Modeling

Timeslices

Introduction

The core technology behind QuantConnect algorithmic trading is an event-based, streaming analysis system called LEAN. LEAN attempts to model the stream of time as accurately as possible, presenting data ("events") to your algorithms in the order it arrives, as you would experience in reality.

All QuantConnect algorithms have this time-stream baked in as the primary event handler, OnDataon_data. The Slice object this method receives represents all of the data at a moment of time, a time-slice. No matter what data you request, you receive it in the order created according to simulated algorithm time. By only letting your algorithm see the present and past moments, we can prevent the most common quantitative-analysis error, look-ahead bias.

Time Frontier

If you request data for multiple securities and multiple resolutions, it can create a situation where one of your data subscriptions is ready to emit, but another subscription with a longer period is still be constructing its bar. Furthermore, if you request multiple datasets that have different time zones, your algorithm will receive the daily bars of each dataset at market close for the respective asset. To coordinate the data in these situations, we use the EndTimeend_time of each data point to signal when LEAN should transmit it to your algorithm.

Time frontier as backtest progress

Once your algorithm reaches the EndTimeend_time of a data point, LEAN sends the data to your OnDataon_data method. For intraday bars, this is the beginning of the next period. For daily bars, it's market close or midnight, depending on your DailyPreciseEndTimedaily_precise_end_time setting. To avoid look-ahead bias, you can only access data from before this Time Frontier. The Timetime property of your algorithm is always equal to the Time Frontier.

Properties

Slice objects have the following properties:

Get Time Slices

To get the current Slice object, define an OnDataon_data method or use the CurrentSlicecurrent_slice property of your algorithm. The Slice contains all the data for a given moment in time. The Barsbars and QuoteBarsquote_bars properties are Symbol/string indexed dictionaries. The Ticksticks property is a list of ticks for that moment of time, indexed by the Symbol. To check which data formats are available for each asset class, see the Data Formats page in the Asset Classes chapter.

The Slice object gives you the following ways to access your data:

  • Indexing the Slice, which returns a dynamic object of your type.
  • # Get the current dynamic object for the specified symbol by indexing the Slice object.
    def on_data(self, slice: Slice) -> None:
        data = slice[self._symbol]
    // Get the current dynamic object for the specified symbol by indexing the Slice object.
    public override void OnData(Slice slice)
    {
        var data = slice[_symbol];
    }

    With minute and second resolution data, the dynamic type is TradeBar for Equities and QuoteBar for other asset classes.

  • Indexing the static properties, which returns the type you specify.
  • # Get the current trade and quote bars for a specific symbol from the data slice.
    def on_data(self, slice: Slice) -> None:
        trade_bar = slice.bars[self._symbol]
        quote_bar = slice.quote_bars[self._symbol]
    // Get the current trade and quote bars for a specific symbol from the data slice.
    public override void OnData(Slice slice)
    {
        var tradeBars = slice.Bars[_symbol];
        var quoteBars = slice.QuoteBars[_symbol];
    }
  • Calling the Get<T>() helper method.
  • // Get trade bar, quote bar, and ticks for the specified symbol from the data slice.
    public override void OnData(Slice slice)
    {
        var tradeBar = slice.Get<TradeBar>(_symbol);
        var quoteBar = slice.Get<QuoteBar>(_symbol);
        var ticks = slice.Get<Ticks>(_symbol);
    }

Strongly typed access gives you compile-time safety, but dynamic type access can sometimes simplify coding. We recommend static types since they are easier to debug.

Check if the Slice contains the data you're looking for before you index it. If there is little trading, or you are in the same time loop as when you added the security, it may not have any data. Even if you enabled fill-forward for a security subscription, you should check if the data exists in the dictionary before you try to index it. To check if the Slice contains for a security, call the ContainsKeycontains_key method. Note: if the Slice object doesn't contain any market data but it contains auxiliary data, the slice.ContainsKey(symbol)slice.contains_key(symbol) method can return true while slice[symbol] returns Nonenull.

# Check if the slice contains data for the symbol and retrieve it if it's available.
def on_data(self, slice: Slice) -> None:
    if slice.contains_key(self._symbol) and slice[self._symbol]:
        data = slice[self._symbol]
// Check if the slice contains data for the symbol and retrieve it if it's available.
public override void OnData(Slice slice)
{
    if (slice.ContainsKey(_symbol))
    {
        var data = slice[_symbol];
    }
}

Examples

The following examples demonstrate some common practices for timeslices time modeling.

Example 1: 6-Hour Consolidated Bars

public class TimeslicesTimeModelingAlgorithm : QCAlgorithm
{
    private Symbol _aapl;
    private TradeBarConsolidator _consolidator;

    public override void Initialize()
    {
        SetStartDate(2020, 1, 1);
        SetEndDate(2021, 1, 1);

        // Request AAPL data to trade it. We need a resolution denser than 6-hour for the consolidator.
        _aapl = AddEquity("AAPL", Resolution.Hour).Symbol;
            
        // Create a 6-hour consolidator for smoothing the noise.
        _consolidator = new TradeBarConsolidator(TimeSpan.FromHours(6));
        // Subscribe the consolidator to update with the security's data automatically.
        SubscriptionManager.AddConsolidator(_aapl, _consolidator);
    }
    
    public override void OnData(Slice slice)
    {
        if (slice.Bars.TryGetValue(_aapl, out var bar) && _consolidator.WorkingBar != null)
        {
            // Trade on a rising trend, suggested by the current close is above the past hour open
            // and the past hour open is above the 6-hour bar open.
            if (_consolidator.WorkingBar.Open < bar.Open && bar.Open < bar.Close)
            {
                SetHoldings(_aapl, 1m);
            }
            // Trade on a down trend, suggested by the current close is below the past hour open
            // and the past hour open is below the 6-hour bar open.
            else if (_consolidator.WorkingBar.Open > bar.Open && bar.Open > bar.Close)
            {
                SetHoldings(_aapl, -1m);
            }
            // Otherwise, do not hold a position if there is no deterministic trend.
            else
            {
                Liquidate();
            }
        }
    }
}
class TimeslicesTimeModelingAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2020, 1, 1)
        self.set_end_date(2021, 1, 1)
        
        # Request AAPL data to trade it. We need a resolution denser than 6-hour for the consolidator.
        self.aapl = self.add_equity("AAPL", Resolution.Hour).symbol

        # Create a 6-hour consolidator for smoothing the noise.
        self.consolidator = TradeBarConsolidator(timedelta(hours=6))
        # Subscribe the consolidator to update with the security's data automatically.
        self.subscription_manager.add_consolidator(self.aapl, self.consolidator)

    def on_data(self, slice: Slice) -> None:
        bar = slice.bars.get(self.aapl)
        if bar and self.consolidator.working_bar is not None:
            # Trade on a rising trend, suggested by the current close is above the past hour open
            # and the past hour open is above the 6-hour bar open.
            if self.consolidator.working_bar.open < bar.open < bar.close:
                self.set_holdings(self.aapl, 1)
            # Trade on a down trend, suggested by the current close is below the past hour open
            # and the past hour open is below the 6-hour bar open.
            elif self.consolidator.working_bar.open > bar.open > bar.close:
                self.set_holdings(self.aapl, -1)
            # Otherwise, do not hold a position if there is no deterministic trend.
            else:
                self.liquidate()

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: