Historical Data

Rolling Window

Introduction

A RollingWindow is an array of a fixed-size that holds trailing data. It's more efficient to use RollingWindow objects to hold periods of data than to make multiple historical data requests. With a RollingWindow, you just update the latest data point while a Historyhistory call fetches all of the data over the period you request. RollingWindow objects operate on a first-in, first-out process to allow for reverse list access semantics. Index 0 refers to the most recent item in the window and the largest index refers to the last item in the window.

Supported Types

RollingWindow objects can store any native or C# types.

closeWindow = new RollingWindow<decimal>(4);
tradeBarWindow = new RollingWindow<TradeBar>(2);
quoteBarWindow = new RollingWindow<QuoteBar>(2);
self._close_window = RollingWindow[float](4)
self._trade_bar_window = RollingWindow[TradeBar](2)
self._quote_bar_window = RollingWindow[QuoteBar](2)

To be notified when RollingWindow objects support additional types, subscribe to GitHub Issue #6199.

Add Data

To add data to a RollingWindow, call the Addadd method.

closeWindow.Add(data["SPY"].Close);
tradeBarWindow.Add(data["SPY"]);
quoteBarWindow.Add(data["EURUSD"]);
self._close_window.add(data["SPY"].close)
self._trade_bar_window.add(data["SPY"])
self._quote_bar_window.add(data["EURUSD"])

To update the data at a specific index, set the value for that index. If the index doesn't currently exist, it increases the size and fills the empty indices with a default value (zero or nullNone).

closeWindow[0] = data["SPY"].Close;
tradeBarWindow[0] = data["SPY"];
quoteBarWindow[0] = data["EURUSD"];
self._close_window[0] = data["SPY"].close
self._trade_bar_window[0] = data["SPY"]
self._quote_bar_window[0] = data["EURUSD"]

Warm Up

To warm up a RollingWindow, make a history request and then iterate through the result to add the data to the RollingWindow.

var spy = AddEquity("SPY", Resolution.Daily).Symbol;
var historyTradeBar = History<TradeBar>(spy, 10, Resolution.Daily);
var historyQuoteBar = History<QuoteBar>(spy, 10, Resolution.Minute);

// Warm up the close price and trade bar rolling windows with the previous 10-day trade bar data
var closePriceWindow = new RollingWindow<decimal>(10);
var tradeBarWindow = new RollingWindow<TradeBar>(10);
foreach (var tradeBar in historyTradeBar)
{
    closePriceWindow.Add(tradeBar.Close);
    tradeBarWindow.Add(tradeBar);
}

// Warm up the quote bar rolling window with the previous 10-minute quote bar data
var quoteBarWindow = new RollingWindow<QuoteBar>(10);
foreach (var quoteBar in historyQuoteBar)
{
    quoteBarWindow.Add(quoteBar);
}
spy = self.add_equity("SPY", Resolution.DAILY).symbol
history_trade_bar = self.history[TradeBar](spy, 10, Resolution.DAILY)
history_quote_bar = self.history[QuoteBar](spy, 10, Resolution.MINUTE)

# Warm up the close price and trade bar rolling windows with the previous 10-day trade bar data
close_price_window = RollingWindow[float](10)
trade_bar_window = RollingWindow[TradeBar](10)
for trade_bar in history_trade_bar:
    close_price_window.add(trade_bar.close)
    trade_bar_window.add(trade_bar)

# Warm up the quote bar rolling window with the previous 10-minute quote bar data
quote_bar_window = RollingWindow[QuoteBar](10)
for quote_bar in history_quote_bar:
    quote_bar_window.add(quote_bar)

Check Readiness

To check if a RollingWindow is full, use its IsReady flag.

if (!closeWindow.IsReady) 
{
    return;
}
if not self._close_window.is_ready:
    return

Adjust Size

To adjust the RollingWindow size, set the Sizesize property.

closeWindow.Size = 3;
tradeBarWindow.Size = 3;
quoteBarWindow.Size = 3;
self._close_window.size = 3
self._trade_bar_window.size = 3
self._quote_bar_window.size = 3

When you decrease the size, it removes the oldest values that no longer fit in the RollingWindow. When you explicitly increase the Sizesize member, it doesn't automatically add any new elements to the RollingWindow. However, if you set the value of an index in the RollingWindow and the index doesn't currently exist, it fills the empty indices with a default value (zero or nullNone). For example, the following code increases the Sizesize to 10, sets the 10th element to 3, and sets the 4th-9th elements to the default value:

closeWindow[9] = 3;
self._close_window[9] = 3

Access Data

RollingWindow objects operate on a first-in, first-out process to allow for reverse list access semantics. Index 0 refers to the most recent item in the window and the largest index refers to the last item in the window.

var currentClose = closeWindow[0];
var previousClose = closeWindow[1];
var oldestClose = closeWindow[closeWindow.Count-1];
current_close = self._close_window[0]
previous_close = self._close_window[1]
oldest_close = self._close_window[self._close_window.count-1]

To get the item that was most recently removed from the RollingWindow, use the MostRecentlyRemovedmost_recently_removed property.

var removedClose = closeWindow.MostRecentlyRemoved;
removed_close = self._close_window.most_recently_removed

Combine with Indicators

The Windowwindow property of the indicators is a built-in RollingWindow that stores historical values. It holds a collection of IndicatorDataPoint objects, enabling quick access to the most recent historical indicator values for analysis, calculations, or comparisons in trading and financial strategies.

Default size of the in-built RollignWindow is 2, but you can adjust its size by setting the Sizesize according to your need.

To warm up the indicator together with its RollignWindow, you can call the IndicatorHistoryindicator_history method to do so.

public class IndicatorRolingWindowAlgorithm : QCAlgorithm
{
    // Manual SMA indicator example.
    private SimpleMovingAverage _sma = new(20);
    private Beta _beta;

    public override void Initialize()
    {
        var spy = AddEquity("SPY").Symbol;
        var qqq = AddEquity("QQQ").Symbol;

        // Automatic Beta indicator example.
        _beta = B(spy, qqq, 20);

        // Adjust the window size to 5 to access the previous 5 indicator data points.
        _sma.Window.Size = 5;
        _beta.Window.Size = 5;
        
        // Warm up the indicator for immediate usage; also, warm up the window. 
        // You can use IndicatorHistory to control the number of bars you warm up with.
        IndicatorHistory(_sma, spy, _sma.WarmUpPeriod + _sma.Window.Size, Resolution.Daily);
        // For indicators using 2+ symbols, use an iterable symbol list.
        IndicatorHistory(_beta, new[] { spy, qqq }, _beta.WarmUpPeriod + _beta.Window.Size, Resolution.Daily);
    }
}
class IndicatorRolingWindowAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        spy = self.add_equity("SPY").symbol
        qqq = self.add_equity("QQQ").symbol
    
        # Manual SMA indicator example.
        self._sma = SimpleMovingAverage(20)
        # Automatic Beta indicator example.
        self._beta = self.b(spy, qqq, 20)
    
        # Adjust the window size to 5 to access the previous 5 indicator data points.
        self._sma.window.size = 5
        self._beta.window.size = 5
    
        # Warm up the indicator for immediate usage; also, warm up the window. 
        # You can use indicator_history to control the number of bars to warm up with.self.indicator_history(self._sma, spy, self._sma.warm_up_period + self._sma.window.size, Resolution.DAILY)
        # For indicators using 2+ symbols, use an iterable symbol list.
        self.indicator_history(self._beta, [spy, qqq], self._beta.warm_up_period + self._beta.window.size, Resolution.DAILY)

Combine with Consolidators

To store a history of consolidated bars, in the consolidation handler, add the consolidated bar to the RollingWindow.

_consolidator.DataConsolidated += (sender, consolidatedBar) => tradeBarWindow.Add(consolidatedBar);
self._consolidator.data_consolidated += self._on_data_consolidated

# Define consolidator handler function as a class method
def _on_data_consolidated(self, sender, consolidated_bar):
    self._trade_bar_window.add(consolidated_bar)

Cast to Other Types

You can cast a RollingWindow to a list or a DataFrame. If you cast it to a list, reverse the list so the most recent element is at the last index of the list. This is the order the elements would be in if you added the elements to the list with the Add method. To cast a RollingWindow to a DataFrame, the RollingWindow must contain Slice, Tick, QuoteBar, or TradeBar objects. If the RollingWindow contains ticks, the ticks must have unique timestamps.

You can cast a RollingWindow to a list. If you cast it to a list, reverse the list so the most recent element is at the last index of the list. This is the order the elements would be in if you added the elements to the list with the Addadd method.

var closes = closeWindow.Reverse().ToList();
closes = list(self._close_window)[::-1]

tick_df = self.pandas_converter.get_data_frame[Tick](list(self._tick_window)[::-1])
trade_bar_df = self.pandas_converter.get_data_frame[TradeBar](list(self._trade_bar_window)[::-1])
quote_bar_df = self.pandas_converter.get_data_frame[QuoteBar](list(self._quote_bar_window)[::-1])

Delete Data

To remove all of the elements from a RollingWindow, call the Resetreset method.

closeWindow.Reset();
self._close_window.reset()

Examples

The following examples demonstrate some common practices for implementing rolling windows.

Example 1: Price Actions

Using RollingWindow to cache the last 3 trade bars, the following algorithm could identify volume contraction breakout price action patterns and buy SPY accordingly to ride on the capital inflow. We take 2% profit and stop loss at 1% in OnOrderEventon_order_event.

public class RollingWindowAlgorithm : QCAlgorithm
{
    private Symbol _spy;
    // Set up a rolling window to hold the last 3 trade bars for price action detection as a trade signal.
    private RollingWindow<TradeBar> _windows = new(3);

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

        // Request SPY data for signal generation && trading.
        _spy = AddEquity("SPY", Resolution.Minute).Symbol;

        // Warm up for the rolling window.
        var history = History<TradeBar>(_spy, 3, Resolution.Minute);
        foreach (var bar in history)
        {
            _windows.Add(bar);
        }
    }

    public override void OnData(Slice slice)
    {
        if (slice.Bars.TryGetValue(_spy, out var bar))
        {
            // Trade the price action if the previous bars fulfill a contraction breakout.
            if (ContractionAction() && BreakoutAction(bar.Close))
            {
                SetHoldings(_spy, 0.5m);
            }

            // Update the window with the current bar.
            _windows.Add(bar);
        }
    }

    private bool ContractionAction()
    {
        // We trade contraction-type price action, where the buying pressure is increasing.
        // 1. The last 3 bars are green.
        // 2. The price is increasing in trend.
        // 3. The trading volume is increasing as well.
        // 4. The range of the bars is decreasing.
        return _windows[2].Close > _windows[2].Open &&
            _windows[1].Close > _windows[1].Open &&
            _windows[0].Close > _windows[0].Open &&
            _windows[0].Close > _windows[1].Close && _windows[1].Close > _windows[2].Close &&
            _windows[0].Volume > _windows[1].Volume && _windows[1].Volume > _windows[2].Volume &&
            _windows[2].Close - _windows[2].Open > _windows[1].Close - _windows[1].Open &&
            _windows[1].Close - _windows[1].Open > _windows[0].Close - _windows[0].Open;
    }

    private bool BreakoutAction(decimal currentPrice)
    {
        // We trade breakout from contraction: the breakout should be much above the contracted range of the last bar.
        return currentPrice - _windows[0].Close > (_windows[0].Close - _windows[0].Open) * 2m;
    }

    public override void OnOrderEvent(OrderEvent orderEvent)
    {
        if (orderEvent.Status == OrderStatus.Filled)
        {
            if (orderEvent.Ticket.OrderType == OrderType.Market)
            {
                // Stop loss order at 1%.
                var stopPrice = orderEvent.FillQuantity > 0m ? orderEvent.FillPrice * 0.99m : orderEvent.FillPrice * 1.01m;
                StopMarketOrder(_spy, -Portfolio[_spy].Quantity, stopPrice);
                // Take profit order at 2%.
                var takeProfitPrice = orderEvent.FillQuantity > 0m ? orderEvent.FillPrice * 1.02m : orderEvent.FillPrice * 0.98m;
                LimitOrder(_spy, -Portfolio[_spy].Quantity, takeProfitPrice);
            }
            else if (orderEvent.Ticket.OrderType == OrderType.StopMarket || orderEvent.Ticket.OrderType == OrderType.Limit)
            {
                // Cancel any open order if stop loss or take profit order filled.
                Transactions.CancelOpenOrders();
            }
        }
    }
}
class RollingWindowAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2021, 1, 1)
        self.set_end_date(2022, 1, 1)

        # Request SPY data for signal generation and trading.
        self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol

        # Set up a rolling window to hold the last 3 trade bars for price action detection as a trade signal.
        self.windows = RollingWindow[TradeBar](3)
        # Warm up for the rolling window.
        history = self.history[TradeBar](self.spy, 3, Resolution.MINUTE)
        for bar in history:
            self.windows.add(bar)

    def on_data(self, slice: Slice) -> None:
        bar = slice.bars.get(self.spy)
        if bar:
            # Trade the price action if the previous bars fulfill a contraction breakout.
            if self.contraction_action and self.breakout(bar.close):
                self.set_holdings(self.spy, 0.5)

            # update the window with the current bar.
            self.windows.add(bar)

    def contraction_action(self) -> None:
        # We trade contraction-type price action, where the buying pressure is increasing.
        # 1. The last 3 bars are green.
        # 2. The price is increasing in trend.
        # 3. The trading volume is increasing as well.
        # 4. The range of the bars is decreasing.
        return self.windows[2].close > self.windows[2].open and \
            self.windows[1].close > self.windows[1].open and \
            self.windows[0].close > self.windows[0].open and \
            self.windows[0].close > self.windows[1].close > self.windows[2].close and \
            self.windows[0].volume > self.windows[1].volume > self.windows[2].volume and \
            self.windows[2].close - self.windows[2].open > self.windows[1].close - self.windows[1].open > self.windows[0].close - self.windows[0].open

    def breakout(self, current_close: float) -> None:
        # We trade breakout from contraction: the breakout should be much above the contracted range of the last bar.
        return current_close - self.windows[0].close > (self.windows[0].close - self.windows[0].open) * 2

    def on_order_event(self, order_event: OrderEvent) -> None:
        if order_event.status == OrderStatus.FILLED:
            if order_event.ticket.order_type == OrderType.MARKET:
                # Stop loss order at 1%.
                stop_price = order_event.fill_price * (0.99 if order_event.fill_quantity > 0 else 1.01)
                self.stop_market_order(self.spy, -self.portfolio[self.spy].quantity, stop_price)
                # Take profit order at 2%.
                take_profit_price = order_event.fill_price * (1.02 if order_event.fill_quantity > 0 else 0.98)
                self.limit_order(self.spy, -self.portfolio[self.spy].quantity, take_profit_price)
            elif order_event.ticket.order_type == OrderType.STOP_MARKET or order_event.ticket.order_type == OrderType.LIMIT:
                # Cancel any open order if stop loss or take profit order filled.
                self.transactions.cancel_open_orders()

