# How BanTA Handles Missing Data (NaN Values) in Technical Analysis

> Discover how BanTA effectively handles missing data NaN values in technical analysis. Learn its state-reset propagation strategy to prevent contaminated indicator results and ensure data integrity.

- Repository: [banbox/banta](https://github.com/banbox/banta)
- Tags: how-to-guide
- Published: 2026-02-26

---

**BanTA treats NaN values as missing data points and implements a state-reset propagation strategy that outputs NaN for invalid inputs while preserving internal calculation states, ensuring gaps in market feeds never contaminate indicator results.**

Real-world market data often contains gaps and invalid readings, which is why the `banbox/banta` repository implements a comprehensive **missing data handling** system for technical analysis. The library uniformly applies a **state-reset / propagation** strategy across all indicators, preventing a single NaN value from corrupting exponential moving averages, volume-weighted calculations, or cumulative sums.

## Central NaN Guard and Propagation Logic

According to the source code in [`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go), nearly every indicator begins with a guard clause that checks `math.IsNaN(value)` before processing. When an input value is NaN, the function immediately sets the corresponding output to NaN and continues to the next iteration without updating internal accumulation states.

This pattern appears consistently throughout the codebase:

- In the `Sum` function (lines 33-36), the code checks `math.IsNaN(v)` and executes `continue` early to skip the current iteration
- The `VWMA` implementation (lines 34-36) validates `cost` values before adding them to the volume-weighted buffer
- The `ewma` function (lines 74-76) returns the input unchanged when detecting `math.IsNaN(inVal)`, preventing the missing value from affecting the exponential smoothing state

This **central NaN guard** guarantees that missing data never leaks into mathematical operations, sums, or recursive calculations.

## Window-Based Indicators and Sample Requirements

For sliding-window indicators like **SMA**, **VWMA**, and **WMA**, BanTA maintains temporary buffers (`tmp`, `costs`, `volumes`) that store only valid data points. The algorithm only emits a valid value once the sliding window contains **enough consecutive non-NaN samples** to satisfy the indicator's period requirement.

In [`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go) (lines 50-53), the `Sum` function demonstrates this logic by checking `len(tmp) >= period` before writing results. Until the buffer fills with clean data, the output remains NaN. The `SMA` function (lines 66-71) then divides this validated sum by the period, ensuring that averages calculate exclusively from complete data windows rather than partial sets contaminated by gaps.

When a NaN appears within a filled window, the buffer management subtracts the earliest valid point and excludes the NaN from the running total, preserving mathematical correctness even when gaps appear mid-series.

## Stateful Indicators and Calculation Reset

Exponential moving averages (**EMA**, **RMA**) present unique challenges because they rely on previous results (`prevRes`) for recursive calculations. Rather than propagating NaN values through the infinite impulse response filter, BanTA implements a **state preservation** strategy.

As implemented in [`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go) (lines 74-76), when `ewma` encounters a NaN input, it returns the input unchanged and leaves `prevRes` unaffected. When the next valid input arrives, the algorithm restarts using the selected initialization strategy:

- **`initType == 0`**: Seeds the first EMA with a simple moving average (SMA)
- **`initType == 1`**: Uses the first valid price directly
- **`initVal`**: Applies a user-supplied initial value for custom restart behavior

This approach prevents a single gap from resetting the entire indicator history while ensuring that calculations resume from a mathematically sound starting point.

## Series Abstraction and Object-Oriented API

The higher-level Go API in [`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go) mirrors this missing-data logic through the `Series` type. Each method—`Sum`, `SMA`, `VWMA`, and others—checks `math.IsNaN(curVal)` before updating internal state variables like `sumVal` or ring buffers (`arr`).

This design ensures that both raw slice operations and object-oriented workflows inherit identical **missing data protection** without additional developer intervention. Any downstream calculation consuming these outputs automatically receives NaN for periods with insufficient data, creating a cascade of **consistent NaN propagation** throughout complex indicator chains.

## Practical Implementation Examples

### Using the Raw Slice API

When working directly with float64 slices, indicators automatically handle NaN values according to the window requirements:

```go
import (
    "github.com/banbox/banta/main/tav"
    "math"
)

func exampleSlice() {
    price := []float64{100, 101, math.NaN(), 103, 104, 105}
    volume := []float64{200, 210, 220, 230, 240, 250}

    // Simple moving average – NaNs break the window until enough clean data appear
    sma := tav.SMA(price, 3) // → [NaN, NaN, NaN, 101.333..., 102.333..., 104]

    // Volume-weighted moving average – resets when price*volume is NaN
    vwma := tav.VWMA(price, volume, 3)
    // → [NaN, NaN, NaN, 101.5, 102.5, 104.5]
}

```

Source: `SMA` uses `Sum` which skips NaNs (lines 33-36 of [`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go)); `VWMA` checks `math.IsNaN(cost)` (lines 34-36).

### Using the Series Abstraction

For stateful calculations across multiple bars, the `Series` type maintains internal buffers:

```go
func exampleSeries(env *banta.BarEnv) {
    closeSeries := env.Close          // *Series
    period := 5

    // EMA will emit NaN until the first valid price, then continue normally
    ema := banta.EMA(closeSeries, period)

    // VWMA on series – also respects NaNs in price or volume series
    volSeries := env.Volume
    vwma := banta.VWMA(closeSeries, volSeries, period)

    // You can read the latest value:
    fmt.Println("Current EMA:", ema.Get(0))
    fmt.Println("Current VWMA:", vwma.Get(0))
}

```

Source: `EMA` calls `ewma`, which returns the input unchanged for NaN (lines 74-76). `VWMA` checks `math.IsNaN(cost)` (lines 34-36).

### Comparing NaN Values in Tests

BanTA provides a helper for validation scenarios where two NaN values should be considered equivalent:

```go
if banta.EqualIn(math.NaN(), math.NaN()) {
    fmt.Println("Both values are NaN – considered equal")
}

```

Source: `equalIn` in [`utils.go`](https://github.com/banbox/banta/blob/main/utils.go) (lines 55-58) returns `true` when both arguments are NaN.

## Summary

- BanTA uses a **central NaN guard** in [`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go) to prevent invalid data from entering calculations, with specific checks at lines 33-36 for `Sum` and lines 74-76 for `ewma`
- **Window-based indicators** require consecutive non-NaN samples before emitting valid results, as implemented in the `Sum` buffer logic (lines 50-53)
- **Stateful indicators** preserve previous results (`prevRes`) during NaN gaps and restart with configurable initialization strategies (`initType`/`initVal`) when valid data returns
- The **Series abstraction** in [`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go) implements identical protection for object-oriented workflows, ensuring consistent behavior across both APIs
- The **`equalIn`** helper in [`utils.go`](https://github.com/banbox/banta/blob/main/utils.go) (lines 55-58) treats two NaN values as equal for testing and internal validation purposes

## Frequently Asked Questions

### What happens when a NaN value appears in the middle of a price series?

The indicator outputs NaN for that specific period and does not update its internal accumulation state. When valid data returns, window-based indicators continue filling their buffers from where they left off, while stateful indicators like EMA restart their calculations using the configured initialization type to ensure mathematical correctness.

### How does BanTA initialize EMA calculations after encountering NaN values?

According to [`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go) (lines 74-76), when the `ewma` function detects a NaN input, it returns the value unchanged and preserves the existing `prevRes`. Upon receiving the next valid input, the algorithm restarts using either an SMA seed (`initType == 0`), the first valid price (`initType == 1`), or a user-supplied `initVal`, preventing the gap from corrupting subsequent exponential smoothing results.

### Does BanTA treat two NaN values as equal in comparisons?

Yes. The `equalIn` function defined in [`utils.go`](https://github.com/banbox/banta/blob/main/utils.go) (lines 55-58) explicitly returns `true` when both arguments are NaN, which is useful for unit testing and internal validation checks where traditional equality operators would return false for NaN comparisons.

### Which technical indicators in BanTA support missing data handling?

All indicators in the library implement consistent NaN handling, including `Sum`, `SMA`, `EMA`, `RMA`, `VWMA`, and `WMA`. Both the raw slice API in [`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go) and the Series API in [`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go) uniformly apply the state-reset propagation strategy, ensuring that any indicator chain automatically inherits missing-data awareness.