How to Use BanTA for Backtesting Trading Strategies
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), 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#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#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) 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:
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.
Parallel Computation for Vectorized Backtests
For rapid strategy screening across static datasets, use the tav package functions which return raw slices:
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#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.BarEnvobject and callenv.OnBar()for each candle. Indicator methods returnSeriesobjects queryable via.Get(0). - Parallel: Import
from bbta import tavand passgo.Slice_float64arrays 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
BarEnvprovides event-driven backtesting that accurately simulates live trading conditions and minimizes recomputation through automatic indicator caching. - Parallel mode via the
tavpackage delivers vectorized bulk calculations on[]float64slices for rapid research and optimization. - The
BarEnvtype manages per-instrument state intypes.go, whileSeriesmethods likeBackincore.goenable efficient historical data access. - Indicator functions in
sta_inds.go(state-caching) andtav/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 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.
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 →