Model fill intensity using Hawkes processes for state-dependent kappa estimation. Use when upgrading from simple fill-rate kappa, building fill probability predictions, incorporating queue position dynamics, or adding Hyperliquid-specific features (funding, OI) to fill rate models.
Model the full intensity process for fills, conditional on exchange-specific state. This replaces simple frequency-based kappa estimation with a proper probabilistic model that captures:
measurement-infrastructure for prediction logging and validationsignal-audit to identify which features have predictive power for fillsA self-exciting point process where each event increases the probability of future events:
λ(t) = μ + ∫₀ᵗ α·e^(-β(t-s)) dN(s)
Where:
Stationarity condition: α/β < 1 (excitation must decay faster than it accumulates)
When a trade occurs:
The Hawkes process captures this clustering of fills.
The standard Hawkes is too simple. We extend it with:
The baseline intensity μ varies with market state:
μ(t) = μ₀ · exp(w_F·F(t) + w_OI·ΔOI(t) + w_τ·τ(t) + w_σ·σ(t))
Where:
compute_baseline_intensity() multiplies mu_0 by an exponential of weighted market features (funding, OI change, settlement phase, volatility). See implementation.md for full code.
Different trades have different excitation effects:
compute_excitation() computes per-trade alpha as alpha_base * size_mult * side_mult * aggressor_mult. Size effect is sublinear (exponent 0.3-0.5, capped at 3x), same-side trades get ~1.5x, aggressors get ~1.2x. See implementation.md for full code.
The kernel shouldn't just depend on time—it should depend on queue consumption:
adaptive_kernel() returns exp(-beta * time) * (1 + queue_sensitivity * consumed_fraction) -- standard temporal decay boosted by queue consumption. See implementation.md for full code.
HyperliquidFillIntensityModel contains BaselineParams (mu_0, w_funding, w_oi, w_settlement, w_volatility), ExcitationParams (alpha_base, size_exponent, same_side_mult, aggressor_mult, median_trade_size), KernelParams (beta, queue_sensitivity), depth_half_life_bps, and regime_multipliers. See implementation.md for full struct definitions.
intensity_at(t, recent_trades, queue_position, queue_history, market_state, our_side) -> f64 -- Computes (mu(t) + sum(alpha_i * kernel_i)) * regime_mult. Sums excitation from trades within 60s window.expected_fills_in_window(t_start, t_end, depth_bps, ...) -> f64 -- Numerical integration of intensity over time window with depth decay (exp(-0.693 * depth / half_life)).fill_probability(...) -> f64 -- Returns 1 - exp(-expected_fills) (Poisson survival).See implementation.md for full struct definitions and method bodies.
For initial parameter estimation or periodic retraining. The log-likelihood is sum(log(lambda(t_i))) - integral(lambda(t), 0, T), computed via numerical integration (1000 steps). Optimized with L-BFGS (100 iterations, tolerance 1e-6).
Key functions: hawkes_log_likelihood() and fit_hawkes_model(). See implementation.md for full code.
OnlineHawkesEstimator performs real-time parameter updates via stochastic gradient:
on_fill(): Computes innovation (1 - predicted * dt), applies gradient step to mu_0 (clamped to 0.5x-2.0x per step), decays learning rate.on_no_fill(): If expected fills > 0.5 but none observed, reduces mu_0 proportionally.See implementation.md for full OnlineHawkesEstimator struct and methods.
The GLFT formula uses kappa: fill rate per unit spread. Convert from Hawkes intensity:
intensity_to_kappa() estimates kappa via finite difference: perturbs depth by 0.5 bps, measures fill rate change over a 1-second window, divides by depth change in fraction. Floors at 100.0 to prevent GLFT division issues. See implementation.md for full code.
validate_fill_model() extracts per-level fill predictions vs actual outcomes from PredictionRecord data, then computes Brier score decomposition and calibration curve (20 bins). See implementation.md for full code.
Default regime multipliers:
| Regime | Multiplier | Rationale |
|---|---|---|
| Quiet | 1.0 | Baseline |
| Trending | 0.8 | One side gets picked off, other doesn't fill |
| Volatile | 2.0 | High activity, lots of fills |
| Cascade | 5.0 | Extreme activity, fills are certain but toxic |
These are set via default_regime_multipliers(). See implementation.md for code.
QuoteEngine::compute_kappa() chains: intensity_to_kappa() at 10bps reference depth, then multiplies by adverse selection adjustment and regime-blended kappa multiplier. Result is floored at MIN_KAPPA. See implementation.md for full code.
intensity_to_kappakappa_effective (~3250, blended with adaptive floor). Feature was removed as redundant after the incident. Never compare raw kappa against thresholds — use kappa_effective.on_trade calls in handlers.rs plus the canonical one in messages/trades.rs caused 2x kappa inflation. Removed the duplicates. Always verify there is exactly ONE call site for any fill/trade learning callback.