Limit Order Book Slope#
Introduction#
Some various slope measures of the limit order book (LOB). A steeply sloping order book indicates a lack of depth beyond the best bid or ask quote, a sign of illiquidity.
Note
The notations below largely follow the respective papers. Therefore, the same symbol may have different meanings.
Certain measures are estimated by (typically hourly) snapshots or intervals, where quantities supplied at each level are aggregated. If so, the functions here should be applied on data by intervals.
Næs and Skjeltorp (2006)#
Næs and Skjeltorp (2006) introduce the slope measure of bid, ask and the LOB. They measure the slope at each price level by the ratio of the increase in depth over the increase in price, then average across all price levels.
where \(N^B\) and \(N^A\) are the total number of bid and ask prices (tick levels) of stock \(i\) (e.g., if we examine 5 levels of LOB, \(N^B\) and \(N^A\) will be 5) and \(\tau\) is the tick level. The best bid (ask) price is denoted by \(p_1^B\) (\(p_1^A\)), i.e. \(\tau=1\). \(p_0\) is the bid-ask midpoint. \(v_{\tau}^B\) and \(v_{\tau}^A\) denote the natural logarithms of the accumulated volume at each tick level \(\tau\) on the bid and ask sides.
Note
If we define \(V^B_\tau\) as the total volume demanded at \(p^B_\tau\), then \(v_{\tau}^B = \ln \left(\sum_{j=1}^\tau V_j^B\right)\).
The LOB slope for stock \(i\) at time \(t\) is then computed as
Intuitively, the bid (ask) slope measures the percentage change in the bid (ask) volume relative to the percentage change in the corresponding bid (ask) price, which is averaged across all limit price levels in the bid (ask) order book, and the LOB slope is an equally weighted average of the bid and ask slopes.
Ghysels and Nguyen (2019)#
Ghysels and Nguyen (2019) take a more direct approach. The slope is estimated from regressing the percentage of cumulative depth on the price distance at each level.
where \(QP_{it\tau}\) is the percentage of cumulative depth at level \(\tau\) at time \(t\) and \(|P_{i\tau} - m_{it}|\) is the distance between the price at level \(\tau\) and the best bid-ask midpoint. \(\beta_i\) is the estimated slope. The bid and ask slope are estimated separately.
Because the cumulative depths are standardized by the total depth across all \(K\) levels, \(QP_{itK}\) is always 100%. As such, the slope measure \(\beta\) depends only on how wide the \(K\)-level pricing grid is (the intercept \(\alpha\) takes care of \(QP_{it1}\)). Accordingly, this slope measure captures the tightness of prices in the book. A steeper slope indicates that limit orders are priced closer to the market. Another way of interpreting the slope is that it measures the elasticity of depth with respect to price. A steeper slope implies that for one unit increase in price, there is a greater increase in the quantity bid or offered, implying a greater willingness of liquidity providers to facilitate trading demand on each side of the market.
Hasbrouck and Seppi (2001)#
Let \(B_k\) and \(A_k\) denote the per share bid and ask for quote record \(k\), and let \(N_k^B\) and \(N_k^A\) denote the respective number of shares posted at these quotes. Thus, a prospective purchaser knows that, if hers is the first market buy order to arrive, she can buy at least \(N_k^A\) shares at the ask price \(A_k\).
Note
These slope measures below are for the whole LOB, not specific to bid or ask.
Quote Slope#
Log Quote Slope#
Della Vedova, Gao, Grant and Westerholm (working paper)#
Slope is measured by the cumulative volume at level \(K\) divided by the distance from the \(K\)-th level price to the bid-ask midpoint.
where \(\text{Depth}_{itx}\) is the sum of the quantity of available at bid/ask depth level \(x\) in stock \(i\) at time \(t\), \(\text{Price}_{it}^K\) is the bid/ask price at the \(K\)-th level, and \(m_{it}\) is the bid-ask midpoint at time \(t\).
Bid-side Slope Difference#
Ask-side Slope Difference#
Scaled Depth Difference#
In addition, the relative asymmetry of depth is also informative about the relative demand and supply of the stock. Scaled Depth Difference (SDD) is constructed to capture the relative asymmetry in the LOB at a particular time up to a particular level \(K\) (e.g., 5).
SDD is essentially the depth difference scaled by the average depth. A value of SDD greater than zero indicates asymmetry in the direction of the ask side of the book.
Note
(TODO) Implementation of all other slope measures.
References#
Næs and Skjeltorp (2006), Order Book Characteristics and the Volume–Volatility Relation: Empirical Evidence from a Limit Order Market, Journal of Financial Markets 9(4), 408–32.
Valenzuela, Zer, Fryzlewicz and Rheinländer (2015), Relative liquidity and future volatility, Journal of Financial Markets, 24, 25–48.
Ghysels and Nguyen (2019), Price discovery of a speculative asset: Evidence from a bitcoin exchange, Journal of Risk and Financial Management, 12(4), 164.
Duong and Kalev (2007), Order Book Slope and Price Volatility, SSRN.
API#
- frds.measures.lob_slope.DGGW_ask_slope(ask_size: ndarray, ask_price_highest_level: ndarray, mid_point: ndarray) float [source]#
Ask Slope from Della Vedova, Gao, Grant and Westerholm (working paper)
- Parameters:
ask_size (np.ndarray) –
(n,k)
array of LOB ask sizes, wheren
is number of quotes andk
is number of levels.ask_price_highest_level (np.ndarray) –
(n,)
array of ask prices at the highest levelk
.mid_point (np.ndarray) –
(n,)
array of bid-ask midpoints.
- Returns:
ask slope
- Return type:
float
Examples
>>> from frds.measures.lob_slope import DGGW_ask_slope >>> import numpy as np >>> rng = np.random.RandomState(42) >>> mid_point = np.ones((1000,)) # Assume bid-ask midpoint stays at 1 >>> lv1 = rng.uniform(low=100, high=120, size=(1000,)) # Simulated level 1 ask sizes >>> lv2 = rng.uniform(low=90, high=110, size=(1000,)) # Simulated level 2 ask sizes >>> lv3 = rng.uniform(low=80, high=100, size=(1000,)) >>> lv4 = rng.uniform(low=70, high=90, size=(1000,)) >>> lv5 = rng.uniform(low=50, high=80, size=(1000,)) >>> ask_size = np.array([lv1, lv2, lv3, lv4, lv5]).T >>> ask_size.shape (1000, 5) >>> ask_price_highest_level = rng.uniform(low=1.1, high=2.0, size=(1000,)) # Simulated ask price at level 5 >>> DGGW_ask_slope(ask_size, ask_price_highest_level, mid_point) 1136.8718207500128
- frds.measures.lob_slope.DGGW_bid_slope(bid_size: ndarray, bid_price_highest_level: ndarray, mid_point: ndarray) float [source]#
Bid Slope from Della Vedova, Gao, Grant and Westerholm (working paper)
- Parameters:
bid_size (np.ndarray) –
(n,k)
array of LOB bid sizes, wheren
is number of quotes andk
is number of levels.bid_price_highest_level (np.ndarray) –
(n,)
array of bid prices at the highest levelk
.mid_point (np.ndarray) –
(n,)
array of bid-ask midpoints.
- Returns:
bid slope
- Return type:
float
Examples
>>> from frds.measures.lob_slope import DGGW_bid_slope >>> import numpy as np >>> rng = np.random.RandomState(42) >>> mid_point = np.ones((1000,)) # Assume bid-ask midpoint stays at 1 >>> lv1 = rng.uniform(low=100, high=120, size=(1000,)) # Simulated level 1 bid sizes >>> lv2 = rng.uniform(low=90, high=110, size=(1000,)) # Simulated level 2 bid sizes >>> lv3 = rng.uniform(low=80, high=100, size=(1000,)) >>> lv4 = rng.uniform(low=70, high=90, size=(1000,)) >>> lv5 = rng.uniform(low=50, high=80, size=(1000,)) >>> bid_size = np.array([lv1, lv2, lv3, lv4, lv5]).T >>> bid_size.shape (1000, 5) >>> bid_price_highest_level = rng.uniform(low=.7, high=.99, size=(1000,)) # Simulated bid price at level 5 >>> DGGW_bid_slope(bid_size, bid_price_highest_level, mid_point) 5316.539811566988
- frds.measures.lob_slope.DGGW_bid_side_slope_difference(bid_size: ndarray, bid_price: ndarray) float [source]#
Bid-side Slope Difference from Della Vedova, Gao, Grant and Westerholm (working paper)
- Parameters:
bid_size (np.ndarray) –
(n,k)
array of LOB bid sizes, wheren
is number of quotes andk
is number of levels.bid_price (np.ndarray) –
(n,k)
array of LOB bid prices, wheren
is number of quotes andk
is number of levels.
- Returns:
bid-side slope difference
- Return type:
float
- frds.measures.lob_slope.DGGW_ask_side_slope_difference(ask_size: ndarray, ask_price: ndarray) float [source]#
Ask-side Slope Difference from Della Vedova, Gao, Grant and Westerholm (working paper)
- Parameters:
ask_size (np.ndarray) –
(n,k)
array of LOB ask sizes, wheren
is number of quotes andk
is number of levels.ask_price (np.ndarray) –
(n,k)
array of LOB ask prices, wheren
is number of quotes andk
is number of levels.
- Returns:
ask-side slope difference
- Return type:
float
- frds.measures.lob_slope.DGGW_scaled_depth_difference(bid_size: ndarray, ask_size: ndarray) float [source]#
SDD from Della Vedova, Gao, Grant and Westerholm (working paper)
- Parameters:
bid_size (np.ndarray) –
(n,k)
array of LOB bid sizes, wheren
is number of quotes andk
is number of levels.ask_size (np.ndarray) –
(n,k)
array of LOB ask sizes, wheren
is number of quotes andk
is number of levels.
- Returns:
scaled depth difference (simple weighted over
n
quotes)- Return type:
float
Examples
>>> from frds.measures.lob_slope import DGGW_scaled_depth_difference >>> import numpy as np >>> rng = np.random.RandomState(42) >>> lv1 = rng.uniform(low=100, high=120, size=(1000,)) # Simulated level 1 bid sizes >>> lv2 = rng.uniform(low=90, high=110, size=(1000,)) # Simulated level 2 bid sizes >>> lv3 = rng.uniform(low=80, high=100, size=(1000,)) >>> lv4 = rng.uniform(low=70, high=90, size=(1000,)) >>> lv5 = rng.uniform(low=50, high=80, size=(1000,)) >>> bid_size = np.array([lv1, lv2, lv3, lv4, lv5]).T >>> lv1 = rng.uniform(low=200, high=300, size=(1000,)) # Simulated level 1 ask sizes >>> lv2 = rng.uniform(low=90, high=110, size=(1000,)) # Simulated level 2 ask sizes >>> lv3 = rng.uniform(low=80, high=100, size=(1000,)) >>> lv4 = rng.uniform(low=70, high=90, size=(1000,)) >>> lv5 = rng.uniform(low=50, high=80, size=(1000,)) >>> ask_size = np.array([lv1, lv2, lv3, lv4, lv5]).T >>> DGGW_scaled_depth_difference(bid_size, ask_size) >>> 0.2697934067000831