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

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, 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 (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 (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 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:

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); VWMA checks math.IsNaN(cost) (lines 34-36).

Using the Series Abstraction

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

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:

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

Source: equalIn in utils.go (lines 55-58) returns true when both arguments are NaN.

Summary

  • BanTA uses a central NaN guard in 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 implements identical protection for object-oriented workflows, ensuring consistent behavior across both APIs
  • The equalIn helper in 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 (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 (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 and the Series API in sta_inds.go uniformly apply the state-reset propagation strategy, ensuring that any indicator chain automatically inherits missing-data awareness.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →