cTrader

AlgoWay Blog

Guides, manuals and platform references.

Core
JSON & Alerts
MT5 / Expert Advisors
Errors
Integrations
Other Publications
AlgoWay Strategy Lab

pandas Rolling NaN: Missing Candles and min_periods

Published November 4, 2026 · View Markdown

Rolling candle windows with one missing observation and two different eligibility thresholds

A three-row window can contain only two usable prices. That is enough to explain many missing moving-average values, even when the DataFrame looks comfortably long.

For a pandas rolling mean, min_periods controls how many non-missing observations the window must contain. An integer window of three defaults to requiring three. One missing close can therefore keep the result at NaN after the initial startup rows have passed. The pandas windowing guide documents this valid-observation requirement.

In an automated trading bot, this is part of the signal rule. Before using AlgoWay's Python webhook integration guide, decide what your strategy should do when its input is incomplete. Producing a number by relaxing the calculation can change which orders your program decides to send.

Count prices, not just rows

Take six hypothetical closes: 100, 102, missing, 106, 108 and 110. Compare a strict three-observation mean with a three-row mean that accepts two valid observations:

import pandas as pd

close = pd.Series([100.0, 102.0, float("nan"), 106.0, 108.0, 110.0])
strict = close.rolling(3, min_periods=3).mean()
relaxed = close.rolling(3, min_periods=2).mean()
RowCloseValid prices in windowStrict meanRelaxed mean
11001NaNNaN
21022NaN101
3Missing2NaN101
41062NaN104
51082NaN107
61103108108

At row 4, the window contains 102, missing and 106. The relaxed mean is (102 + 106) / 2 = 104. The strict result remains missing. Only at row 6 does the missing close leave the window, allowing the strict mean of 106, 108 and 110.

The snippet and these results were checked locally with pandas 2.3.3. They are a calculation example, not a strategy backtest. The important change is the denominator: accepting two observations does not reconstruct the third price.

Three rows and three minutes answer different questions

The rolling API reference distinguishes integer windows from time-based windows. The former counts observations; the latter selects observations within a time interval. A time-based window defaults to min_periods=1, whereas an integer window defaults to its window size.

Suppose your timestamped series contains 100 at 09:30, 102 at 09:31 and 110 at 09:35. At 09:35, a three-row mean is 104. A trailing three-minute mean with default boundaries includes only the latest observation, producing 110. Setting min_periods=3 on that time window instead produces a missing result. These outcomes were also checked in the local example.

This exposes a second problem: an absent candle and a row containing a missing close are different data shapes. If 09:32 never appears in the index, a row-count calculation does not know that you expected a candle there. A valid mean is not proof of uninterrupted market data. Check expected timestamps against the instrument's trading schedule separately.

Filling the gap changes the price series

The pandas forward-fill method carries the previous valid observation into a missing value. In the first example, filling the missing close with 102 makes the first complete three-row mean approximately 101.33. That differs from both the strict missing result and the relaxed two-price mean of 101.

A carried-forward quote can be a deliberate modeling choice, but it is not evidence of a new trade at that price. Preserve a separate record of which values were filled. Otherwise a data outage can quietly become a row of apparently uneventful candles.

Similarly, filling a missing indicator value with its previous result makes the signal depend on an older calculation. Filling it with zero introduces a different value entirely. Neither choice should enter a strategy merely to make a table look tidy.

Make eligibility explicit before evaluating the entry

For a rule that requires three valid closes, keep that requirement in the calculation and check eligibility before comparing prices:

average = close.rolling(3, min_periods=3).mean()
ready = close.notna() & average.notna()
entry_condition = ready & (close > average)

This illustrates an eligibility mask, not a complete bot. It assumes the rows have already been restricted to completed candles for one instrument. A live system still needs checks for stale data, expected timestamps, repeated events and current order state. The mask does not cancel existing orders or manage existing positions when data becomes unavailable.

Check four situations before accepting a change: startup with too little history, an interior missing value, an absent timestamp, and the first complete window after recovery. Record whether each situation permits a new entry. If the values are present but change when you load a longer history, that is a different investigation; see indicator stability and recursive startup history.

A missing result can be useful information. Decide what evidence is required for a signal first, then make the calculation enforce that decision.

Last updated: November 4, 2026