# How to Detect Crossovers of Indicators with BanTA: Vector and Stateful Methods

> Learn how to detect indicator crossovers with BanTA using vector and stateful methods. Understand `tav.Cross` for historical data and `Series.Cross` for live trading.

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

---

**BanTA provides two crossover detection modes—`tav.Cross` for bulk vector calculations on complete historical series and `Series.Cross` for incremental stateful tracking in live trading environments, both returning signed integers where the absolute value minus one indicates bars since the last cross.**

The **banbox/banta** repository offers a robust technical analysis framework for Go and Python that includes reliable tools to detect crossovers of indicators with BanTA. Whether you are backtesting strategies on complete datasets or processing real-time market data streams, BanTA delivers optimized crossover detection through two distinct APIs. Both implementations monitor the sign change between two data series to identify when an indicator crosses above or below a reference value.

## Understanding BanTA Crossover Detection Modes

BanTA implements crossover detection through complementary vector and stateful approaches. The **vector-style** `tav.Cross` function processes entire slices offline, making it ideal for strategy backtesting. The **stateful** `Series.Cross` method maintains internal history through `CrossLog` structs, designed for incremental updates in live trading bots. Both methods share a consistent return convention: positive values indicate up-crosses (series moves from below to above), negative values indicate down-crosses, and the distance from the crossing event equals `abs(return_value) - 1`.

## Vector-Style Crossover Detection with `tav.Cross`

