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

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 (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:

// 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 (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, 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:

// 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, 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.

// 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, lines 52-55) containing individual output series. This parallels Pine Script's tuple unpacking where k, d = kdj(...) assigns multiple values.

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:

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:

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 – 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 – 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 – Provides the high-level description of state-caching and the explicit TradingView/Pine Script reference (lines 7-9, 111-112).

  • tav/indicators.go and 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →