# How to Use BanTA State-Caching Mode for Real-Time Trading

> Master BanTA state-caching mode for real-time trading. Process new candles in microseconds with incremental indicator updates. Optimize your trading bots with BanTA.

- Repository: [banbox/banta](https://github.com/banbox/banta)
- Tags: how-to-guide
- Published: 2026-02-26

---

**BanTA’s state-caching mode enables incremental indicator updates by attaching a BarEnv to a symbol-timeframe pair, allowing trading bots to process new candles in microseconds without recomputing entire histories.**

BanTA (Ban Technical Analysis) is an open-source technical analysis library written in Go and available at `banbox/banta`. Unlike traditional libraries that recompute indicators from scratch on every call, BanTA offers a **state-caching mode** specifically designed for low-latency, real-time trading systems. This article explains the internal mechanics of the caching architecture and provides production-ready implementations in both Go and Python.

## Understanding BanTA Execution Models

BanTA supports two distinct execution strategies that determine how indicator values are calculated across historical data.

**Parallel Mode** treats every calculation as stateless. When you call an indicator function, the library recomputes values for the entire input series from the first bar to the last. This is ideal for backtesting large datasets where you need complete histories, but it introduces unnecessary latency for live trading.

**State-Caching Mode** maintains persistent state between bars. When a new candle arrives, only the incremental update is calculated. The library stores previous results in `Series` objects attached to a `BarEnv` (environment), allowing microsecond-level indicator updates essential for high-frequency strategies.

## How State-Caching Works in BanTA

The state-caching architecture relies on three core components working in concert: the environment manager (`BarEnv`), the value containers (`Series`), and the indicator functions that interact with both.

### The BarEnv Lifecycle

Every symbol-timeframe pair requires its own `BarEnv` instance. This environment acts as the central coordinator for incoming market data.

When a new bar arrives, you invoke `BarEnv.OnBar`, which validates chronological ordering and delegates to `OnBar2`. According to the source in [`core.go`](https://github.com/banbox/banta/blob/main/core.go) (lines 36-70), `OnBar2` performs three critical operations:

1. Appends the new OHLCV values to the environment’s built-in series
2. Advances the internal timestamp (`TimeStop`)
3. Invokes `TrimOverflow` to enforce memory bounds

`TrimOverflow` (lines 88-98 in [`core.go`](https://github.com/banbox/banta/blob/main/core.go)) maintains the cache size according to `BarEnv.MaxCache` (default 1500 bars). When storage exceeds `1.5 * MaxCache`, the oldest values are discarded to prevent unbounded memory growth during long-running trading sessions.

### Series Caching Mechanism

Indicators in BanTA operate on `Series` objects rather than raw slices. Each `Series` maintains its own timestamp (`s.Time`) and cached values.

The caching logic centers on `Series.Cached` (lines 69-71 in [`core.go`](https://github.com/banbox/banta/blob/main/core.go)). This method returns `true` when `s.Time >= s.Env.TimeStop`, indicating the series already contains a computed value for the current bar. When cached, indicator functions return the stored result immediately without recalculation.

For new bars, indicators compute values and call `Series.Append` (lines 31-66 in [`core.go`](https://github.com/banbox/banta/blob/main/core.go)). This method:
- Validates the series is not already cached
- Pushes the new value onto the internal slice
- Handles multi-column results (e.g., KDJ outputs three series)
- Updates `s.Time` to match the current environment timestamp

### Handling Stateful Indicators

Some indicators require persistent internal state beyond simple value caching. Examples include ADX, DM (Directional Movement), and other rolling-window calculations that maintain intermediate sums or previous trend directions.

BanTA solves this through the `Series.More` field, an `interface{}` storage for arbitrary state structs. As implemented in [`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go) (lines 79-88), stateful indicators check `Series.More` on first execution. If nil, they initialize a state struct (e.g., `dmState`); subsequent calls reuse this struct, allowing the algorithm to continue calculations from the previous bar’s state.

## Implementing Real-Time Trading with BanTA State-Caching

The following production-ready examples demonstrate how to integrate BanTA into live trading loops. Both examples assume you receive real-time candle data from an exchange WebSocket or REST API.

### Go Implementation

In Go, maintain a map of `BarEnv` instances keyed by symbol-timeframe pairs. This pattern ensures thread-safe isolation between different trading pairs.

```go
import (
	"fmt"
	ta "github.com/banbox/banta"
)

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

func OnBar(symbol, timeframe string, bar *ta.Kline) {
	// Obtain or create the BarEnv for this symbol-timeframe pair
	key := fmt.Sprintf("%s_%s", symbol, timeframe)
	env, exists := envMap[key]
	if !exists {
		env = &ta.BarEnv{
			TimeFrame: timeframe,
			BarNum:    1,
			MaxCache:  2000, // Increase for longer lookback periods
		}
		envMap[key] = env
	}

	// Push the new candle - updates all built-in series and advances time
	env.OnBar(bar.Time, bar.Open, bar.High, bar.Low, bar.Close,
		bar.Volume, bar.Quote, bar.BuyVolume, bar.TradeNum)

	// Compute indicators - automatically reuse cached values
	ma5 := ta.SMA(env.Close, 5)
	ma30 := ta.SMA(env.Close, 30)
	atr := ta.ATR(env.High, env.Low, env.Close, 14).Get(0)

	// Trading logic
	if ta.Cross(ma5, ma30) == 1 {
		cur := env.Close.Get(0)
		stop := cur - atr
		fmt.Printf("Open long at %.4f, stop-loss %.4f\n", cur, stop)
	} else if ta.Cross(ma5, ma30) == -1 {
		fmt.Printf("Close long at %.4f\n", env.Close.Get(0))
	}

	// Multi-column indicator example
	kdj := ta.KDJ(env.High, env.Low, env.Close, 9, 3, 3).Cols
	k, d := kdj[0], kdj[1]
	_ = k
	_ = d
}

```

### Python Implementation

The Python wrapper (`bbta`) generated with **gopy** exposes the same caching semantics. The API mirrors the Go version closely.

```python
from bbta import ta

# Create environment per symbol/timeframe

env = ta.BarEnv(TimeFrame="1m")

# Feed candles as they arrive from exchange

for ts, o, h, l, c, v in klines:
    # Push new bar - updates internal series and timestamp

    env.OnBar(ts, o, h, l, c, v, 0, 0, 0)  # quote, buyVol, tradeNum set to 0

    # Compute indicators (cached automatically)

    ma5 = ta.Series(ta.SMA(env.Close, 5))
    ma30 = ta.Series(ta.SMA(env.Close, 30))
    atr = ta.ATR(env.High, ta.Low(env.Low), env.Close, 14).Get(0)

    # Signal detection

    if ta.Cross(ma5, ma30) == 1:
        cur = env.Close.Get(0)
        print(f"Open long at {cur:.2f}, stop-loss {cur-atr:.2f}")
    elif ta.Cross(ma5, ma30) == -1:
        print(f"Close long at {env.Close.Get(0):.2f}")

    # Multi-column indicator

    k, d, j = ta.KDJ(env.High, env.Low, env.Close, 9, 3, 3).Cols

```

## Configuring Cache Limits and Performance

Memory management in BanTA state-caching mode is controlled through `BarEnv.MaxCache`. The default value of **1500 bars** suits most intraday strategies, but high-timeframe position trading may require larger buffers.

When the cache exceeds `1.5 * MaxCache`, `TrimOverflow` automatically discards the oldest values. This prevents memory leaks during weeks of continuous operation. For ultra-low-latency systems, keep `MaxCache` minimal to improve cache locality, while ensuring it covers your longest indicator lookback period (e.g., 200 bars for a 200-period SMA).

## Summary

- **BanTA state-caching mode** eliminates redundant calculations by incrementally updating indicators as new bars arrive, making it ideal for real-time trading bots.
- The **`BarEnv`** struct manages symbol-timeframe state, while **`Series`** objects store cached indicator values and timestamps.
- **`OnBar`** and **`OnBar2`** handle incoming market data, **`TrimOverflow`** enforces memory limits, and **`Series.Cached`** prevents duplicate computations.
- **Stateful indicators** leverage `Series.More` to persist intermediate calculations across bars (e.g., ADX, DM).
- Both **Go** and **Python** APIs support identical caching semantics, allowing sub-microsecond indicator updates in production trading systems.

## Frequently Asked Questions

### What is the difference between BanTA’s parallel mode and state-caching mode?

**Parallel mode** recomputes every indicator value from the first bar to the current bar on each call, which is computationally expensive but simple for backtesting. **State-caching mode** maintains persistent `Series` objects attached to a `BarEnv`, updating only the newest candle incrementally. This reduces latency from milliseconds to microseconds, making it essential for live trading systems where every tick matters.

### How does BanTA prevent memory leaks during long-running trading sessions?

BanTA implements automatic cache management through `BarEnv.MaxCache` and the `TrimOverflow` method in [`core.go`](https://github.com/banbox/banta/blob/main/core.go) (lines 88-98). When the number of stored bars exceeds 1.5 times the `MaxCache` value (default 1500), the oldest values are discarded. This bounded buffer strategy ensures that memory usage remains predictable even when trading bots run for weeks or months continuously.

### Can I use BanTA state-caching mode with Python for real-time trading?

Yes. BanTA provides a Python wrapper (`bbta`) generated with **gopy** that exposes the same state-caching API as the Go version. You create a `ta.BarEnv`, feed candles via `OnBar`, and call indicator functions like `ta.SMA` or `ta.ATR`. The underlying Go objects handle caching automatically, so Python users receive identical performance benefits for real-time trading applications.

### Which indicators in BanTA require special handling in state-caching mode?

Most indicators work automatically with the standard `Series` caching mechanism, but **stateful indicators** like ADX, DM (Directional Movement), and similar rolling-window algorithms require persistent intermediate state. These indicators store their internal state in the `Series.More` field (an `interface{}` storage) as seen in [`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go) (lines 79-88). The first bar initializes a state struct (e.g., `dmState`), and subsequent bars reuse this struct to continue calculations from the previous step.