11 KiB
BOP: Balance of Power
The market is a tug of war between buyers and sellers. BOP tells you who's pulling harder.
| Property | Value |
|---|---|
| Category | Momentum |
| Inputs | OHLCV bar (TBar) |
| Parameters | None |
| Outputs | Single series (BOP) |
| Output range | Varies (see docs) |
| Warmup | 0 bars (always hot) |
| PineScript | bop.pine |
- The Balance of Power measures buying versus selling pressure by comparing the body (Close minus Open) to the range (High minus Low).
- No configurable parameters; computation is stateless per bar.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
The Balance of Power measures buying versus selling pressure by comparing the body (Close minus Open) to the range (High minus Low). Created by Igor Livshin in 2001, this ratio oscillates between -1 and +1, providing instantaneous momentum readings with zero lag. A stateless indicator: each bar evaluated independently, no memory of previous values required.
Historical Context
Igor Livshin introduced BOP in the August 2001 issue of Stocks & Commodities magazine. The concept emerged from a simple observation: the relationship between open and close within a bar's range reveals buyer-seller dynamics more directly than price movement alone.
The brilliance lies in normalization. A large-bodied candle on a narrow-range day might represent significant conviction. A similar-sized body on a wide-range day might indicate indecision. BOP captures this nuance by measuring body size relative to range, not absolute body size.
Unlike momentum oscillators built on smoothed averages, BOP provides raw, unfiltered readings. This makes it noisy but responsive: ideal for detecting immediate shifts in market sentiment that lagging indicators miss.
The indicator found adoption in high-frequency and scalping strategies where milliseconds matter. Zero warmup, zero lag, zero state: BOP computes the same value regardless of historical context.
Architecture & Physics
BOP operates as a pure function: input bar, output ratio. No internal state, no memory, no recursion.
1. Body Calculation
The body measures directional movement within the bar:
Body = Close - Open
Positive body indicates closing above opening (buyers prevailed). Negative body indicates closing below opening (sellers prevailed). Zero body (doji) indicates equilibrium.
2. Range Calculation
The range measures total price movement:
Range = High - Low
Range provides the denominator for normalization. Larger ranges indicate higher volatility; smaller ranges indicate consolidation.
3. Ratio Computation
The core BOP formula:
BOP = \frac{Close - Open}{High - Low}
This ratio is bounded:
- Maximum +1: Close equals High, Open equals Low (perfect bullish bar)
- Minimum -1: Close equals Low, Open equals High (perfect bearish bar)
- Zero: Close equals Open (doji or no movement)
4. Zero-Range Handling
When High equals Low (no range), division by zero would occur. QuanTAlib returns zero:
BOP = \begin{cases}
\frac{Close - Open}{High - Low} & \text{if } High > Low \\
0 & \text{if } High = Low
\end{cases}
The epsilon threshold is double.Epsilon (~5×10⁻³²⁴), handling floating-point edge cases.
5. Interpretation Matrix
| BOP Value | Interpretation | Market State |
|---|---|---|
| +1.0 | Maximum bullish | Close=High, Open=Low |
| +0.5 to +1.0 | Strong buying pressure | Buyers dominant |
| 0 to +0.5 | Mild buying pressure | Slight buyer edge |
| 0 | Equilibrium | Balanced or doji |
| -0.5 to 0 | Mild selling pressure | Slight seller edge |
| -1.0 to -0.5 | Strong selling pressure | Sellers dominant |
| -1.0 | Maximum bearish | Close=Low, Open=High |
6. Noise Characteristics
BOP produces high-frequency signals with no smoothing. Signal-to-noise ratio depends entirely on timeframe and instrument volatility. Common practice: smooth with SMA(14) or EMA(10) for trend identification while preserving raw readings for entry timing.
Mathematical Foundation
Closed-Form Expression
BOP admits no transfer function: it is a ratio, not a filter. No z-domain representation applies because there is no temporal dependency.
Statistical Properties
For random price movements (no trend):
E[BOP] = 0
The expected value is zero when buyers and sellers have equal strength over time.
Variance
Variance depends on the distribution of body sizes relative to ranges:
Var(BOP) = E\left[\left(\frac{Close - Open}{High - Low}\right)^2\right] - E[BOP]^2
Empirically, BOP variance increases during trending periods (bodies consistently directional) and decreases during consolidation (bodies randomly directional).
Correlation with Returns
BOP correlates with bar-level returns but captures different information:
Return_t = \frac{Close_t - Close_{t-1}}{Close_{t-1}}
Returns measure change from previous close; BOP measures efficiency of movement within current bar. A gap-up bar with a bearish body produces positive return but negative BOP.
Performance Profile
Operation Count (Streaming Mode, Scalar)
Per-bar update is minimal:
| Operation | Count | Cost (cycles) | Subtotal |
|---|---|---|---|
| SUB (body) | 1 | 1 | 1 |
| SUB (range) | 1 | 1 | 1 |
| CMP (range > ε) | 1 | 1 | 1 |
| DIV (body/range) | 1 | 15 | 15 |
| Total | 4 | — | ~18 cycles |
Division dominates (83%). Predictable branch (range > 0 almost always true) avoids misprediction penalties.
Batch Mode (SIMD Analysis)
The Calculate method is fully vectorized:
| Operation | Scalar Ops | SIMD Ops (AVX2) | Speedup |
|---|---|---|---|
| Body calculation | N | N/4 | 4× |
| Range calculation | N | N/4 | 4× |
| Division | N | N/4 | 4× |
| Conditional select | N | N/4 | 4× |
Full 4× speedup with AVX2 (double precision). With AVX-512, achieves 8× speedup. No recursive dependencies: entire calculation parallelizes.
Benchmark Results
Test environment: AMD Ryzen 9 7950X, 128 GB DDR5-6000, .NET 10.0, Windows 11 24H2
| Operation | Time (μs) | Throughput | Allocations |
|---|---|---|---|
| Streaming 100K bars | 89 | 1.12B bars/s | 0 bytes |
| Batch 100K bars | 23 | 4.35B bars/s | 0 bytes |
The batch mode approaches memory bandwidth limits rather than compute limits.
Comparative Performance
| Indicator | Cycles/bar | Relative |
|---|---|---|
| BOP | 18 | 1.0× |
| EMA | 21 | 1.17× |
| RSI | 73 | 4.06× |
| ATR | 26 | 1.44× |
| Bollinger | 156 | 8.67× |
BOP is among the fastest indicators: no state, no memory, no warmup.
Quality Metrics
| Metric | Score | Notes |
|---|---|---|
| Accuracy | 10/10 | Exact mathematical formula |
| Timeliness | 10/10 | Zero lag (instantaneous) |
| Overshoot | N/A | Bounded [-1, +1] by construction |
| Smoothness | 2/10 | Raw signal, very noisy |
| Responsiveness | 10/10 | Immediate reaction to price action |
| State Complexity | 1/10 | No state required |
Validation
Validated against four external libraries across all operating modes.
| Library | Batch | Streaming | Span | Notes |
|---|---|---|---|---|
| TA-Lib | ✅ | ✅ | ✅ | Exact match with TA_BOP |
| Skender | ✅ | ✅ | ✅ | Exact match with GetBop |
| Tulip | ✅ | ✅ | ✅ | Exact match with ti.bop |
| Ooples | ✅ | — | — | Exact match (batch only) |
Tolerance: exact match (ratio of integers produces identical floating-point results).
Common Pitfalls
-
Noise Sensitivity: Raw BOP is extremely volatile. Single-bar spikes or dips rarely indicate trend changes. Smooth with SMA(14) or similar before making trend judgments. Use raw BOP for entry timing within an established trend direction.
-
Gap Handling: Gaps create disconnect between bars. A gap-up followed by bearish bar produces negative BOP despite overall bullish day. Consider combining with gap analysis for complete picture.
-
Doji Interpretation: BOP equals zero when Close equals Open. This indicates indecision, not necessarily equilibrium. High-range dojis and low-range dojis both produce BOP = 0 but carry different implications.
-
TValue Input Limitation: The
Update(TValue)method returns zero because BOP requires OHLC data. Using TValue input produces meaningless results. Always useUpdate(TBar)for proper calculation. -
Period Selection for Smoothing: When smoothing BOP, period choice affects signal timing. Shorter periods (7-10) preserve responsiveness; longer periods (14-21) reduce whipsaws. Match to trading timeframe.
-
Extreme Value Rarity: BOP reaching ±1.0 is rare: requires Close at High/Low and Open at Low/High. Values beyond ±0.8 typically indicate strong conviction. Use these for confirmation, not entry signals.
-
Volume Ignorance: BOP ignores volume entirely. A high-BOP bar on low volume carries different weight than same BOP on high volume. Consider pairing with volume analysis.
Usage Examples
Streaming Mode
var bop = new Bop();
foreach (var bar in liveStream)
{
TValue result = bop.Update(bar);
if (result.Value > 0.6)
Console.WriteLine($"{bar.Time}: Strong buying pressure ({result.Value:F2})");
else if (result.Value < -0.6)
Console.WriteLine($"{bar.Time}: Strong selling pressure ({result.Value:F2})");
}
Batch Mode
var bars = new TBarSeries();
// ... populate with historical data ...
TSeries bopSeries = Bop.Batch(bars);
// Calculate average BOP over lookback
double avgBop = bopSeries.Values.TakeLast(14).Average();
Span-Based Calculate
ReadOnlySpan<double> open = openPrices;
ReadOnlySpan<double> high = highPrices;
ReadOnlySpan<double> low = lowPrices;
ReadOnlySpan<double> close = closePrices;
Span<double> bopValues = stackalloc double[open.Length];
Bop.Calculate(open, high, low, close, bopValues);
Smoothed BOP Chain
var bop = new Bop();
var sma = new Sma(14);
foreach (var bar in liveStream)
{
TValue rawBop = bop.Update(bar);
TValue smoothBop = sma.Update(rawBop);
// Use raw for timing, smooth for direction
bool trendBullish = smoothBop.Value > 0;
bool strongEntry = rawBop.Value > 0.5;
if (trendBullish && strongEntry)
Console.WriteLine("Bullish entry signal");
}
References
- Livshin, I. (2001). "Balance of Power." Technical Analysis of Stocks & Commodities, August 2001.
- Investopedia. "Balance of Power (BOP) Indicator." https://www.investopedia.com/terms/b/bop.asp
- Achelis, S. (2000). Technical Analysis from A to Z. McGraw-Hill. (General indicator theory)