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

> Compare BanTA state-caching vs parallel computation modes. Discover how state-caching enables O(1) live trading updates while parallel computation optimizes research with batch processing.

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

---

**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](https://github.com/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`](https://github.com/banbox/banta/blob/main/core.go), where the `Series.Cached()` method determines whether the series already covers the current environment time:

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

```

(see [[`core.go`](https://github.com/banbox/banta/blob/main/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:

```go
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`](https://github.com/banbox/banta/blob/main/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`](https://github.com/banbox/banta/blob/main/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`](https://github.com/banbox/banta/blob/main/tav/indicators.go) iterates over the whole input array once:

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

```

(see [[`tav/indicators.go`](https://github.com/banbox/banta/blob/main/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:

```go
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`](https://github.com/banbox/banta/blob/main/types.go)** — Defines the `Series` struct (the state holder) and `CrossLog`
- **[`core.go`](https://github.com/banbox/banta/blob/main/core.go)** — Implements `Series.Cached()`, `Series.To()`, and the environment logic driving state-caching
- **[`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go)** — Contains state-aware implementations for indicators like SMA, ADX, and ATR
- **[`tav/indicators.go`](https://github.com/banbox/banta/blob/main/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:

```go
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`](https://github.com/banbox/banta/blob/main/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:

```go
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`](https://github.com/banbox/banta/blob/main/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`](https://github.com/banbox/banta/blob/main/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.