I am reading a paper "A Simple Way to Estimate Bid-Ask Spreads from Daily High and Low Prices" cf.A Simple Way to Estimate Bid-Ask Spreads from Daily High and Low Prices

The authors proposed the method of estimation the bid-ask spread from high and low prices of consecutive two days.

From what I can understand, there is an important assumption there that the prices follow geometric Brownian motion and, therefore, the true variance over a 2-day period is twice as large as the expectation of the variance over a single day. This property is used for the spread estimation.

Next, assume that I have more data, than just high and low prices, say, 10 min bars.

Will it improve the spread estimator if I use high and low prices of consecutive two 10 min bars instead on days? Does it contradict to the derivation for daily case?


2 Answers 2


If you have access to intraday data, they are better ways to estimate the bid-ask spread. If you have Open, High, Low and Close price on each 5min bin $b$ (or any other interval): the Close of the previous bin and the Open of this one are consecutive. Hence $dP(b)=C(b-1)-O(b)$ allows to define an estimate $\psi(b)$ of the bid-ask spread $$\psi(b):=\min_{b:\, |dP(b)|>0} |dP(b)|.$$

It is not defined on every bin of each day (sometimes $dP(b)=0$), but often you have several of them. You can average them to obtain an estimate for the bid-ask of the day.

Of course you can add an estimate deduced from High and Low of the bin, but it is clearly worst than this $\phi$.

[EDIT following Kri's comment]
There is no academic paper comparing different approaches because anyone with empirical data can compare. Moreover, under common regularity assumption:

  • for the bid-ask spread: the higher frequency the better (of course it is not the same for the volatility --because of the bid-ask spread bounce at least, convicting to the well documented "signature plot" effect--),
  • nevertheless there is a difference at high-frequency between the tie-weighted average spread and the traded-quantity-weighted bid-ask spread. The spread is on average smaller around the trades.
  • $\begingroup$ I can't comment, but wanted to ask about @lehalle's answer - seems intuitive and straightforward - is there any papers empirically comparing these techniques? $\endgroup$
    – kri
    Commented Sep 30, 2021 at 17:01
  • 1
    $\begingroup$ @kri I thought your comment deserved an edit of my answer: this is done $\endgroup$
    – lehalle
    Commented Oct 1, 2021 at 10:46
  • $\begingroup$ Following up @lehalle's comments yet again. The estimate πœ“(𝑏) is a point estimate over some time-interval right (i.e. 60 consecutive 1-min bars to make an hourly estimate). Then when you say "You can average them to obtain an estimate for the bid-ask of the day", do you mean to average separate such hourly estimates of πœ“(𝑏) over some longer time span (e.g. 3-days, or 72 estimates of πœ“(𝑏)). $\endgroup$
    – kri
    Commented Oct 3, 2021 at 21:32
  • $\begingroup$ @kri you average over the day to have a bid-ask spread for the day. Of course there is in fact an intraday seasonality of the bid-ask spread, hence you can choose to average a time-weighted or trade-weighted way; results will be different. $\endgroup$
    – lehalle
    Commented Oct 6, 2021 at 17:19

I implemented and tested the Corwin Shultz method of liquidity calculation from High and low prices as follows: Paper: https://www.scielo.br/j/bar/a/DbHB3rhpfgr8f6qRFKPSMhG/?format=pdf&lang=en

def calculate_corwin_schultz(ohlc_df): # Code written based on Corwin & Schultz (2011) for details: https://www.scielo.br/j/bar/a/DbHB3rhpfgr8f6qRFKPSMhG/?format=pdf&lang=en
# Check for null or infinite values
if ohlc_df['High'].isnull().any() or ohlc_df['Low'].isnull().any():
    print("Null values detected in 'High' or 'Low'.")
    return 0
if np.isinf(ohlc_df['High']).any() or np.isinf(ohlc_df['Low']).any():
    print("Infinite values detected in 'High' or 'Low'.")
    return 0

epsilon = 1e-10  # Small constant to prevent division by zero

beta = (np.log(ohlc_df['High'] / (ohlc_df['Low'] + epsilon)) ** 2).dropna()
beta[beta < (np.sqrt(2) / (3 - 2 * np.sqrt(2))) ** 2] = (np.sqrt(2) / (3 - 2 * np.sqrt(2))) ** 2

gamma = (np.log(ohlc_df['High'].shift(-1) / (ohlc_df['Low'].shift(-1) + epsilon)) ** 2).dropna()

alpha_arg = 2 * beta - np.sqrt(beta) / (3 - 2 * np.sqrt(2))
alpha = (np.sqrt(alpha_arg) - np.sqrt(gamma / (3 - 2 * np.sqrt(2)))).dropna()

S = (2 * (np.exp(alpha) - 1) / (1 + np.exp(alpha))).dropna()

# If standard deviation is less than 3, return mean
print("Standard Deviation:",S.std())
if S.std() < 3:
    return S.mean()
# Else, return Winsorized mean
    return winsorize(S, limits=[0.15, 0.15]).mean()

Basically, after calculating the Spread from the high-low values as described in the paper, we return the mean spread or winsozised means spread depending on the number of outliers in the data. If there are too many outlier liquidities then we winsorize them by 15% (upper and lower) and then return the winsorized spread. This addition ensures a more representative distribution. Additionally, I added a small Elipson number in the denominator ensures that there are no divide-by-zero errors.

From the above code, you can see that if you have more granular data, such as 10-minute bars, the geometric Brownian motion assumption may not hold as strongly. Market microstructure effects, such as intraday volatility patterns, order flow imbalances, and liquidity provision, can significantly impact high and low prices at intraday frequencies. These effects could introduce bias into the Corwin and Schultz estimator if applied to high-frequency data.

Moreover, the Corwin and Schultz estimator relies on the relationship between the variance of high and low prices over two consecutive days. Using 10-minute bars would change this relationship, potentially invalidating the estimator's assumptions and affecting its accuracy.

So, instead of improving the estimator, it will introduce more noise or bias, unless the estimator is adapted and validated for high-frequency data. The original derivation for the daily case may not directly extend to intraday data. It will need some modification for intraday.


Your Answer

By clicking β€œPost Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.