# How BanTA Implements NaN Compatibility in the Series Struct

> Learn how BanTA implements NaN compatibility in its Series struct. Discover how NaN values are handled for accurate financial data analysis.

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

---

**BanTA treats IEEE-754 NaN values as first-class citizens in its `Series` struct, storing them without conversion, returning them safely for invalid indices, propagating them through technical indicators, and comparing them with specialized helpers to ensure missing price data never corrupts calculations.**

The banbox/banta library is designed for robust technical analysis on imperfect financial data feeds. NaN compatibility is woven into the `Series` type at the storage layer and persists through every indicator calculation, ensuring that missing prices generate predictable gaps rather than panics or misleading signals.

## NaN Storage and Retrieval in the Core Series Type

The foundation of BanTA's NaN handling lies in the `Series` struct definition in [`types.go`](https://github.com/banbox/banta/blob/main/types.go) (lines 48-58). The struct stores raw price data as a slice of `float64`, which natively supports IEEE-754 NaN values without requiring special wrappers or nullable types.

### Appending and Accessing NaN Values

In [`core.go`](https://github.com/banbox/banta/blob/main/core.go), the `Series.Append` method (lines 31-66) directly appends any `float64` value—including `math.NaN()`—to the internal `Data` slice. There is no filtering or conversion logic; NaN values are stored exactly as received.

When accessing data, `Series.Get` (lines 73-78) protects against out-of-range indices by returning `math.NaN()` rather than panicking. This design guarantees that calling code can always expect a float64 return value, using `math.IsNaN()` to detect invalid or missing data points.

For batch operations, `Series.RangeValid` (lines 105-115) constructs sub-slices that skip NaN values entirely, enabling indicators to operate only on valid price bars when needed.

## NaN Propagation in Technical Indicators

BanTA's technical indicators follow a strict propagation rule: *if any input operand is NaN, the output for that bar is NaN, and internal state remains unchanged.* This prevents a single missing price from corrupting rolling calculations.

### Simple Moving Average (SMA)

The `SMA` implementation in [`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go) (lines 87-98) emits NaN until the rolling window contains enough non-NaN samples to compute a valid average. If the window includes any NaN values, the result for that period is NaN.

### Exponential Moving Average (EMA)

The `ewma` function (lines 162-176), which underpins EMA calculations, checks `math.IsNaN` on inputs before updating internal state. When a NaN is encountered, the indicator propagates it as output but preserves the existing smoothing state, allowing the calculation to resume normally once valid data returns.

### Volume-Weighted Moving Average (VWMA)

In [`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go) (lines 334-350), the `VWMA` implementation returns NaN immediately if the weighted cost calculation yields NaN. Otherwise, it updates rolling sums only with valid numerical values.

## Safe Comparison and Equality Checks

Standard Go equality operators treat NaN as not equal to any value, including itself. BanTA provides the `equalIn` helper in [`utils.go`](https://github.com/banbox/banta/blob/main/utils.go) (lines 55-60) to handle NaN-aware comparisons used by testing utilities and caching mechanisms. This function considers two NaN values as equal, enabling reliable test assertions and cache hits even when data contains missing values.

The library exposes this through `EqualNearly`, which wraps `equalIn` for public use:

```go
a := math.NaN()
b := math.NaN()
fmt.Println(banta.EqualNearly(a, b)) // → true

```

## Practical Usage Examples

Creating a series with gaps and computing indicators demonstrates the NaN-safe workflow:

```go
// Create a Series with a missing value
s := env.NewSeries([]float64{100.0, math.NaN(), 102.0})

// Compute 3-period SMA
// - First bars lack sufficient data → NaN
// - Bars containing NaN inputs → NaN
sma := banta.SMA(s, 3)
fmt.Println(sma.Get(0)) // → NaN

```

EMA calculations automatically skip NaN inputs while preserving internal state:

```go
ema := banta.EMA(s, 5)
fmt.Println(ema.Get(0)) // → NaN when last input was NaN

```

## Summary

- **Raw Storage**: The `Series` struct in [`types.go`](https://github.com/banbox/banta/blob/main/types.go) stores `float64` values directly, including NaN, without conversion or filtering via `Append` in [`core.go`](https://github.com/banbox/banta/blob/main/core.go).
- **Safe Access**: `Series.Get` in [`core.go`](https://github.com/banbox/banta/blob/main/core.go) returns NaN for invalid indices, preventing panics and allowing callers to handle missing data gracefully.
- **Indicator Propagation**: Functions like `SMA`, `ewma`, and `VWMA` in [`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go) emit NaN when inputs are invalid and protect internal state from corruption.
- **Equality Handling**: The `equalIn` helper in [`utils.go`](https://github.com/banbox/banta/blob/main/utils.go) treats two NaN values as equal, supporting reliable testing and caching mechanisms.

## Frequently Asked Questions

### How does BanTA prevent panics when accessing invalid Series indices?

The `Series.Get` method in [`core.go`](https://github.com/banbox/banta/blob/main/core.go) (lines 73-78) checks index bounds and returns `math.NaN()` for any out-of-range request rather than panicking. Callers simply check `math.IsNaN()` on the result to detect invalid data.

### Why does BanTA treat two NaN values as equal when Go normally does not?

Standard Go equality considers NaN != NaN, which breaks cache keys and test assertions. BanTA's `equalIn` function in [`utils.go`](https://github.com/banbox/banta/blob/main/utils.go) (lines 55-60) explicitly handles this case, returning true when both operands are NaN to support reliable caching and testing.

### Do technical indicators reset their state when encountering NaN values?

No, indicators like `EMA` and `SMA` preserve their internal state when encountering NaN inputs. As implemented in [`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go), they emit NaN for the current bar but leave rolling sums and smoothing factors unchanged, allowing calculations to continue seamlessly once valid data resumes.

### Which source files contain the core NaN handling logic?

The primary implementations reside in four files: [`types.go`](https://github.com/banbox/banta/blob/main/types.go) defines the `Series` struct; [`core.go`](https://github.com/banbox/banta/blob/main/core.go) contains storage and retrieval methods; [`utils.go`](https://github.com/banbox/banta/blob/main/utils.go) provides comparison helpers; and [`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go) implements NaN-aware technical indicators including SMA, EMA, and VWMA.