Time Modeling

Periods

Introduction

Data comes in two different "shapes" according to the time period it covers: point values or period values. In QuantConnect, ticks are point values, and bars are a period values. These data formats have different properties, which control when LEAN emits them into your algorithm.

Start time vs end time for tick and bar data

Start and End Time

Bars have a start and end time that represent the time period of which the bar aggregates data. LEAN passes the bar to your algorithm at the end time so that you don't receive the bar before it was actually available. Free online data providers commonly timestamp their bars to the start time of the bar and include the bar close price, making your research prone to look ahead bias.

Carefully consider the end-time of data to avoid one-off errors when you import the data into QC or compare indicator values across different platforms. Generally, bar timestamps can be represented as follows: bar.EndTime = bar.Time + bar.Periodbar.end_time = bar.time + bar.period.

Period Values

Bars aggregate data across a period of time into a single object. We make this easy for you by pre-aggregating billions of raw trade-ticks into TradeBar objects and quote-ticks into QuoteBar objects.

We don't know the close of an intraday bar until the start of the next bar, which can sometimes be confusing. For daily bars, the bar will include all the ticks from market open to market close, but LEAN will emit it to your algorithm at market close. As a result, if you analyze the Friday data and then place an order, LEAN sends the order to your brokerage on Friday after the market close.

Time of receiving bar data via OnData handler

When there are no ticks during a period, LEAN emits the previous bar. This is the default behavior for bar data and it's referred to as "filling the data forward". You can enable and disable this setting when you create the security subscription.

We provide bar data in second, minute, hour, and daily bar formats. To create other periods of bars, see Consolidating Data.

Daily Periods

LEAN emits daily bars at market close or midnight, depending on your DailyPreciseEndTimedaily_precise_end_time setting. To be notified when a security has finished trading for the day, add an OnEndOfDayon_end_of_day method to your algorithm. LEAN calls this method for each security every day.

# Perform end-of-day logic for a given symbol.
def on_end_of_day(self, symbol: Symbol) -> None:
    pass
// Perform end-of-day logic for a given symbol.
public override void OnEndOfDay(Symbol symbol)
{
    
}

Point Values

Point values have no period because they occur at a singular point in time. Examples of point data includes ticks, open interest, and news releases. Tick data represents a single trade or quote in the market. It's a discrete event with no period, so the Timetime and EndTimeend_time properties are the same. LEAN emits ticks as soon as they arrive and doesn't fill them forward.

Time of receiving tick data via OnData handler

Examples

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

Example 1: Tick Signal Daily Security Bar

This example shows using a tick/pointwise signal from Smart Insider Intention dataset to buy AAPL in daily resolution. You can count down the holding trade days per each AAPL trade bar received to liquidate the position after 2 trading days.

public class PeriodTimeModelingAlgorithm : QCAlgorithm
{
    private Symbol _aapl;
    private Symbol _smartInsiderIntention;
    // For counting down the holding days per opening position.
    private int _holdDays = 0;

    public override void Initialize()
    {
        SetStartDate(2016, 2, 1);
        SetEndDate(2021, 3, 1);

        // Subscribe in daily resolution can aids with counting down.
        // We order with market on open order and will hold position for full day, so daily resolution is sufficient.
        _aapl = AddEquity("AAPL", Resolution.Daily).Symbol;
            
        // Requesting insider trade intention as sentiment trading signal.
        _smartInsiderIntention = AddData<SmartInsiderIntention>(_aapl).Symbol;
    }
    
    public override void OnData(Slice slice)
    {
        // Buy Apple whenever we receive a buyback intention or transaction notification due to volatility.
        if (slice.ContainsKey(_smartInsiderIntention))
        {
            // Hold the position for 2 days to fully digest the insider intention sentiment.
            MarketOnOpenOrder(_aapl, 100);
            _holdDays = 2;
        }

        // Count down the holding day per each daily trade bar received.
        if (Portfolio[_aapl].Invested && slice.Bars.ContainsKey(_aapl) && _holdDays-- <= 0)
        {
            // Liquidate the position if 2 days were fully counted down.
            Liquidate(_aapl);
        }
    }
}
class PeriodTimeModelingAlgorithm(QCAlgorithm):
    def initialize(self) :
        self.set_start_date(2016, 2, 1)
        self.set_end_date(2021, 3, 1)
        
        # Subscribe in daily resolution can aids with counting down.
        # We order with market on open order and will hold position for full day, so daily resolution is sufficient.
        self.aapl = self.add_equity("AAPL", Resolution.DAILY).symbol

        # Requesting insider trade intention as sentiment trading signal.
        self.smart_insider_intention = self.add_data(SmartInsiderIntention, self.aapl).symbol

        # For counting down the holding days per opening position.
        self.hold_days = 0

    def on_data(self, slice: Slice):
        # Buy Apple whenever we receive a buyback intention or transaction notification due to volatility.
        if self.smart_insider_intention in slice:
            # Hold the position for 2 days to fully digest the insider intention sentiment.
            self.market_on_open_order(self.aapl, 100)
            self.hold_days = 2

        # Count down the holding day per each daily trade bar received.
        if self.portfolio[self.aapl].invested and self.aapl in slice.bars:
            self.hold_days -= 1

            # Liquidate the position if 2 days were fully counted down.
            if self.hold_days <= 0:
                self.liquidate(self.aapl)

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: