# Pine Script-Like Features in BanTA's State-Caching Mode

> Explore BanTA's state-caching mode and its Pine Script-like features. Discover event-driven calculations, automatic history storage, and built-in cross detection for efficient analysis.

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

---

**BanTA's state-caching mode implements an event-driven, incremental-calculation model identical to Pine Script, using Series objects with automatic history storage, per-bar caching, and built-in cross detection.**

The `banbox/banta` library brings TradingView's Pine Script semantics to Go and Python through a sophisticated state-caching architecture. Unlike vectorized backtesting libraries that require the entire price history on every calculation, BanTA maintains persistent state across bars, allowing indicators to update incrementally just like Pine Script's built-in variables.

## Core Pine Script-Like Features

### Series Objects with Automatic History

BanTA's `Series` type functions exactly like Pine Script's series values. Defined in [`types.go`](https://github.com/banbox/banta/blob/main/types.go) (lines 48-55), each `Series` maintains a `Data []float64` slice that stores the entire history of calculated values.

Access the current bar's value using `Get(0)`, mirroring Pine Script's `close` or `ma` syntax:

```go
// Current value (equivalent to Pine Script's 'close')
current := e.Close.Get(0)

// Previous bar (equivalent to 'close[1]')
previous := e.Close.Get(1)

```

### Per-Bar Caching with Cached()

The `Cached()` method in [`core.go`](https://github.com/banbox/banta/blob/main/core.go) (lines 69-71) prevents redundant calculations by checking whether the series already contains a value for the current bar index. If cached, the method returns immediately without recomputing the indicator logic.

This mechanism replicates Pine Script's internal caching where built-in functions like `sma()` or `rsi()` compute only once per bar regardless of how many times they are referenced.

### Event-Driven Updates via OnBar

BanTA uses `BarEnv.OnBar` (implemented in [`core.go`](https://github.com/banbox/banta/blob/main/core.go), lines 28-45) as the central event handler, called once per incoming candle. This method appends new OHLCV data to the environment and triggers updates for all derived series.

The workflow mirrors Pine Script's execution model where the script runs once per bar update:

```go
// Called for each new candle - equivalent to Pine Script's execution on each bar
func OnBar(e *ta.BarEnv, k *ta.Kline) {
    e.OnBar(k.Time, k.Open, k.High, k.Low, k.Close, k.Volume, 
            k.Quote, k.BuyVolume, k.TradeNum)
    
    // Indicators now available for this bar
    ma5 := ta.SMA(e.Close, 5)
}

```

### Cross Detection with XLogs

The `Series.Cross` method (found in [`core.go`](https://github.com/banbox/banta/blob/main/core.go), lines 73-95) implements Pine Script-style crossover detection using a per-series `XLogs` field. It tracks the last crossing point and returns the distance to the most recent cross, functioning identically to Pine Script's `crossover()` and `crossunder()` functions.

```go
// Returns 1 if series1 crossed above series2 on this bar
if ta.Cross(ma5, ma30) == 1 {
    fmt.Println("Bullish crossover detected")
}

```

### Multi-Output Indicators

Multi-output indicators like `KDJ` return a `Series` with a `Cols` field (defined in [`types.go`](https://github.com/banbox/banta/blob/main/types.go), lines 52-55) containing individual output series. This parallels Pine Script's tuple unpacking where `k, d = kdj(...)` assigns multiple values.

```go
kdj := ta.KDJ(e.High, e.Low, e.Close, 9, 3, 3)
k := kdj.Cols[0].Get(0)  // K line
d := kdj.Cols[1].Get(0)  // D line
j := kdj.Cols[2].Get(0)  // J line

```

## Implementation Examples

### Go Implementation

The following example demonstrates the complete event-driven workflow using BanTA's state-caching mode:

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

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

// Called for each new candle
func OnBar(symbol, timeframe string, k *ta.Kline) {
	key := fmt.Sprintf("%s_%s", symbol, timeframe)
	e, ok := envs[key]
	if !ok {
		// First bar for this symbol-timeframe
		e = &ta.BarEnv{TimeFrame: timeframe, BarNum: 1}
		envs[key] = e
	}
	// Update environment with the new bar
	_ = e.OnBar(k.Time, k.Open, k.High, k.Low, k.Close,
		k.Volume, k.Quote, k.BuyVolume, k.TradeNum)

	// Indicators – just like PineScript functions
	ma5  := ta.SMA(e.Close, 5)          // returns a *Series
	ma30 := ta.SMA(e.Close, 30)
	atr  := ta.ATR(e.High, e.Low, e.Close, 14).Get(0)

	// Cross detection – same semantics as crossover()
	if ta.Cross(ma5, ma30) == 1 {
		fmt.Printf("MA5 crossed up MA30 at %.2f\n", e.Close.Get(0))
	}

	// Multi-output indicator (KDJ)
	kdj := ta.KDJ(e.High, e.Low, e.Close, 9, 3, 3)
	k, d := kdj.Cols[0].Get(0), kdj.Cols[1].Get(0)
	fmt.Printf("K=%.2f D=%.2f ATR=%.2f\n", k, d, atr)
}

```

### Python Implementation

Using the `bbta` wrapper, Python developers can access the same state-caching semantics:

```python
from bbta import ta

# Create a BarEnv for the 1-minute chart

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

# Simulated candle stream

klines = [
    (1672531200000, 100, 102, 99, 101, 1000),
    (1672531260000, 101, 103, 100, 102, 1200),
    # … more candles …

]

for ts, o, h, l, c, v in klines:
    env.OnBar(ts, o, h, l, c, v, 0, 0, 0)

    # Indicators

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

    # Cross detection (same semantics as PineScript's crossover())

    if ta.Cross(ma5, ma30) == 1:
        print(f"MA5 crossed up MA30 on close={c}")

    # KDJ – multi-output indicator

    kdj = ta.KDJ(env.High, env.Low, env.Close, 9, 3, 3)
    k, d = kdj.Cols[0].Get(0), kdj.Cols[1].Get(0)
    print(f"Close={c:.2f} MA5={ma5.Get(0):.2f} K={k:.2f} D={d:.2f} ATR={atr:.2f}")

```

## Key Source Files

Understanding these source files reveals how BanTA achieves Pine Script compatibility:

- **[`types.go`](https://github.com/banbox/banta/blob/main/types.go)** – Defines `Series` and `BarEnv` (lines 48-55). Shows how historic values are stored in the `Data []float64` slice and accessed via `Get()`, and how `Cols` enables multi-output indicators.

- **[`core.go`](https://github.com/banbox/banta/blob/main/core.go)** – Implements `BarEnv.OnBar` (lines 28-45), `Series.Cached` (lines 69-71), and `Series.Cross` (lines 73-95). Demonstrates the event-driven update mechanism, per-bar caching logic, and cross-detection using `XLogs`.

- **[`readme.md`](https://github.com/banbox/banta/blob/main/readme.md)** – Provides the high-level description of state-caching and the explicit TradingView/Pine Script reference (lines 7-9, 111-112).

- **[`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go)** and **[`sta_inds.go`](https://github.com/banbox/banta/blob/main/sta_inds.go)** – Contain concrete indicator implementations (SMA, ATR, KDJ) that return cached `*Series`, illustrating how ordinary TA functions behave like Pine Script built-ins.

## Summary

BanTA's state-caching mode replicates Pine Script's execution model through these key mechanisms:

- **Series objects** store complete history in `Data []float64`, accessible via `Get(0)` for the current bar and `Get(1)` for previous bars, matching Pine Script's series indexing.
- **Automatic per-bar caching** via `Series.Cached()` prevents redundant calculations, ensuring indicators compute only once per candle.
- **Event-driven architecture** using `BarEnv.OnBar` updates all series incrementally when new candles arrive, identical to Pine Script's per-bar execution loop.
- **Built-in cross detection** through `Series.Cross` and `XLogs` provides `crossover()` and `crossunder()` semantics without manual state management.
- **Multi-output support** via the `Cols` field allows indicators like KDJ to return multiple series simultaneously, matching Pine Script's tuple unpacking.

## Frequently Asked Questions

### How does BanTA's state-caching mode differ from vectorized backtesting libraries?

Vectorized libraries like Pandas or NumPy require passing the entire price history to calculate indicators on every iteration, causing O(n²) complexity when running walk-forward analysis. BanTA's state-caching mode maintains persistent `Series` objects that update incrementally via `OnBar`, achieving O(1) per-bar performance regardless of history length, exactly like Pine Script's execution engine.

### Can I use BanTA's state-caching features in Python without writing Go code?

Yes. The `bbta` Python wrapper exposes the complete state-caching API to Python developers. You create a `ta.BarEnv` object, call `OnBar` for each incoming candle, and access indicators through `ta.SMA`, `ta.ATR`, and other functions that operate on cached Series objects. The wrapper handles all Go runtime interactions transparently.

### How does `Series.Get(0)` compare to Pine Script's `close` variable?

In Pine Script, `close` refers to the closing price of the current bar. In BanTA, `e.Close.Get(0)` provides identical functionality, returning the current bar's value from the cached `Data` slice. Both systems use index-based history access where index 0 is current, index 1 is previous, maintaining semantic parity between Pine Script's series and BanTA's `Series` type.

### What happens if I call the same indicator multiple times on the same bar?

BanTA's `Series.Cached()` method detects when a value has already been computed for the current bar index and returns the cached result immediately. This prevents redundant calculations when an indicator is referenced multiple times in your strategy logic, ensuring optimal performance identical to Pine Script's built-in memoization of series values.