For bulk analysis of complete time series, BanTA exposes `tav.Cross` in [`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go) (lines 2290-2318). This function accepts two `[]float64` slices and returns a parallel `[]int` slice encoding crossover events.

The implementation iterates through both series simultaneously, calculating the difference at each index. When the sign of the difference changes between consecutive valid bars, the function records a crossover direction and updates the distance counter. The algorithm handles `NaN` values gracefully by propagating the previous state without resetting the distance calculation.

```go
// Cross 计算两个序列在每个时间点的交叉状态。
// 返回值：正数表示上穿，负数表示下穿，0表示无交叉或未知。
// 返回值的绝对值减1 (abs(ret) - 1) 代表了最近一次交叉点到当前元素的距离。
func Cross(data1 []float64, data2 []float64) []int {
    n := len(data1)
    res := make([]int, n)

    // 维护交叉状态的局部变量
    var curSign int           // 最近一次交叉的方向
    var lastIndex = -1        // 最近一次交叉点的索引
    var prevDiff = math.NaN() // 上一个有效点的差值

    for i, v1 := range data1 {
        currentDiff := v1 - data2[i]
        if math.IsNaN(currentDiff) || currentDiff == 0 {
            // 仍然返回上一次的状态（距离递增）
            res[i] = curSign * (i - lastIndex + 1)
            continue
        }

        if !math.IsNaN(prevDiff) {
            // 只要符号相反就记录一次交叉
            if prevDiff*currentDiff < 0 {
                curSign = 1
                if currentDiff < 0 {
                    curSign = -1
                }
                lastIndex = i
            }
        }
        prevDiff = currentDiff
        // 若尚未发生交叉，返回 0；否则返回距离
        if lastIndex >= 0 {
            res[i] = curSign * (i - lastIndex + 1)
        }
    }
    return res
}

```

**Key implementation details:**

- **NaN resilience**: Missing values continue the previous state rather than interrupting the distance count.
- **Direction encoding**: `curSign` stores `+1` for up-crosses and `-1` for down-crosses.
- **Distance calculation**: The value `abs(res[i]) - 1` yields the number of bars elapsed since the last crossover event.

## Stateful Crossover Detection with `Series.Cross`

For live streaming applications where bars arrive incrementally, BanTA provides `Series.Cross` in [`core.go`](https://github.com/banbox/banta/blob/main/core.go) (lines 540-595). This method operates on `*Series` objects and maintains persistent state through the `CrossLog` struct defined in [`types.go`](https://github.com/banbox/banta/blob/main/types.go) (lines 58-66).

The stateful implementation caches comparison history in `Series.XLogs`, a map keyed by the comparison target. For series-to-series comparisons, the key uses the negative ID of the opposing series; for constant values, the key derives from a scaled integer representation. On each new bar, the method compares the current difference against `log.PrevVal`; when the product is negative (indicating a sign change), it appends an `XState` record to `log.Hist`.

```go
// Cross 计算最近一次交叉的距离。比较对象必须是常数或Series对象
// 返回值：正数上穿，负数下穿，0表示未知或重合；abs(ret) - 1表示交叉点与当前bar的距离
func (s *Series) Cross(obj2 interface{}) int {
    var env = s.Env
    var key int
    var v2 float64

    // Resolve the right‑hand operand (Series, int, float32, float64)
    switch v := obj2.(type) {
    case *Series:
        key = -v.ID
        v2 = v.Get(0)
    case int:
        key = v
        v2 = float64(v)
    case float32:
        key = int(v * 100)
        v2 = float64(v)
    case float64:
        key = int(v * 100)
        v2 = v
    default:
        panic(ErrInvalidSeriesVal)
    }

    // Retrieve or create the per‑pair log
    log, newData := s.XLogs[key], false
    if log == nil {
        log = &CrossLog{env.TimeStart, math.NaN(), []*XState{}}
        s.XLogs[key] = log
        newData = true
    } else if env.TimeStart > log.Time {
        newData = true
        log.Time = env.TimeStart
    }

    // If we have a fresh bar, update the log
    if newData {
        diffVal := s.Get(0) - v2
        if diffVal != 0 && !math.IsNaN(diffVal) {
            if math.IsNaN(log.PrevVal) {
                log.PrevVal = diffVal
            } else if log.PrevVal*diffVal < 0 {
                // Sign change → crossing
                log.PrevVal = diffVal
                log.Hist = append(log.Hist, &XState{numSign(diffVal), env.BarNum})
            }
        }
    }

    // Return the most recent crossing distance, if any
    if len(log.Hist) > 0 {
        state := log.Hist[len(log.Hist)-1]
        return state.Sign * (env.BarNum - state.BarNum + 1)
    }
    return 0
}

```

**Key implementation details:**

- **CrossLog persistence**: Each unique comparison pair stores its previous difference value and crossing history in `XLogs`.
- **Incremental updates**: The `newData` flag triggers recalculation only when the environment advances to a new timestamp.
- **Flexible operands**: Accepts `*Series`, `int`, `float32`, or `float64` as the comparison target.

## Python Bindings for Crossover Detection

BanTA exposes the vector crossover function to Python through CGO bindings in [`python/tav/index.go`](https://github.com/banbox/banta/blob/main/python/tav/index.go) (lines 332-338). The `banta.tav.Cross` function maintains identical semantics to its Go counterpart, accepting two float slices and returning a list of integers.

```go
// Cross detects crossovers between two data series.
func Cross(data1 []float64, data2 []float64) []int {
    return banta_tav.Cross(data1, data2)
}

```

Python users can import `banta.tav` to perform vectorized crossover detection on NumPy arrays or standard lists without manual loop implementation.

## Practical Code Examples

### Go Vector Example (Offline Backtesting)

Use `tav.Cross` when analyzing complete historical series to generate entry signals for backtests.

```go
package main

import (
	"fmt"

	"github.com/banbox/banta/main/tav"
)

func main() {
	close := []float64{101, 103, 102, 105, 107, 106}
	sma10 := []float64{100, 100, 101, 102, 103, 104}

	// Cross returns a slice; positive = up‑cross, negative = down‑cross
	cross := tav.Cross(close, sma10)
	fmt.Println("Cross slice:", cross)
	// Example output: [0 1 0 1 1 1] (meaning an up‑cross at index 1, etc.)
}

```

### Go Stateful Example (Live Streaming)

Use `Series.Cross` within a `BarEnv` to detect real-time crosses against price levels or other indicators.

```go
package main

import (
	"fmt"

	"github.com/banbox/banta/main"
)

func main() {
	env, _ := banta.NewBarEnv("binance", "spot", "", "1h")

	// Simulate receiving bars
	for i := 0; i < 5; i++ {
		env.OnBar(0, 0, 0, 0, float64(30000+i*10), 0, 0, 0, 0) // close rises
		// Detect crossing against the constant 30000 level
		distance := env.Close.Cross(30000)
		fmt.Printf("Bar %d – distance to last cross: %d\n", i, distance)
	}
	// Output shows a positive distance once the price moves above 30000.
}

```

### Python Vector Example

Process historical data in Python using the `banta.tav` module.

```python
import banta.tav as tav

close = [101, 103, 102, 105, 107, 106]
sma =  [100, 100, 101, 102, 103, 104]

cross = tav.Cross(close, sma)
print("cross =", cross)   # e.g. [0, 1, 0, 1, 1, 1]

```

### Python Stateful Example

Implement live crossover detection in Python by interacting with the `BarEnv` wrapper.

```python
from banta import BarEnv

env = BarEnv("binance", "spot", "", "1h")

for i in range(5):
    env.on_bar(0, 0, 0, 0, 30000 + i*10, 0, 0, 0, 0)   # close rises

    distance = env.close.cross(30000)   # method provided by Series wrapper

    print(f"Bar {i} → distance = {distance}")

```

## Summary

- **BanTA** offers two methods to detect crossovers of indicators with BanTA: the vector-based `tav.Cross` for batch processing and the stateful `Series.Cross` for incremental updates.
- **Vector detection** ([`tav/indicators.go`](https://github.com/banbox/banta/blob/main/tav/indicators.go)) returns a slice where each element encodes direction and distance since the last cross, ideal for backtesting complete datasets.
- **Stateful detection** ([`core.go`](https://github.com/banbox/banta/blob/main/core.go)) maintains `CrossLog` entries in `Series.XLogs` to track crossing history across individual bars, optimized for live trading environments.
- Both implementations use consistent sign conventions: positive values indicate up-crosses, negative values indicate down-crosses, and `abs(value) - 1` calculates the bar distance.
- Python bindings in [`python/tav/index.go`](https://github.com/banbox/banta/blob/main/python/tav/index.go) provide direct access to vector crossover functionality without leaving the Python runtime.

## Frequently Asked Questions

### What is the difference between `tav.Cross` and `Series.Cross`?

`tav.Cross` performs bulk calculations on complete float slices, returning a full history of crossover events suitable for offline analysis. `Series.Cross` operates incrementally on streaming data, maintaining internal state through `CrossLog` structs to report only the most recent crossing distance, making it suitable for live trading bots.

### How does BanTA handle missing data (NaN values) in crossover detection?

Both implementations gracefully handle `NaN` values by preserving the previous valid state. In `tav.Cross`, missing values propagate the existing `curSign` and continue the distance counter without resetting the crossing history. In `Series.Cross`, `NaN` differences are skipped during the `newData` update phase, preventing false crossover signals.

### Can `Series.Cross` compare an indicator against a constant price level?

Yes. The `Series.Cross` method accepts constants (`int`, `float32`, `float64`) as the comparison operand. It generates a unique map key by scaling float values by 100 or using integer values directly, storing the crossing history separately from series-to-series comparisons.

### How do I interpret the return values from BanTA crossover functions?

A return value of `0` indicates no crossover has occurred yet. Positive values signify an up-cross (the first series moved from below to above the second), while negative values indicate a down-cross. The magnitude follows the formula `abs(return_value) - 1`, which yields the number of bars elapsed since the crossover event occurred.