Development of Liquidity Forecasting Model
Crypto market liquidity is not constant. It drops sharply during periods of market stress, off-business hours, and upon anomalous events. A liquidity forecasting model allows algorithmic systems to adapt strategy: changing order sizes, widening spreads, deferring execution.
Liquidity Metrics
Bid-Ask Spread: simplest liquidity metric. Tight spread = high liquidity.
Relative Spread = (Ask - Bid) / Mid × 100%
Market Depth: total volume in order book at N% from mid-price. Deep order book will absorb large order without strong slippage.
Amihud Illiquidity Ratio: measures price movement per unit of volume:
def amihud_ratio(returns, volumes, window=24):
"""
High value = large price movement on small volume = low liquidity
"""
illiquidity = np.abs(returns) / (volumes + 1e-8)
return illiquidity.rolling(window).mean()
Kyle's Lambda (price impact): regression of price change on order flow:
def kyles_lambda(price_changes, order_flow, window=100):
"""
λ = Cov(ΔP, OF) / Var(OF)
High λ = large price impact = low liquidity
"""
lambdas = []
for i in range(window, len(price_changes)):
dp = price_changes[i-window:i]
of = order_flow[i-window:i]
cov = np.cov(dp, of)[0, 1]
var = np.var(of)
lambdas.append(cov / max(var, 1e-10))
return lambdas
Liquidity Factors
Temporal patterns:
- Daily cycle: 14:00–22:00 UTC (overlap of European and American sessions) — maximum liquidity
- Weekends — liquidity 20–30% lower
- Holidays — especially low liquidity
Market regime:
- High volatility periods → market-makers widen spreads or leave
- Trending market → asymmetric liquidity (worse on trend side)
Cross-asset effects:
- When BTC falls, overall market liquidity worsens
- Stress events (hack, regulatory) → sharp deterioration
Forecasting Model
import lightgbm as lgb
import pandas as pd
import numpy as np
def create_liquidity_features(df, spread_col='spread', depth_col='depth_1pct'):
features = pd.DataFrame(index=df.index)
# Temporal features
features['hour'] = df.index.hour
features['day_of_week'] = df.index.dayofweek
features['is_weekend'] = (features['day_of_week'] >= 5).astype(int)
features['hour_sin'] = np.sin(2 * np.pi * features['hour'] / 24)
features['hour_cos'] = np.cos(2 * np.pi * features['hour'] / 24)
# Lagged liquidity
for lag in [1, 4, 12, 24, 48]:
features[f'spread_lag_{lag}'] = df[spread_col].shift(lag)
if depth_col in df.columns:
features[f'depth_lag_{lag}'] = df[depth_col].shift(lag)
# Rolling statistics
for window in [12, 24, 72]:
features[f'spread_ma_{window}'] = df[spread_col].rolling(window).mean()
features[f'spread_std_{window}'] = df[spread_col].rolling(window).std()
# Volatility (liquidity proxy)
returns = df['close'].pct_change() if 'close' in df.columns else pd.Series(index=df.index)
for window in [12, 24]:
features[f'vol_{window}h'] = returns.rolling(window).std()
# Volume
if 'volume' in df.columns:
features['vol_ratio'] = df['volume'] / df['volume'].rolling(24).mean()
# Amihud ratio
if 'close' in df.columns and 'volume' in df.columns:
features['amihud'] = amihud_ratio(returns, df['volume'])
return features.dropna()
def train_liquidity_model(liquidity_df, target_col='spread', horizon=4):
"""
Predict spread/liquidity through horizon periods
"""
X = create_liquidity_features(liquidity_df)
y = liquidity_df[target_col].shift(-horizon) # future spread
# Walk-forward split
split_idx = int(len(X) * 0.8)
X_train, X_test = X.iloc[:split_idx], X.iloc[split_idx:]
y_train, y_test = y.iloc[:split_idx], y.iloc[split_idx:]
# Remove NaN from target
valid_mask = y_train.notna()
model = lgb.LGBMRegressor(
n_estimators=500,
learning_rate=0.05,
num_leaves=31,
early_stopping_rounds=50
)
model.fit(
X_train[valid_mask], y_train[valid_mask],
eval_set=[(X_test, y_test.fillna(method='ffill'))],
callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)]
)
return model
Market Impact Assessment
Before executing a large order — forecast its impact on price:
def estimate_market_impact(order_size_usd, current_depth,
current_spread, lambda_estimate):
"""
Simplified Almgren-Chriss model for market impact
"""
# Temporary impact (disappears quickly)
temporary_impact = lambda_estimate * np.sqrt(order_size_usd)
# Permanent impact (market information)
permanent_impact = 0.5 * temporary_impact # usually 50% of temporary
# Spread cost
spread_cost = current_spread / 2 * order_size_usd
total_cost = (temporary_impact + permanent_impact + spread_cost)
total_cost_bps = total_cost / order_size_usd * 10000 # in basis points
return {
'total_impact_usd': total_cost,
'total_impact_bps': total_cost_bps,
'temporary': temporary_impact,
'permanent': permanent_impact,
'spread_cost': spread_cost,
'optimal_execution': total_cost_bps > 10 # need TWAP/VWAP
}
Liquidity Forecasting in Production
Realtime update: liquidity forecast recalculated every 15 minutes for next 4 hours.
Execution integration: before running large order — check liquidity forecast. If deterioration expected (e.g., end of US business day approaching in 2 hours) — accelerate execution now.
Adaptive spreads for market-making: on forecast of liquidity decline — automatically widen quoted spread.
Developing a liquidity forecasting model with temporal features, lagged spread/depth, market regime accounting, market impact estimation (Almgren-Chriss) and integration with execution algorithms.







