I implemented and tested the Corwin Shultz method of liquidity calculation from High and low prices as follows:
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'.")
if np.isinf(ohlc_df['High']).any() or np.isinf(ohlc_df['Low']).any():
print("Infinite values detected in 'High' or 'Low'.")
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
if S.std() < 3:
# 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.