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
Sumfunction (lines 33-36), the code checksmath.IsNaN(v)and executescontinueearly to skip the current iteration - The
VWMAimplementation (lines 34-36) validatescostvalues before adding them to the volume-weighted buffer - The
ewmafunction (lines 74-76) returns the input unchanged when detectingmath.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 directlyinitVal: 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.goto prevent invalid data from entering calculations, with specific checks at lines 33-36 forSumand lines 74-76 forewma - Window-based indicators require consecutive non-NaN samples before emitting valid results, as implemented in the
Sumbuffer 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.goimplements identical protection for object-oriented workflows, ensuring consistent behavior across both APIs - The
equalInhelper inutils.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →