# Order book imbalance formula: OBI, OFI and edge cases

URL: https://fincodelabs.com/en/blog/order-book-imbalance-formula · Publisher: Fincode (Fincode Yazılım ve Teknoloji A.Ş.), Istanbul, Turkey · Category: Exchange APIs · Published: 2026-11-06 · Updated: 2026-11-06

Order book imbalance formula: (bid - ask) / (bid + ask) by levels or price band, weighted variants, how OFI differs, and tested Python for the edge cases.

**Short answer:** The **order book imbalance formula** is OBI = (B - A) / (B + A), where B is the bid quantity and A the ask quantity over the same part of the book, so the result runs from -1 to +1. The value depends on which part you measure: the top of the book, N levels or a price band around the mid. OBI describes resting quantity at one moment, and this page makes no claim about predicting prices.

Two correct implementations can return opposite signs on one book, as the test below shows.

It is a calculation reference only: no threshold, no backtest and no signal. Naming an exchange is not a recommendation, and what you may do with its data depends on your jurisdiction and the platform's terms. Article 99/A of Capital Markets Law No. 6362 treats activity by foreign crypto-asset platforms directed at Turkish residents as unauthorized.

## What is order book imbalance?

Order book imbalance is the normalized difference between resting bid and ask quantity in a part of the book you choose.

```
OBI = (B - A) / (B + A)

B = sum of bid quantity in scope
A = sum of ask quantity in scope
```

A value of +1 puts all quantity in scope on the bid side, -1 all on the ask side, and 0 means equal quantity. B and A must share a unit, so check each venue's quantity definition before mixing base-asset and contract books. Some code uses r = B / (B + A), from 0 to 1; OBI = 2r - 1.

## Order book imbalance calculation: levels, bands and weights

The open choice is which quantity counts, and it can flip the sign.

**Common scopes for B and A**

| Scope | B and A are | Weak point |
| --- | --- | --- |
| Top of book | Quantity at the best bid and best ask | One small order can set the value |
| N levels | The first N price levels per side | N levels span different distances on different books |
| Distance band | All quantity within d basis points of the mid | A band narrower than the spread leaves a side empty |
| Level weighted | Quantity times a weight such as 1 / rank | The weight is a modeling choice; publish it with the value |

> **Hypothetical book.** Bids: 100.00 x 5, 99.99 x 3, 99.98 x 2, 99.95 x 1, 99.90 x 1. Asks: 100.02 x 1, 100.03 x 4, 100.04 x 4, 100.06 x 6, 100.10 x 5. The mid is 100.01.
>
> **The same book under six scopes**
>
> | Scope | B | A | OBI |
> | --- | ---: | ---: | ---: |
> | Level 1 | 5 | 1 | +0.6667 |
> | Level 3 | 10 | 9 | +0.0526 |
> | Level 5 | 12 | 20 | -0.2500 |
> | Band 5 bps | 10 | 15 | -0.2000 |
> | Band 10 bps | 11 | 20 | -0.2903 |
> | Top 5, weight 1 / rank | 7.6167 | 6.8333 | +0.0542 |

None is wrong; they measure different things, so store the scope with every value.

## Order book imbalance vs order flow imbalance

OBI is a state at one moment, while order flow imbalance (OFI) is a flow that adds up changes at the best bid and ask between observations.

**The difference in one table**

| Item | Order book imbalance | Order flow imbalance |
| --- | --- | --- |
| Input | One snapshot | Consecutive best bid and ask updates |
| Output | Ratio from -1 to +1 | Quantity over an interval, no fixed bound |
| After a missed update | The next snapshot repairs it | Events inside the gap are lost or blurred |

[Cont, Kukanov and Stoikov](https://arxiv.org/abs/1011.6402) score each book event against the previous observation:

```
e_n = I(Pb_n >= Pb_n-1) qb_n  -  I(Pb_n <= Pb_n-1) qb_n-1
    - I(Pa_n <= Pa_n-1) qa_n  +  I(Pa_n >= Pa_n-1) qa_n-1

OFI_k = sum of e_n over interval k
```

Here P and q are the best price and size, b the bid and a the ask. The paper's regressions relate OFI to mid-price changes over the same intervals, not the next ones, on April 2010 NYSE TAQ data for 50 US stocks, not crypto.

[Su, Sun, Li and Yuan](https://arxiv.org/abs/2112.02947) generalize OFI for snapshot data, where the best price can move more than one tick between observations, by summing quantity across the levels it moved through.

**Hypothetical sequence: the two measures can disagree in sign**

| Event | Best bid | Best ask | OBI | e_n | Running OFI |
| --- | --- | --- | ---: | ---: | ---: |
| Bid size rises | 100.00 x 7 | 100.02 x 1 | +0.7500 | +2 | +2 |
| Ask size rises | 100.00 x 7 | 100.02 x 4 | +0.2727 | -3 | -1 |
| Bid price rises | 100.01 x 2 | 100.02 x 4 | -0.3333 | +2 | +1 |
| Best ask removed | 100.01 x 2 | 100.03 x 6 | -0.5000 | +4 | +5 |
| Bid price falls | 100.00 x 9 | 100.03 x 6 | +0.2000 | -2 | +3 |

The sequence starts from 100.00 x 5 and 100.02 x 1.

## Tested Python for OBI and OFI

The script computes every scope above and the OFI sequence from strings, with `Decimal` to avoid binary rounding.

```python
from decimal import Decimal as D

def clean(side, reverse):
    """[price, qty, ...] strings to (price, qty), no zero rows, best price first."""
    rows = [(D(p), D(q)) for p, q, *_ in side]
    if len({p for p, _ in rows}) < len(rows):
        raise ValueError("repeated price level")
    return sorted((r for r in rows if r[1] > 0), key=lambda r: r[0], reverse=reverse)

def obi(bids, asks, levels=None, band_bps=None, weight=lambda i: D(1), full_book=False):
    if levels is not None and levels < 1:
        raise ValueError("levels must be 1 or more")
    b, a = clean(bids, True), clean(asks, False)
    if not b or not a:
        return None                       # a data problem, not +1 or -1
    if b[0][0] >= a[0][0]:
        raise ValueError("crossed book")  # stale or broken input
    if levels and not full_book and levels > min(len(b), len(a)):
        return None                       # fewer rows than levels
    if band_bps is not None:
        mid = (b[0][0] + a[0][0]) / 2
        width = mid * D(band_bps) / 10000
        if not full_book and (b[-1][0] >= mid - width or a[-1][0] <= mid + width):
            return None                   # band may reach past fetched rows
        b = [r for r in b if r[0] >= mid - width]
        a = [r for r in a if r[0] <= mid + width]
        if not b or not a:
            return None                   # band narrower than the spread
    vb = sum(weight(i) * q for i, (_, q) in enumerate(b[:levels]))
    va = sum(weight(i) * q for i, (_, q) in enumerate(a[:levels]))
    return (vb - va) / (vb + va)

def ofi_step(prev, cur):
    """e_n; each input is (bid_px, bid_qty, ask_px, ask_qty)."""
    pb0, qb0, pa0, qa0 = prev
    pb, qb, pa, qa = cur
    return ((qb if pb >= pb0 else 0) - (qb0 if pb <= pb0 else 0)
            - (qa if pa <= pa0 else 0) + (qa0 if pa >= pa0 else 0))

BIDS = [["100.00", "5"], ["99.99", "3"], ["99.98", "2"], ["99.95", "1"], ["99.90", "1"]]
ASKS = [["100.02", "1"], ["100.03", "4"], ["100.04", "4"], ["100.06", "6"], ["100.10", "5"]]
show = lambda x: "None" if x is None else f"{x:+.4f}"
for name, kw in {"level 1": {"levels": 1}, "level 3": {"levels": 3}, "level 5": {"levels": 5},
                 "band 5 bps": {"band_bps": 5}, "band 10 bps": {"band_bps": 10},
                 "1/rank top 5": {"levels": 5, "weight": lambda i: D(1) / (i + 1)}}.items():
    print(f"{name:13}", show(obi(BIDS, ASKS, full_book=True, **kw)))
print("partial book ", show(obi(BIDS, ASKS, band_bps=10)))
print("empty asks   ", show(obi(BIDS, [])))

books = [tuple(map(D, s.split())) for s in ("100.00 5 100.02 1", "100.00 7 100.02 1", "100.00 7 100.02 4",
                                            "100.01 2 100.02 4", "100.01 2 100.03 6", "100.00 9 100.03 6")]
total = 0
for prev, cur in zip(books, books[1:]):
    total += ofi_step(prev, cur)
    print("e_n", f"{ofi_step(prev, cur):+}", "OFI", f"{total:+}")
```

Output on Python 3.11.3, 5 November 2026, synthetic data only:

```
level 1       +0.6667
level 3       +0.0526
level 5       -0.2500
band 5 bps    -0.2000
band 10 bps   -0.2903
1/rank top 5  +0.0542
partial book  None
empty asks    None
e_n +2 OFI +2
e_n -3 OFI -1
e_n +2 OFI +1
e_n +4 OFI +5
e_n -2 OFI +3
```

The `partial book` line treats the same rows as a fetched snapshot: the last ask, 100.10, lies inside the 10 bps band, so quantity beyond it could be missing. On 5 November 2026, Binance Spot's default 100-level BTCUSDT snapshot did not reach 5 bps from the mid on either side, and `obi()` without this check returned in-range but truncated values for 5 and 10 bps bands. Crossed or locked books, `levels` below 1 and repeated prices raise `ValueError`; more `levels` than rows fetched returns `None`. Only synthetic output is published, because venue terms limit showing their data: Kraken's API notice (legacy docs) requires prior permission for any non-personal commercial use of public market data, Binance's API docs defer to its Terms of Use, which did not load for this check, and the [crypto price API guide](https://fincodelabs.com/en/blog/crypto-price-api) covers OKX and Binance Vision.

## Edge cases that break the number

Most wrong values come from the data path, and a bad input often looks like a strong reading.

**Edge cases and how to handle them**

| Case | What goes wrong | Handling |
| --- | --- | --- |
| Empty side | The ratio returns exactly +1 or -1 | Return `None` and alert |
| Crossed or locked book | Best bid at or above best ask, usually stale or out of sync | Raise an error and resync |
| Zero-quantity rows | Binance's depth updates use 0 to remove a level, which distorts level counts if kept | Drop them before counting levels |
| Band wider than fetched depth | Quantity past the last returned level is silently missing | Return `None` unless the last row on each side lies outside the band |
| Stale snapshot | Binance Spot's REST depth response has `lastUpdateId` but no time; USD-M's adds `E` and `T` | Age it with your own clock; reject an ID that does not advance |
| Orders outside the feed | Binance's USD-M depth response excludes Retail Price Improvement orders; iceberg orders show only part of their size | Treat the result as displayed quantity only |

Tick size matters too: regrouping the hypothetical book to 0.10 steps, bids rounded down and asks up, moves level 1 from +0.6667 to -0.6000, level 3 from +0.0526 to -0.2500 and the 10 bps band from -0.2903 to -0.6000, because the mid shifts as well. Only scopes that already held every order, level 5 and a 15 bps band, stay at -0.2500.

## Reading depth for OBI: snapshot or stream

Use a snapshot for occasional readings, a stream for a series, and a scope within the depth delivered.

**Depth sources, official documents read 5 November 2026 (alphabetical)**

| Source | Request | Depth available |
| --- | --- | --- |
| Binance Spot REST | `GET /api/v3/depth` | Default 100, maximum 5,000; weight 5 to 250 |
| Binance Spot partial stream | `<symbol>@depth5`, 10 or 20 | Top 5, 10 or 20, every 1,000 ms or 100 ms |
| Binance USD-M REST | `GET /fapi/v1/depth` | 5, 10, 20, 50, 100, 500 or 1,000 (default 500); weight 2 to 20 |
| Kraken REST | `GET /0/public/Depth` | Default 100, maximum 500; rows are price, volume, timestamp |
| Kraken WebSocket v2 | `book` channel | 10, 25, 100, 500 or 1,000 (default 10) |

Uncompressed sizes on 5 November 2026 (BTCUSDT, XBTUSD), rounded: Binance 6.4 KB at limit 100 and 320 KB at 5,000; Kraken 7.1 KB at count 100 and 35 KB at 500. One `obi()` call on a synthetic book of 5,000 levels per side took 15 to 35 ms across runs (Python 3.11.3), mostly parsing and sorting, so keep a parsed local book for a series.

A full depth stream needs a correct local book first: the snapshot and update-ID procedure is in the [WebSocket order book guide](https://fincodelabs.com/en/blog/websocket-order-book). Tick and lot rules differ by venue, as the [smart order routing guide](https://fincodelabs.com/en/blog/smart-order-routing-crypto) notes, and general plumbing is in [crypto exchange API integration](https://fincodelabs.com/en/blog/crypto-exchange-api-integration).

## What order book imbalance does not tell you

OBI reports displayed quantity at one instant, and it can be canceled before anyone trades against it.

- **No forecast here.** Some glossaries call imbalance predictive without showing evidence; this guide makes no such claim.
- **It can be distorted.** Spoofing and layering fill the book with orders not meant to trade, and they are market abuse. The CFTC's 2013 guidance quotes the Commodity Exchange Act's description of spoofing: "bidding or offering with the intent to cancel the bid or offer before execution". In Turkey, Article 107(1) of Law No. 6362 covers placing, canceling or changing orders to create a false or misleading impression about capital market instruments.

For integration work in general, see [Fincode's exchange API integration page](https://fincodelabs.com/en/services/exchange-api-integration); naming Binance or Kraken here does not mean Fincode integrates them or has built on them. Trading decisions stay yours, and trades can lose money.

## Frequently asked questions

### How many levels should the calculation use?

No document read for this guide sets a standard. Record the scope with every value and keep it fixed.

### What should OBI return when one side of the book is empty?

Treat it as missing data and alert. The raw ratio gives exactly +1 or -1, which looks extreme but usually means a feed or parsing problem.

### Is order book imbalance different in crypto?

The arithmetic is the same, but tick size, depth limits and quantity units differ by venue, so values from different venues are not directly comparable.

### Does order book imbalance predict price?

This page makes no such claim and offers no threshold or backtest. The Cont, Kukanov and Stoikov regressions concern OFI and same-interval price changes in US stocks.

## Sources

- [Cont, Kukanov, Stoikov: The Price Impact of Order Book Events (arXiv 1011.6402, v3, 13 April 2011)](https://arxiv.org/abs/1011.6402)
- [Su, Sun, Li, Yuan: The Price Impact of Generalized Order Flow Imbalance (arXiv 2112.02947, 6 December 2021)](https://arxiv.org/abs/2112.02947)
- [Binance Spot API documentation: REST API (order book endpoint and weights, iceberg parameter)](https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md)
- [Binance Spot API documentation: WebSocket Streams (partial book depth)](https://github.com/binance/binance-spot-api-docs/blob/master/web-socket-streams.md)
- [Binance USD-M Futures: Market data REST API (order book limits, weights, E and T fields, RPI note)](https://developers.binance.com/en/docs/catalog/core-trading-derivatives-trading-usd-s-m-futures/api/rest-api/market-data)
- [Binance Spot API documentation: Terms of Use (refers to the Binance Product Terms of Use; last modified 30 October 2026)](https://developers.binance.com/docs/binance-spot-api-docs/PROD-TERMS-OF-USE)
- [Kraken API: Get Order Book](https://docs.kraken.com/api-reference/market-data/get-order-book)
- [Kraken: Spot WebSocket v2 Book (Level 2) channel](https://docs.kraken.com/exchange/api-reference/spot-websocket-v2/book)
- [Kraken API Center (legacy documentation): Kraken APIs introduction, Notices on commercial use of public endpoint data](https://docs-legacy.kraken.com/api/docs/guides/global-intro/)
- [CFTC: Antidisruptive Practices Authority, interpretive guidance and policy statement (2013)](https://www.cftc.gov/LawRegulation/FederalRegister/FinalRules/2013-12365.html)
- [SPK (Capital Markets Board): Market manipulation in capital markets, Article 107 of Law No. 6362 (Turkish)](https://www.spk.gov.tr/yatirimcilar/sermaye-piyasasinda-piyasa-dolandiriciligi-manipulasyon)
- [Capital Markets Law No. 6362, consolidated text, Articles 99/A and 107 (Turkish)](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf)
- [Python documentation: decimal, decimal fixed-point arithmetic](https://docs.python.org/3/library/decimal.html)

> This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the [editorial policy](https://fincodelabs.com/en/editorial-policy).

---

Published by Fincode (Fincode Yazılım ve Teknoloji A.Ş.), a software company in Istanbul, Turkey: https://fincodelabs.com/en/blog/order-book-imbalance-formula

Cite as: Fincode, "Order book imbalance formula: OBI, OFI and edge cases", updated November 6, 2026, https://fincodelabs.com/en/blog/order-book-imbalance-formula
