Balance of Power (BOP)
Overview
Igor Livshin introduced the Balance of Power indicator in August 2001 through his article "Balance of Market Power" in Stocks and Commodities magazine, creating a unique oscillator that measures who controls each price bar by examining the relationship between opens and closes relative to the trading range. The indicator calculates the distance from open to close divided by the distance from high to low, producing values between -1 and +1 that reveal whether buyers or sellers dominated each period. While Livshin's original methodology involved complex calculations, the formula simplifies elegantly to (Close - Open) / (High - Low), making it computationally efficient while preserving the essential market dynamics. When BOP reads positive, buyers pushed prices higher from open to close, with values near +1 indicating complete buyer control where prices opened at the low and closed at the high. Conversely, negative readings show seller dominance, with -1 representing a bar that opened at the high and closed at the low. Livshin recommended smoothing raw BOP values with a 14-period moving average to filter noise and generate clearer signals, particularly at zero-line crossovers where market control shifts between buyers and sellers. The indicator excels at identifying divergences between price action and internal strength, warning when new price extremes lack the corresponding power shifts that would confirm the move's sustainability.
Implementation Examples
Compute BOP from OHLC slices or candles:
use vector_ta::indicators::bop::{bop, BopInput, BopParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using raw OHLC slices
let open = vec![10.0, 5.0, 6.0, 10.0, 11.0];
let high = vec![15.0, 6.0, 9.0, 20.0, 13.0];
let low = vec![10.0, 5.0, 4.0, 10.0, 11.0];
let close = vec![14.0, 6.0, 7.0, 12.0, 12.0];
let input = BopInput::from_slices(&open, &high, &low, &close, BopParams::default());
let result = bop(&input)?;
// Using with Candles (open/high/low/close sources)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = BopInput::with_default_candles(&candles);
let result = bop(&input)?;
// Access the BOP values
for value in result.values {
println!("BOP: {}", value);
} API Reference
Input Methods ▼
// From OHLC slices
BopInput::from_slices(&[f64], &[f64], &[f64], &[f64], BopParams) -> BopInput
// From candles (uses open/high/low/close sources)
BopInput::from_candles(&Candles, BopParams) -> BopInput
// Candles with default params
BopInput::with_default_candles(&Candles) -> BopInput Parameters Structure ▼
pub struct BopParams {
// Currently empty; reserved for future options
} Output Structure ▼
pub struct BopOutput {
pub values: Vec<f64>, // BOP values per input bar
} Validation, Warmup & NaNs ▼
- Errors:
BopError::EmptyInputDatafor zero-length input;BopError::InputLengthsMismatchif OHLC lengths differ. - Warmup: indices before the first all‑finite OHLC sample are
NaN(prefix fill). - Computation starts at first valid index; per‑bar rule: if
high − low ≤ 0then0.0else(close − open)/(high − low). - Streaming:
BopStream::update(o,h,l,c)returns the value immediately (no warmup state).
Error Handling ▼
use vector_ta::indicators::bop::{bop, BopInput, BopParams, BopError};
let input = BopInput::from_slices(&open, &high, &low, &close, BopParams::default());
match bop(&input) {
Ok(out) => println!("{} values", out.values.len()),
Err(BopError::EmptyInputData) => eprintln!("bop: Input data is empty."),
Err(BopError::InputLengthsMismatch { open_len, high_len, low_len, close_len }) => {
eprintln!("bop: Input lengths mismatch - open: {open_len}, high: {high_len}, low: {low_len}, close: {close_len}");
}
} Python Bindings
Basic Usage ▼
Compute BOP from NumPy arrays; optionally select a kernel:
import numpy as np
from vector_ta import bop
open = np.array([10.0, 5.0, 6.0, 10.0, 11.0], dtype=float)
high = np.array([15.0, 6.0, 9.0, 20.0, 13.0], dtype=float)
low = np.array([10.0, 5.0, 4.0, 10.0, 11.0], dtype=float)
close = np.array([14.0, 6.0, 7.0, 12.0, 12.0], dtype=float)
# Kernel is optional: "auto", "scalar", "avx2", "avx512" (if available)
values = bop(open, high, low, close, kernel="auto")
print(values) Streaming Real-time Updates ▼
State-free update per bar using OHLC inputs:
from vector_ta import BopStream
stream = BopStream()
for (o, h, l, c) in ohlc_feed:
val = stream.update(o, h, l, c)
process(val) Batch Processing ▼
BOP has no tunable parameters; batch returns a single row:
import numpy as np
from vector_ta import bop_batch
open, high, low, close = np.array([...]), np.array([...]), np.array([...]), np.array([...])
out = bop_batch(open, high, low, close, kernel="auto")
print(out['values'].shape) # (1, len)
print(out['rows'], out['cols']) CUDA Acceleration ▼
CUDA helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible). Rust CUDA wrappers are documented below.
import numpy as np
from vector_ta import bop_cuda_batch_dev, bop_cuda_many_series_one_param_dev
open_f32 = np.asarray(open_series, dtype=np.float32)
high_f32 = np.asarray(high_series, dtype=np.float32)
low_f32 = np.asarray(low_series, dtype=np.float32)
close_f32 = np.asarray(close_series, dtype=np.float32)
dev = bop_cuda_batch_dev(open_f32, high_f32, low_f32, close_f32, device_id=0)
tm_open = np.asarray(open_matrix, dtype=np.float32) # (rows, cols)
tm_high = np.asarray(high_matrix, dtype=np.float32) # (rows, cols)
tm_low = np.asarray(low_matrix, dtype=np.float32) # (rows, cols)
tm_close = np.asarray(close_matrix, dtype=np.float32) # (rows, cols)
dev_tm = bop_cuda_many_series_one_param_dev(
tm_open.ravel(), tm_high.ravel(), tm_low.ravel(), tm_close.ravel(),
cols=tm_open.shape[1], rows=tm_open.shape[0], device_id=0,
) JavaScript/WASM Bindings
Basic Usage ▼
Calculate BOP from OHLC arrays:
import { bop_js } from 'vectorta-wasm';
const open = new Float64Array([10, 5, 6, 10, 11]);
const high = new Float64Array([15, 6, 9, 20, 13]);
const low = new Float64Array([10, 5, 4, 10, 11]);
const close = new Float64Array([14, 6, 7, 12, 12]);
const values = bop_js(open, high, low, close);
console.log('BOP:', values); Memory-Efficient Operations ▼
Use zero-copy pointers for large datasets:
import { bop_alloc, bop_free, bop_into, memory } from 'vectorta-wasm';
const open = new Float64Array([/* ... */]);
const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
const close = new Float64Array([/* ... */]);
const len = open.length;
// Allocate WASM buffers
const outPtr = bop_alloc(len);
const openPtr = bop_alloc(len);
const highPtr = bop_alloc(len);
const lowPtr = bop_alloc(len);
const closePtr= bop_alloc(len);
// Copy input arrays into WASM memory
new Float64Array(memory.buffer, openPtr, len).set(open);
new Float64Array(memory.buffer, highPtr, len).set(high);
new Float64Array(memory.buffer, lowPtr, len).set(low);
new Float64Array(memory.buffer, closePtr,len).set(close);
// Compute directly into pre-allocated output
bop_into(openPtr, highPtr, lowPtr, closePtr, outPtr, len);
// Read results (slice() to copy out of WASM memory)
const values = new Float64Array(memory.buffer, outPtr, len).slice();
// Free WASM buffers
[openPtr, highPtr, lowPtr, closePtr, outPtr].forEach(ptr => bop_free(ptr, len)); Batch Processing ▼
BOP has no parameters; batch returns a single row:
import { bop_batch_js, bop_batch_metadata_js } from 'vectorta-wasm';
const values = bop_batch_js(open, high, low, close);
const metadata = bop_batch_metadata_js(); // Empty for BOP
console.log(values.length, metadata.length); CUDA Bindings (Rust)
use vector_ta::cuda::CudaBop;
let cuda = CudaBop::new(0)?;
let open: [f32] = /* ... */;
let high: [f32] = /* ... */;
let low: [f32] = /* ... */;
let close: [f32] = /* ... */;
let out = cuda.bop_batch_dev(&open, &high, &low, &close)?;
let _ = out; Performance Analysis
Across sizes, Rust CPU runs about 3.43× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-02-28
Related Indicators
Accumulation/Distribution
Technical analysis indicator
Accumulation/Distribution Oscillator
Technical analysis indicator
Chaikin Flow Oscillator
Technical analysis indicator
Elder Force Index
Technical analysis indicator
Ease of Movement
Technical analysis indicator
Klinger Volume Oscillator
Technical analysis indicator