Overview
Order Book Imbalance, also known as Order Flow Imbalance, is a widely recognized microstructure indicator often analyzed alongside trade flow. This concept has several derivatives, including:
- Micro-Price
- VAMP (Volume Adjusted Mid Price)
- Weighted-Depth Order Book Price
- Static Order Book Imbalance
Static Order Book Imbalance
Static Order Book Imbalance=∑iNQbidi+∑iNQaski∑iNQbidi−∑iNQaski
Through standardization, an alternative expression for order imbalance can be obtained:
Standardized Order Book Imbalance=standardize(i∑NQbidi−i∑NQaski)
Volume Adjusted Mid Price (VAMP)
Note that price and quantity are cross-multiplied between bid and ask sides:
VAMPbbo=Qbest bid+Qbest askPbest bid×Qbest ask+Pbest ask×Qbest bid
VAMPN=∑iNQbidi+∑iNQaski∑iNPbidi×Qaski+∑iNPaski×Qbidi
where N is usually defined as a percentage of the mid price. For 1% market depth, compute bid side down to mid × 0.99 and ask side up to mid × 1.01.
Weighted-Depth Order Book Price
Note that price and quantity are multiplied within the same side:
Weighted-Depth Order Book Price=∑iNQbidi+∑iNQaski∑iNPbidi×Qbidi+∑iNPaski×Qaski
In this case, N is defined by a fixed total quantity. For 500 qty of market depth, compute bid side down to its total quantity reaches 500 and ask side up to its total quantity reaches 500.
Implementation
import numpy as np
from numba import njit
from numba.typed import Dict
from hftbacktest import BUY, SELL, GTX, LIMIT
@njit
def obi_mm(
hbt,
stat,
half_spread,
skew,
c1,
looking_depth,
interval,
window,
order_qty_dollar,
max_position_dollar,
grid_num,
grid_interval,
roi_lb,
roi_ub
):
asset_no = 0
imbalance_timeseries = np.full(30_000_000, np.nan, np.float64)
tick_size = hbt.depth(0).tick_size
lot_size = hbt.depth(0).lot_size
t = 0
roi_lb_tick = int(round(roi_lb / tick_size))
roi_ub_tick = int(round(roi_ub / tick_size))
while hbt.elapse(interval) == 0:
hbt.clear_inactive_orders(asset_no)
depth = hbt.depth(asset_no)
position = hbt.position(asset_no)
orders = hbt.orders(asset_no)
best_bid = depth.best_bid
best_ask = depth.best_ask
mid_price = (best_bid + best_ask) / 2.0
# Calculate order book imbalance
sum_ask_qty = 0.0
from_tick = max(depth.best_ask_tick, roi_lb_tick)
upto_tick = min(int(np.floor(mid_price * (1 + looking_depth) / tick_size)), roi_ub_tick)
for price_tick in range(from_tick, upto_tick):
sum_ask_qty += depth.ask_depth[price_tick - roi_lb_tick]
sum_bid_qty = 0.0
from_tick = min(depth.best_bid_tick, roi_ub_tick)
upto_tick = max(int(np.ceil(mid_price * (1 - looking_depth) / tick_size)), roi_lb_tick)
for price_tick in range(from_tick, upto_tick, -1):
sum_bid_qty += depth.bid_depth[price_tick - roi_lb_tick]
imbalance_timeseries[t] = sum_bid_qty - sum_ask_qty
# Standardize the order book imbalance
m = np.nanmean(imbalance_timeseries[max(0, t + 1 - window):t + 1])
s = np.nanstd(imbalance_timeseries[max(0, t + 1 - window):t + 1])
alpha = np.divide(imbalance_timeseries[t] - m, s)
# Compute bid and ask prices
order_qty = max(round((order_qty_dollar / mid_price) / lot_size) * lot_size, lot_size)
fair_price = mid_price + c1 * alpha
normalized_position = position / order_qty
reservation_price = fair_price - skew * normalized_position
bid_price = min(np.round(reservation_price - half_spread), best_bid)
ask_price = max(np.round(reservation_price + half_spread), best_ask)
bid_price = np.floor(bid_price / tick_size) * tick_size
ask_price = np.ceil(ask_price / tick_size) * tick_size
# Update quotes (order management code)
# ...
t += 1
stat.record(hbt)
Key Parameters
- half_spread: Base half spread in price units
- skew: Position-based skew coefficient
- c1: Alpha signal multiplier
- looking_depth: Depth percentage from mid-price for imbalance calculation (e.g., 0.025 = 2.5%)
- interval: Strategy update interval in nanoseconds
- window: Rolling window size for standardization
- order_qty_dollar: Dollar amount per order
- max_position_dollar: Maximum position in dollar terms
References
The order book imbalance signal works best when:
- Market depth is sufficient and stable
- The asset has reasonable liquidity
- Parameters are properly calibrated for the specific asset
- Rebates are available to offset trading costs
Typical results show:
- Sharpe Ratio: 10-15 (annualized)
- Daily number of trades: 4,000-4,500
- Return per trade: 0.008-0.014% (including rebates)
The effectiveness of this strategy heavily depends on having access to maker rebates. Without rebates, the strategy may not be profitable after accounting for trading fees.