BanTA State-Caching vs Parallel Computation Modes: A Technical Comparison

BanTA provides two execution strategies for technical analysis indicators: state-caching mode maintains per-series history for incremental O(1) updates during live trading, while parallel computation mode processes entire float64 slices in batch for research workloads.

The banbox/banta repository implements these dual execution paths to optimize for fundamentally different use cases—real-time streaming data versus historical bulk analysis. Understanding the architectural distinction between state-caching (state-aware) and parallel (batch) modes is essential for selecting the correct API surface and achieving optimal performance in your trading applications.

How State-Caching Mode Works

State-caching mode is designed for incremental computation where new market bars arrive one at a time, such as in live trading or event-driven backtesting environments.

Core Mechanism

In this mode, each technical indicator operates on a *Series object that persists internal state across calculation cycles. When a new candle arrives, the library updates the Series incrementally rather than recomputing the entire history.

The caching mechanism lives in core.go, where the Series.Cached() method determines whether the series already covers the current environment time:

func (s *Series) Cached() bool {
    return s.Time >= s.Env.TimeStop   // cached when the series covers the env's latest bar
}

(see [core.go lines 269-271](https://github.com/banbox/banta/blob/main/core.go#L269-L271))

Indicators that require mutable state—such as ADX, DM, or running-sum calculations—store intermediate values in the Series.More field. When environments are cloned, this state is preserved via Series.DupMore, ensuring continuity across symbol and timeframe boundaries.

API Usage

State-caching indicators are accessed through the main package using state-aware *Series objects:

ma5 := ta.SMA(e.Close, 5)               // state-cached SMA
atr := ta.ATR(e.High, e.Low, e.Close, 14).Get(0)

In sta_inds.go, the SMA implementation maintains a running sum inside Series.More, allowing O(1) updates per new bar rather than O(N) recalculation (see [sta_inds.go lines 87-93](https://github.com/banbox/banta/blob/main/sta_inds.go#L87-L93)).

How Parallel Computation Mode Works

Parallel computation mode—implemented in the tav sub-package—processes entire input arrays in a single pass without maintaining per-candle state.

Batch Processing Architecture

Unlike the state-caching approach, parallel mode receives plain []float64 slices and computes the full indicator series from scratch. This mode discards all intermediate state between calls, returning a new slice containing the complete result vector.

The tav package duplicates the mathematical logic from the main indicators but operates exclusively on slices. For example, the DV2 indicator in tav/indicators.go iterates over the whole input array once:

func DV2(h, l, c []float64, period, maLen int) []float64 { … }   // batch version

(see [tav/indicators.go lines 85-87](https://github.com/banbox/banta/blob/main/tav/indicators.go#L85-L87))

When to Use Parallel Mode

Use this mode for research, bulk-historical analysis, or vectorized operations where you need the full time series output at once. The API surface uses the tav prefix:

ma5 := tav.SMA(closeArr, 5)                // slice-based SMA
atr := tav.ATR(highArr, lowArr, closeArr, 14)
xArr := tav.Cross(ma5, ma30)

Performance Characteristics and Trade-offs

State-caching mode minimizes CPU usage for streaming data by reusing historic calculations. Memory overhead is minimal—only the cached series values and the More state field are retained. This makes it ideal for high-frequency updates where only the latest bar changes.

Parallel computation mode incurs the full O(N) computational cost on every call because the entire series is recomputed from raw inputs. However, it avoids object allocation overhead for Series instances and performs well for one-off bulk calculations, behaving similarly to TA-Lib implementations.

Implementation Details and Key Files

The dual-mode architecture spans several critical files in the repository:

  • types.go — Defines the Series struct (the state holder) and CrossLog
  • core.go — Implements Series.Cached(), Series.To(), and the environment logic driving state-caching
  • sta_inds.go — Contains state-aware implementations for indicators like SMA, ADX, and ATR
  • tav/indicators.go — Houses parallel, slice-based implementations of the same mathematical functions

Both modes share identical mathematical cores; the divergence lies strictly in state management—either preserving it via Series.More or discarding it for functional purity.

Practical Code Examples

State-Caching for Live Trading

Use this pattern when processing real-time bar updates:

var envMap = make(map[string]*ta.BarEnv)

func OnBar(symbol, timeframe string, bar *ta.Kline) {
    key := fmt.Sprintf("%s_%s", symbol, timeframe)
    e := envMap[key]
    if e == nil {
        e = &ta.BarEnv{TimeFrame: timeframe, BarNum: 1}
        envMap[key] = e
    }
    e.OnBar(bar.Time, bar.Open, bar.High, bar.Low, bar.Close,
        bar.Volume, bar.Quote, bar.BuyVolume, bar.TradeNum)

    ma5 := ta.SMA(e.Close, 5)               // state-cached SMA
    atr := ta.ATR(e.High, e.Low, e.Close, 14).Get(0)
    // …
}

(see [readme.md lines 69-92](https://github.com/banbox/banta/blob/main/readme.md#L69-L92))

Parallel Mode for Research

Use this pattern for historical vectorized analysis:

highArr := []float64{1.01, 1.01, 1.02, 0.996}
lowArr  := []float64{0.99, 1.00, 1.00, 0.98}
closeArr := []float64{1.00, 1.01, 1.00, 0.99}

ma5 := tav.SMA(closeArr, 5)                // slice-based SMA
atr := tav.ATR(highArr, lowArr, closeArr, 14)
xArr := tav.Cross(ma5, ma30)

(see [readme.md lines 20-34](https://github.com/banbox/banta/blob/main/readme.md#L20-L34))

Summary

  • State-caching mode maintains a per-symbol, per-timeframe Series object that remembers past results, enabling O(1) incremental updates for live trading scenarios
  • Parallel computation mode operates on raw []float64 slices without persistent state, recomputing full vectors from scratch for batch analysis
  • State-caching uses the main package API (ta.Indicator()) while parallel mode requires the tav sub-package (tav.Indicator())
  • Both implementations share the same mathematical logic but differ in whether they store intermediate state in Series.More

Frequently Asked Questions

Can I mix state-caching and parallel modes in the same application?

Yes. According to the banbox/banta source code, both modes can coexist in the same codebase. Use state-caching (ta package) for real-time data processing and parallel mode (tav package) for offline research or initial historical data seeding. The mathematical results are identical, though floating-point accumulation may differ slightly due to running-sum versus full-recalculation algorithms.

Why does state-caching use the Series.More field?

The Series.More field in types.go provides generic storage for indicator-specific state that must persist across bar updates. Complex indicators like ADX or Wilder's smoothing require remembering previous values, which More stores as a raw interface. When environments are cloned via Series.DupMore, this state copies forward, ensuring continuity without global variables.

Is parallel mode actually parallelized with goroutines?

Despite the name, "parallel" in BanTA refers to batch vectorization rather than concurrent goroutine execution. The tav functions process entire arrays in single-pass loops without per-candle state, but they do not automatically spawn multiple threads. The parallelism describes the data flow (processing all bars simultaneously) rather than CPU threading.

Which mode should I use for backtesting large historical datasets?

For bulk backtesting where all historical bars are available upfront, parallel computation mode (tav package) is typically preferred. It avoids the memory overhead of maintaining Series objects for every indicator and symbol combination. However, if your backtest processes bars sequentially like live trading, state-caching provides better performance by avoiding redundant recalculation of unchanged history.

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 →