Last active
September 9, 2024 02:30
-
-
Save nicklatin/14f51a62ff35a362be0757758ea4b313 to your computer and use it in GitHub Desktop.
Computes the high-low spread estimator, an estimate of bid-offer spreads, a measure of liquidity risk. See Corwin & Schultz (2011) for details: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1106193
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# high-low spread estimator (hlse) | |
def hlse(ohlc_df, frequency='daily'): | |
""" | |
Computes the high-low spread estimator, an estimate of bid-offer spreads, a measure of liquidity risk. | |
See Corwin & Schultz (2011) for details: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1106193 | |
Parameters | |
---------- | |
ohlc_df: DataFrame | |
DataFrame with DatetimeIndex and Open, High, Low and Close (OHLC) prices from which to compute the high-low spread estimates. | |
frequency: str, {'daily', 'weekly', 'monthly'}, default 'daily' | |
daily: daily bid-offer spread estimate. | |
weekly: weekly bid-offer spread estimate, resampled over a weekly frequency as the mean of daily estimates. | |
monthly: monthly bid-offer spread estimate, resampled over a monthly frequency as the mean of daily estimates. | |
Returns | |
------- | |
S: Series | |
Datetimeindex and time series of high-low spread estimates. | |
""" | |
# define vars: mid, 2 day high and 2 day low vars | |
mid, high_2d, low_2d = (ohlc_df.high + ohlc_df.low)/2, ohlc_df.high.rolling(2).max(), ohlc_df.low.rolling(2).min() | |
# compute adjustment for overnight price moves | |
ohlc_df['gap_up'], ohlc_df['gap_down'] = ohlc_df.low - ohlc_df.close.shift(1), ohlc_df.high - ohlc_df.close.shift(1) | |
# adjustment for gap up | |
ohlc_df['high_adj'], ohlc_df['low_adj'] = np.where(ohlc_df.gap_up > 0, ohlc_df.high - ohlc_df.gap_up, ohlc_df.high), np.where(ohlc_df.gap_up > 0, ohlc_df.low - ohlc_df.gap_up, ohlc_df.low) | |
# adjustment for gap down | |
ohlc_df['high_adj'], ohlc_df['low_adj'] = np.where(ohlc_df.gap_down < 0, ohlc_df.high - ohlc_df.gap_down, ohlc_df.high), np.where(ohlc_df.gap_down < 0, ohlc_df.low - ohlc_df.gap_down, ohlc_df.low) | |
# B beta | |
B = (np.log(ohlc_df.high_adj/ohlc_df.low_adj))**2 + (np.log(ohlc_df.high_adj.shift(1)/ohlc_df.low_adj.shift(1)))**2 | |
# G gamma | |
G = (np.log(high_2d/low_2d))**2 | |
# alpha | |
alpha = ((np.sqrt(2 * B) - np.sqrt(B)) / (3 - 2 * np.sqrt(2))) - (np.sqrt(G/(3 - 2 * np.sqrt(2)))) | |
# replace negative values by 0 | |
alpha = pd.Series(np.where(alpha < 0, 0, alpha), index=alpha.index) | |
# substitute alpha into equation 14 to get high-low spread estimate S | |
S = (2 * (np.exp(alpha) - 1)) / (1 + np.exp(alpha)) | |
# resample using daily mean | |
if frequency == 'weekly': | |
S = S.resample('W').mean() | |
if frequency == 'monthly': | |
S = S.resample('M').mean() | |
# drop NaNs | |
S.dropna(inplace=True) | |
return S |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment