Bulls v Bears
period = 14 | ma_type = ema | calculation_method = normalized | normalized_bars_back = 120 | raw_rolling_period = 50 | raw_threshold_percentile = 95 | threshold_level = 80 Overview
Bulls v Bears measures how far each bar extends above and below a moving-average anchor, then combines those opposing pressures into a regime line. The raw bull component is the distance from the bar high to the anchor, while the raw bear component is the distance from the anchor to the bar low. That gives the indicator a more structured read than a single oscillator because the internal bull and bear legs remain available alongside the composite value.
VectorTA supports two interpretation modes. The normalized mode rescales recent bull and bear ranges into a bounded spread and uses symmetric trigger bands. The raw mode keeps the spread in native units and derives rolling trigger levels from the recent range. In both cases the output also includes threshold crossings and zero-line cross markers, which makes the indicator useful for both regime analysis and event-style signal logic.
Defaults: Bulls v Bears uses `period = 14`, `ma_type = "ema"`, `calculation_method = "normalized"`, `normalized_bars_back = 120`, `raw_rolling_period = 50`, `raw_threshold_percentile = 95.0`, and `threshold_level = 80.0`.
Implementation Examples
Compute the full bull-vs-bear regime surface from slices or from candle data.
use vector_ta::indicators::bulls_v_bears::{
bulls_v_bears,
BullsVBearsInput,
BullsVBearsParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
let high = vec![101.0, 102.2, 103.0, 102.8, 104.1, 104.6];
let low = vec![99.4, 100.8, 101.2, 101.0, 102.5, 103.0];
let close = vec![100.5, 101.6, 102.4, 101.9, 103.6, 104.0];
let output = bulls_v_bears(&BullsVBearsInput::from_slices(
&high,
&low,
&close,
BullsVBearsParams::default(),
))?;
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = bulls_v_bears(&BullsVBearsInput::with_default_candles(&candles))?;
println!("value = {:?}", output.value.last());
println!("bullish signal = {:?}", output.bullish_signal.last());
println!("zero cross up = {:?}", candle_output.zero_cross_up.last()); API Reference
Input Methods ▼
// From candles
BullsVBearsInput::from_candles(&Candles, BullsVBearsParams)
-> BullsVBearsInput
// From high, low, close slices
BullsVBearsInput::from_slices(&[f64], &[f64], &[f64], BullsVBearsParams)
-> BullsVBearsInput
// From candles with default parameters
BullsVBearsInput::with_default_candles(&Candles)
-> BullsVBearsInput Parameters Structure ▼
pub struct BullsVBearsParams {
pub period: Option<usize>, // default 14
pub ma_type: Option<BullsVBearsMaType>, // default Ema
pub calculation_method: Option<BullsVBearsCalculationMethod>, // default Normalized
pub normalized_bars_back: Option<usize>, // default 120
pub raw_rolling_period: Option<usize>, // default 50
pub raw_threshold_percentile: Option<f64>, // default 95.0
pub threshold_level: Option<f64>, // default 80.0
} Output Structure ▼
pub struct BullsVBearsOutput {
pub value: Vec<f64>,
pub bull: Vec<f64>,
pub bear: Vec<f64>,
pub ma: Vec<f64>,
pub upper: Vec<f64>,
pub lower: Vec<f64>,
pub bullish_signal: Vec<f64>,
pub bearish_signal: Vec<f64>,
pub zero_cross_up: Vec<f64>,
pub zero_cross_down: Vec<f64>,
} Validation, Warmup & NaNs ▼
- The high, low, and close slices must all be non-empty and have identical lengths.
- The indicator requires at least one bar where high, low, and close are all finite; otherwise it returns
AllValuesNaN. period,normalized_bars_back, andraw_rolling_periodmust all be greater than zero.raw_threshold_percentilemust be finite and stay within80.0..=99.0.threshold_levelmust be finite and stay within0.0..=100.0.- Warmup depends on the moving-average type: EMA starts immediately, while SMA and WMA warm up for
period - 1bars. - Streaming returns
NaNplaceholders when a bar or the anchor state is invalid rather than throwing. - Batch range expansion rejects invalid integer or float ranges, and non-batch kernels are rejected at the batch entry point.
Builder, Streaming & Batch APIs ▼
// Builder
BullsVBearsBuilder::new()
.period(usize)
.ma_type(BullsVBearsMaType)
.calculation_method(BullsVBearsCalculationMethod)
.normalized_bars_back(usize)
.raw_rolling_period(usize)
.raw_threshold_percentile(f64)
.threshold_level(f64)
.kernel(Kernel)
.apply_slices(&[f64], &[f64], &[f64])
BullsVBearsBuilder::new()
.apply(&Candles)
BullsVBearsBuilder::new()
.into_stream()
// Stream
BullsVBearsStream::try_new(BullsVBearsParams)
BullsVBearsStream::update(high, low, close)
-> (f64, f64, f64, f64, f64, f64, f64, f64, f64, f64)
// Batch
BullsVBearsBatchBuilder::new()
.range(BullsVBearsBatchRange)
.kernel(Kernel)
.apply_slices(&[f64], &[f64], &[f64]) Error Handling ▼
pub enum BullsVBearsError {
EmptyInputData,
AllValuesNaN,
InconsistentSliceLengths { high_len: usize, low_len: usize, close_len: usize },
InvalidPeriod { period: usize },
InvalidNormalizedBarsBack { normalized_bars_back: usize },
InvalidRawRollingPeriod { raw_rolling_period: usize },
InvalidRawThresholdPercentile { raw_threshold_percentile: f64 },
InvalidThresholdLevel { threshold_level: f64 },
OutputLengthMismatch { expected: usize, got: usize },
InvalidRange { start: String, end: String, step: String },
InvalidKernelForBatch(Kernel),
} Python Bindings
Python exposes a dictionary-returning single-run function, a streaming class, and a batch function. The single-run binding returns NumPy arrays for every output channel: value, bull, bear, ma, upper, lower, bullish_signal, bearish_signal, zero_cross_up, and zero_cross_down. Batch returns those same channels as two-dimensional matrices plus the tested parameter axes.
import numpy as np
from vector_ta import (
bulls_v_bears,
bulls_v_bears_batch,
BullsVBearsStream,
)
high = np.asarray(high_values, dtype=np.float64)
low = np.asarray(low_values, dtype=np.float64)
close = np.asarray(close_values, dtype=np.float64)
result = bulls_v_bears(
high,
low,
close,
period=14,
ma_type="ema",
calculation_method="normalized",
normalized_bars_back=120,
raw_rolling_period=50,
raw_threshold_percentile=95.0,
threshold_level=80.0,
kernel="auto",
)
print(result["value"], result["bullish_signal"])
stream = BullsVBearsStream(
period=14,
ma_type="ema",
calculation_method="normalized",
normalized_bars_back=120,
raw_rolling_period=50,
raw_threshold_percentile=95.0,
threshold_level=80.0,
)
print(stream.update(high[-1], low[-1], close[-1]))
batch = bulls_v_bears_batch(
high,
low,
close,
period_range=(10, 20, 5),
normalized_bars_back_range=(80, 120, 40),
raw_rolling_period_range=(50, 50, 0),
raw_threshold_percentile_range=(95.0, 95.0, 0.0),
threshold_level_range=(70.0, 90.0, 10.0),
ma_type="ema",
calculation_method="normalized",
kernel="auto",
)
print(batch["periods"], batch["rows"], batch["cols"]) JavaScript/WASM Bindings
The WASM layer exposes object-returning single-run and batch wrappers plus lower-level allocation and in-place exports. The normal JavaScript path returns plain objects containing the same ten output arrays as the Rust API. The batch wrapper adds the tested parameter lists together with the final rows and cols shape.
import init, {
bulls_v_bears_js,
bulls_v_bears_batch_js,
} from "/pkg/vector_ta.js";
await init();
const high = new Float64Array(highValues);
const low = new Float64Array(lowValues);
const close = new Float64Array(closeValues);
const result = bulls_v_bears_js(
high,
low,
close,
14,
"ema",
"normalized",
120,
50,
95.0,
80.0,
);
console.log(result.value, result.zero_cross_up);
const batch = bulls_v_bears_batch_js(high, low, close, {
period_range: [10, 20, 5],
normalized_bars_back_range: [80, 120, 40],
raw_rolling_period_range: [50, 50, 0],
raw_threshold_percentile_range: [95.0, 95.0, 0.0],
threshold_level_range: [70.0, 90.0, 10.0],
ma_type: "ema",
calculation_method: "normalized",
});
console.log(batch.periods, batch.threshold_levels, batch.rows, batch.cols); CUDA Bindings (Rust)
Additional details for the CUDA bindings can be found inside the VectorTA repository.
Performance Analysis
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)