Example 2: Bid-Ask Spread

The following algorithm trades the microeconomy of SPY's supply-demand relationship. We buy SPY if the current bid-ask spread is less than the average of the last 20 quote bars, indicating demand is approaching supply during short SPY and vice versa. We use a rolling window to save the last 20 quote data and save the calculated spread.

public class RollingWindowAlgorithm : QCAlgorithm
{
    private Symbol _spy;
    // Set up a rolling window to hold the last 20 bar's bid-ask spread for trade signal generation.
    private RollingWindow<decimal> _windows = new(20);

    public override void Initialize()
    {
        SetStartDate(2020, 2, 20);
        SetEndDate(2020, 2, 27);

        // Request SPY data for signal generation and trading.
        _spy = AddEquity("SPY", Resolution.Minute).Symbol;

        // Warm up for the rolling window with quote data.
        var history = History<QuoteBar>(_spy, 20, Resolution.Minute);
        foreach (var bar in history)
        {
            _windows.Add(bar.Ask.Close - bar.Bid.Close);
        }
    }
    
    public override void OnData(Slice slice)
    {
        if (slice.QuoteBars.TryGetValue(_spy, out var bar))
        {
            // Update the window with the current bid-ask spread.
            var spread = bar.Ask.Close - bar.Bid.Close;
            _windows.Add(spread);

            // Buy if the current spread is smaller than average, indicating demand is approaching supply. Buy force will drive up prices.
            if (spread < _windows.Average())
            {
                SetHoldings(_spy, -0.5m);
            }
            // Short if the current spread is larger than average, indicating supply is gradually overwhelming demand.
            else if (spread > _windows.Average())
            {
                SetHoldings(_spy, 0.5m);
            }
        }
    }
}
class RollingWindowAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2020, 2, 20)
        self.set_end_date(2020, 2, 27)
        
        # Request SPY data for signal generation and trading.
        self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol

        # Set up a rolling window to hold the last 20 bar's bid-ask spread for trade signal generation.
        self.windows = RollingWindow[float](20)
        # Warm up for the rolling window with quote data.
        history = self.history[QuoteBar](self.spy, 20, Resolution.MINUTE)
        for bar in history:
            self.windows.add(bar.ask.close - bar.bid.close)

    def on_data(self, slice: Slice) -> None:
        bar = slice.quote_bars.get(self.spy)
        if bar:
            # Update the window with the current bid-ask spread.
            spread = bar.ask.close - bar.bid.close
            self.windows.add(spread)

            # Buy if the current spread is smaller than average, indicating demand is approaching supply. Buy force will drive up prices.
            if spread < np.mean(list(self.windows)):
                self.set_holdings(self.spy, -0.5)
            # Short if the current spread is larger than average, indicating supply is gradually overwhelming demand.
            elif spread > np.mean(list(self.windows)):
                self.set_holdings(self.spy, 0.5)

Example 3: Trend Acceleration

The following algorithm trades the second derivative of EMA indicated trend, especially the difference between lagged EMA values. If the daily EMA difference increases while being lower than the current price, we estimate the uptrend is accelerating, hence buying SPY. Otherwise, if the price is below the EMA while the daily EMA difference is decelerating, we short SPY.

public class RollingWindowAlgorithm : QCAlgorithm
{
    private Symbol _spy;
    private ExponentialMovingAverage _ema;

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

        // Request SPY data for trading.
        _spy = AddEquity("SPY", Resolution.Minute).Symbol;
        
        // Set up an automatic EMA indicator for trade signal generation.
        _ema = EMA(_spy, 20, Resolution.Daily);
        //Using the rolling window in the EMA indicator, we adjust its size so it can compare with previous data points.
        _ema.Window.Size = 3;

        // Schedule an event to rebalance SPY position at the daily market open.
        Schedule.On(
            DateRules.EveryDay(_spy),
            TimeRules.AfterMarketOpen(_spy, 0),
            Rebalance
        );

        SetWarmUp(23, Resolution.Daily);
    }
    
    private void Rebalance()
    {
        if (_ema.Window.IsReady)
        {
            // Buy if the current EMA increases with acceleration, indicating a strong up trend.
            if (_ema.Window[1] < _ema && _ema.Window[0] - _ema.Window[1] > _ema.Window[1] - _ema.Window[2])
            {
                SetHoldings(_spy, 0.5m);
            }
            // Short if the current EMA decreases with acceleration, indicating a strong downtrend.
            else if (_ema.Window[1] > _ema && _ema.Window[0] - _ema.Window[1] < _ema.Window[1] - _ema.Window[2])
            {
                SetHoldings(_spy, 0.5m);
            }
            // Liquidate if no strong trend is indicated.
            else if (Portfolio.Invested)
            {
                Liquidate(_spy);
            }
        }
    }
}
class RollingWindowAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2021, 8, 1)
        self.set_end_date(2022, 11, 1)
        
        # Request SPY data for trading.
        self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol

        # Set up an automatic EMA indicator for trade signal generation.
        self._ema = self.ema(self.spy, 20, Resolution.DAILY)
        # Using the rolling window in the EMA indicator, we adjust its size so it can compare with previous data points.
        self._ema.window.size = 3

        # Schedule an event to rebalance SPY position at the daily market open.
        self.schedule.on(
            self.date_rules.every_day(self.spy),
            self.time_rules.after_market_open(self.spy, 0),
            self.rebalance
        )

        self.set_warm_up(23, Resolution.DAILY)

    def rebalance(self) -> None:
        if self._ema.window.is_ready:
            # Buy if the current EMA increases with acceleration, indicating a strong up trend.
            if self._ema.window[1].value < self._ema.current.value and \
            self._ema.window[0].value - self._ema.window[1].value > self._ema.window[1].value - self._ema.window[2].value:
                self.set_holdings(self.spy, 0.5)
            # Short if the current EMA decreases with acceleration, indicating a strong downtrend.
            elif self._ema.window[1].value > self._ema.current.value and \
            self._ema.window[0].value - self._ema.window[1].value < self._ema.window[1].value - self._ema.window[2].value:
                self.set_holdings(self.spy, -0.5)
            # Liquidate if no strong trend is indicated.
            elif self.portfolio.invested:
                self.liquidate(self.spy)

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: