How BanTA Implements NaN Compatibility in the Series Struct
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 (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, 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 (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 (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 (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:
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:
// 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:
ema := banta.EMA(s, 5)
fmt.Println(ema.Get(0)) // → NaN when last input was NaN
Summary
- Raw Storage: The
Seriesstruct intypes.gostoresfloat64values directly, including NaN, without conversion or filtering viaAppendincore.go. - Safe Access:
Series.Getincore.goreturns NaN for invalid indices, preventing panics and allowing callers to handle missing data gracefully. - Indicator Propagation: Functions like
SMA,ewma, andVWMAinsta_inds.goemit NaN when inputs are invalid and protect internal state from corruption. - Equality Handling: The
equalInhelper inutils.gotreats 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 (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 (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, 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 defines the Series struct; core.go contains storage and retrieval methods; utils.go provides comparison helpers; and sta_inds.go implements NaN-aware technical indicators including SMA, EMA, and VWMA.
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 →