# How to Use BanTA for Backtesting Trading Strategies

> Learn to backtest trading strategies with BanTA. Create BarEnv, feed historical data, and use state-caching indicators for accurate live trading simulation.

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

---

**To backtest trading strategies with BanTA, create a `BarEnv` state container for each symbol-timeframe pair, feed historical candles sequentially through `Env.OnBar`, and use state-caching indicator functions like `ta.SMA()` and `ta.Cross()` to generate signals exactly as they would appear in live trading.**

BanTA (Ban Technical Analysis) is a Go-native library from the `banbox/banta` repository that provides two distinct computation models for technical analysis. For accurate strategy validation, you must choose between event-driven state caching—which mimics live trading feeds—and parallel batch computation for rapid historical research.

## Choose Your Backtesting Computation Mode

BanTA supports two primary execution models that serve different backtesting workflows:

### State-Caching Mode (Event-Driven)

State-caching mode updates a **`BarEnv`** on each new candle, caches intermediate indicator results, and re-uses them on subsequent calls. This model, defined in the core library files, is equivalent to TradingView’s event-driven architecture. Use this mode when you need to simulate tick-by-tick feeds or when your strategy logic depends on the sequential state of indicators.

### Parallel Computation Mode (Bulk)

Parallel computation mode, implemented in the `tav` subpackage ([`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go)), calculates every indicator over the entire data series in a single call without caching. This mode returns raw `[]float64` slices and is ideal for bulk research, fast-forward simulations on static data, or vectorized operations across historical datasets.

## Core Architecture for Event-Driven Backtests

The state-casing workflow relies on three fundamental types defined in the BanTA source code:

### BarEnv (State Container)

The **`BarEnv`** type, defined in [[`types.go`](https://github.com/banbox/banta/blob/main/types.go)](https://github.com/banbox/banta/blob/main/types.go#L24-L46), holds per-symbol, per-timeframe state including the current `BarNum`, timestamps, and collections of `Series` for OHLCV fields. You create one environment per instrument and update it via `Env.OnBar` as you iterate through historical candles.

### Series (Time-Ordered Data)

A **`Series`** represents a mutable, time-ordered sequence of `float64` values with helper methods for accessing historical data. The `Get(offset)` method retrieves values at specific indices, while `Back(num)`—implemented in [[`core.go`](https://github.com/banbox/banta/blob/main/core.go)](https://github.com/banbox/banta/blob/main/core.go#L442-L456)—returns a new series omitting the most recent bars, enabling efficient look-back calculations without reprocessing data.

### Indicator Functions

State-caching indicators reside in [[`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go)](https://github.com/banbox/banta/blob/main/sta_inds.go) and include `SMA`, `EMA`, `RSI`, `MACD`, `ATR`, and `Cross`. These functions accept `Series` objects from the current `BarEnv` and automatically cache results across bars. For parallel execution, the `tav` package provides equivalent functions that accept and return plain slices.

## Step-by-Step State-Caching Implementation

The following pattern demonstrates a complete backtesting loop using state-caching mode. This example maintains a global map of environments to handle multiple symbols simultaneously:

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

// Global map of environments – one per symbol‑timeframe
var envMap = make(map[string]*ta.BarEnv)

// OnBar is called for each new K‑line in the historical data set.
func OnBar(symbol, timeframe string, k *ta.Kline) {
    key := fmt.Sprintf("%s_%s", symbol, timeframe)
    env, ok := envMap[key]
    if !ok {
        env = &ta.BarEnv{
            Symbol:    symbol,
            TimeFrame: timeframe,
            BarNum:    1,
        }
        envMap[key] = env
    }

    // Update the environment with the new candle
    env.OnBar(k.Time, k.Open, k.High, k.Low, k.Close,
        k.Volume, k.Quote, k.BuyVolume, k.TradeNum)

    // ----- Indicator calculations (cached) -----
    ma5  := ta.SMA(env.Close, 5)          // 5‑period SMA series
    ma30 := ta.SMA(env.Close, 30)         // 30‑period SMA series
    atr  := ta.ATR(env.High, env.Low, env.Close, 14).Get(0)

    // ----- Strategy logic -----
    // Cross returns +1 for an up‑cross, -1 for a down‑cross
    cross := ta.Cross(ma5, ma30)

    if cross == 1 { // bullish entry
        price := env.Close.Get(0)
        stop  := price - atr
        fmt.Printf("Enter long @ %.4f, stoploss %.4f (bar %d)\n",
            price, stop, env.BarNum)
    } else if cross == -1 { // bearish exit
        price := env.Close.Get(0)
        fmt.Printf("Exit long @ %.4f (bar %d)\n", price, env.BarNum)
    }

    // Example of a multi‑output indicator
    kdj := ta.KDJ(env.High, env.Low, env.Close, 9, 3, 3).Cols
    k, d := kdj[0], kdj[1]
    _ = k; _ = d // use as needed
}

```

**Initialization**: Lazily create a `BarEnv` for each unique symbol-timeframe combination when the first candle arrives.

**Updating**: The `Env.OnBar` method records the new candle, automatically advances `BarNum`, and manages the internal OHLCV series.

**Caching**: The first call to `ta.SMA` creates a `Series` that stores all SMA values; subsequent calls reuse cached data, making `ta.Cross(ma5, ma30)` an O(1) operation per bar.

**Look-back Access**: Retrieve values from *n* bars ago using `env.Close.Back(n).Get(0)`, which references the `Back` method implementation in [`core.go`](https://github.com/banbox/banta/blob/main/core.go).

## Parallel Computation for Vectorized Backtests

For rapid strategy screening across static datasets, use the `tav` package functions which return raw slices:

```go
import "github.com/banbox/banta/tav"

func ParallelBacktest(high, low, close []float64) {
    sma5  := tav.SMA(close, 5)
    sma30 := tav.SMA(close, 30)
    atr14 := tav.ATR(high, low, close, 14)

    // Cross detection on slices
    for i := 1; i < len(sma5); i++ {
        if sma5[i-1] < sma30[i-1] && sma5[i] > sma30[i] {
            fmt.Printf("Cross up at index %d, price %.2f\n", i, close[i])
        }
    }
    _ = atr14 // use as needed
}

```

The `tav.SMA` implementation in [[`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go)](https://github.com/banbox/banta/blob/main/tav/indicators.go#L62-L74) processes the entire input array without maintaining state between calls, making this approach suitable for NumPy-style analysis or pre-computing indicator values before running optimization loops.

## Python Integration for Backtesting

BanTA exposes both computation modes to Python via the `bbta` wheel generated with **gopy**. The API mirrors the Go implementation:

- **State-caching**: Create a `ta.BarEnv` object and call `env.OnBar()` for each candle. Indicator methods return `Series` objects queryable via `.Get(0)`.
- **Parallel**: Import `from bbta import tav` and pass `go.Slice_float64` arrays to receive slice outputs.

This allows Python-based backtesters to leverage BanTA's Go-native performance while maintaining familiar pandas-like workflows.

## Summary

- **State-caching mode** using `BarEnv` provides event-driven backtesting that accurately simulates live trading conditions and minimizes recomputation through automatic indicator caching.
- **Parallel mode** via the `tav` package delivers vectorized bulk calculations on `[]float64` slices for rapid research and optimization.
- The `BarEnv` type manages per-instrument state in [`types.go`](https://github.com/banbox/banta/blob/main/types.go), while `Series` methods like `Back` in [`core.go`](https://github.com/banbox/banta/blob/main/core.go) enable efficient historical data access.
- Indicator functions in [`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go) (state-caching) and [`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go) (parallel) share identical mathematical implementations but differ in data structures and caching behavior.
- Both Go and Python APIs support identical dual-mode workflows, allowing seamless migration from research prototypes to production backtesters.

## Frequently Asked Questions

### What is the difference between state-caching and parallel computation modes?

State-caching mode processes one bar at a time through a `BarEnv`, storing intermediate results for reuse on subsequent bars—ideal for event-driven backtesting that mimics live markets. Parallel mode computes indicators across entire arrays at once using the `tav` package, returning plain slices suitable for vectorized analysis but without maintaining bar-to-bar state.

### How do I handle multiple symbols and timeframes in a single backtest?

Maintain a map of `BarEnv` instances keyed by symbol-timeframe combinations (e.g., `map[string]*ta.BarEnv`). For each incoming candle, construct a unique key, retrieve or create the corresponding environment, and call `env.OnBar()`. This isolates indicator state between instruments while allowing simultaneous strategy evaluation across a universe of assets.

### How can I access indicator values from previous bars in state-caching mode?

Use the `Series.Back(num)` method, which returns a new series offset by the specified number of bars. For example, `env.Close.Back(5).Get(0)` retrieves the closing price from 5 bars ago, while `ta.SMA(env.Close, 20).Back(1).Get(0)` accesses the previous period's moving average value. This implementation in [`core.go`](https://github.com/banbox/banta/blob/main/core.go) avoids expensive recalculation by referencing cached data.

### Can BanTA indicators be used for live trading as well as backtesting?

Yes. The state-caching architecture is designed specifically for live trading bots. The same `BarEnv` and indicator logic used in backtesting can process real-time WebSocket feeds or exchange API data, ensuring that strategy behavior remains identical between historical simulation and production execution.