[Canonical HTML article](https://algoway.trade/blog/pandas-rolling-min-periods-missing-candles.html)

datePublished: 2026-11-04T02:35:11+00:00

dateModified: 2026-11-04T02:35:11+00:00

# **pandas Rolling NaN: Missing Candles and min\_periods**

Published November 4, 2026 ·

![Rolling candle windows with one missing observation and two different eligibility thresholds](https://algoway.trade/static/img/blog-img/pandas-rolling-min-periods-missing-candles.jpg)

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](https://pandas.pydata.org/docs/user_guide/window.html) 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](https://algoway.trade/blog/python-trading-bot.html), 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:

```python
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()
```

| Row | Close | Valid prices in window | Strict mean | Relaxed mean |
| --- | --- | --- | --- | --- |
| 1 | 100 | 1 | NaN | NaN |
| 2 | 102 | 2 | NaN | 101 |
| 3 | Missing | 2 | NaN | 101 |
| 4 | 106 | 2 | NaN | 104 |
| 5 | 108 | 2 | NaN | 107 |
| 6 | 110 | 3 | 108 | 108 |

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](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.rolling.html) 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](https://pandas.pydata.org/docs/reference/api/pandas.Series.ffill.html) 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:

```python
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](https://algoway.trade/blog/freqtrade-recursive-analysis-startup-candles.html).

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