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.

(1)#\[\text{Bid Slope}_{it} = \frac{1}{N^B} \left\{\frac{v^B_1}{|p^B_1/p_0 -1|} + \sum_{\tau=1}^{N^B-1} \frac{v^B_{\tau+1}/v^B_{\tau}-1}{|p^B_{\tau+1}/p^B_{\tau}-1|} \right\}\]
(2)#\[\text{Ask Slope}_{it} = \frac{1}{N^A} \left\{\frac{v^A_1}{p^A_1/p_0 -1} + \sum_{\tau=1}^{N^A-1} \frac{v^A_{\tau+1}/v^A_{\tau}-1}{p^A_{\tau+1}/p^A_{\tau}-1} \right\}\]

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

(3)#\[\text{LOB Slope}_{it} = \frac{\text{Bid Slope}_{it} + \text{Ask Slope}_{it}}{2}\]

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.

(4)#\[QP_{it\tau} = \alpha_i + \beta_i \times |P_{it\tau} - m_{it}| + \varepsilon_{it\tau}\]

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#

(5)#\[\text{Quote Slope}_k = \frac{A_k - B_k}{\log N^A_k + \log N^B_k}\]

Log Quote Slope#

(6)#\[\text{Log Quote Slope}_k = \frac{\log (A_k / B_k)}{\log N^A_k + \log N^B_k}\]

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.

(7)#\[\text{Bid Slope}_{it} = \frac{\sum_{x=1}^K \text{Bid Depth}_{itx}}{|\text{Bid Price}^K_{it} - m_{it}|}\]
(8)#\[\text{Ask Slope}_{it} = \frac{\sum_{x=1}^K \text{Ask Depth}_{itx}}{\text{Ask Price}^K_{it} - m_{it}}\]

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#

(9)#\[\text{Bid Slope Difference}_{it} = \frac{\text{Bid Depth}_{it5} - \text{Bid Depth}_{it3}}{|\text{Bid Price}_{it5} - \text{Bid Price}_{it3}|} - \frac{\text{Bid Depth}_{it3} - \text{Bid Depth}_{it1}}{|\text{Bid Price}_{it3} - \text{Bid Price}_{it1}|}\]

Ask-side Slope Difference#

(10)#\[\text{Ask Slope Difference}_{it} = \frac{\text{Ask Depth}_{it5} - \text{Ask Depth}_{it3}}{|\text{Ask Price}_{it5} - \text{Ask Price}_{it3}|} - \frac{\text{Ask Depth}_{it3} - \text{Ask Depth}_{it1}}{|\text{Ask Price}_{it3} - \text{Ask Price}_{it1}|}\]

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).

(11)#\[\text{SDD}_{i,t} = 2\times \frac{\sum_{x=1}^K \text{Ask Depth}_{i,t,x}-\sum_{x=1}^K \text{Bid Depth}_{i,t,x}}{\sum_{x=1}^K \text{Ask Depth}_{i,t,x}+\sum_{x=1}^K \text{Bid Depth}_{i,t,x}}\]

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#

API#

frds.measures.lob_slope.NS2006_bid_slope() float[source]#
frds.measures.lob_slope.NS2006_ask_slope() float[source]#
frds.measures.lob_slope.GN2019_slope() float[source]#
frds.measures.lob_slope.HS2001_quote_slope() float[source]#
frds.measures.lob_slope.HS2001_log_quote_slope() float[source]#
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, where n is number of quotes and k is number of levels.

  • ask_price_highest_level (np.ndarray) – (n,) array of ask prices at the highest level k.

  • 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, where n is number of quotes and k is number of levels.

  • bid_price_highest_level (np.ndarray) – (n,) array of bid prices at the highest level k.

  • 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, where n is number of quotes and k is number of levels.

  • bid_price (np.ndarray) – (n,k) array of LOB bid prices, where n is number of quotes and k 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, where n is number of quotes and k is number of levels.

  • ask_price (np.ndarray) – (n,k) array of LOB ask prices, where n is number of quotes and k 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, where n is number of quotes and k is number of levels.

  • ask_size (np.ndarray) – (n,k) array of LOB ask sizes, where n is number of quotes and k 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