Source confidence: high

Avellaneda-Stoikov + Hawkes Processes — Market Making and Order-Flow Alpha

In short: The two canonical building blocks of academic HFT: a closed-form model for where to quote (Avellaneda-Stoikov) and a statistical model for when trades cluster (Hawkes). Together they describe the two halves of a market maker's problem — price the quotes, then time the quotes.

Key fact Detail
What it covers creative coding, crypto, data science, finance/trading
Tools Meta
Posts covered 1 post (1 S-tier) · top: 10 institutional quant projects…
Creators @heidioftheeast
Sources dated Feb 2026
Prompt system Run-it-yourself prompts
Last verified 2026-10-22

The two canonical building blocks of academic HFT: a closed-form model for where to quote (Avellaneda-Stoikov) and a statistical model for when trades cluster (Hawkes). Together they describe the two halves of a market maker's problem — price the quotes, then time the quotes.

Cloned repo: repos/avellaneda-stoikov/ (from https://github.com/fedecaccia/avellaneda-stoikov)


1. Avellaneda-Stoikov — the canonical market-making model

What it is

A 2008 paper by Marco Avellaneda and Sasha Stoikov, "High Frequency Trading in a Limit Order Book," that solves a specific problem: a market maker holds inventory q, faces a mid-price s that diffuses as Brownian motion, and must choose bid/ask quotes continuously. Where should they post to maximize terminal utility while controlling inventory risk?

The answer is two formulas — the reservation price and the optimal spread — and those two formulas are the foundation for essentially every serious MM model that followed.

How it works

The maker has a utility function (exponential, risk-averse with parameter γ). They post quotes symmetrically around a reservation price that is shifted away from mid based on current inventory. If you're long, you skew quotes lower to encourage sells. If you're short, you skew higher. The spread itself widens with volatility, time-to-horizon, and risk aversion.

Key mechanics — the two formulas

Reservation price (your private indifference price given inventory):

r(s, q, t) = s − q · γ · σ² · (T − t)

Optimal bid-ask spread:

δ_ask + δ_bid = γ · σ² · (T − t) + (2/γ) · ln(1 + γ/k)

Then you post:

The first term of the spread is the inventory term (widens with risk). The second is the adverse selection term (widens when order flow is thin). This is the whole engine.

The repo — fedecaccia/avellaneda-stoikov

Cloned to repos/avellaneda-stoikov/. Minimal Python implementation, ~200 lines. Core loop from src/main.py:

# Risk aversion
gamma = 0.1
# Liquidity parameter (exponential decay of fill intensity)
k = 1.5
# Mid-price follows Brownian motion
sigma = 2

for i in range(N):
    # Reservation price — skews away from inventory
    r[i] = s[i] - q[i] * gamma * (sigma**2) * (T - t[i])
    # Optimal half-spread
    r_spread = gamma * (sigma**2) * (T - t[i]) + (2/gamma) * math.log(1 + gamma/k)
    # Quotes
    r_ask = r[i] + r_spread / 2
    r_bid = r[i] - r_spread / 2
    # Simulate Poisson fills with intensity A*exp(-k*delta)
    ...

The repo includes a doc/ folder with the original paper and follow-ups (Guéant-Lehalle-Tapia closed-form extension, mean-reversion variants). Good starting point, but known limitations: Brownian mid is unrealistic, k and A need live calibration, no queue-position model.

Quick start

cd ./repos/avellaneda-stoikov/
pip install numpy matplotlib
python src/main.py

Runs 100 simulated sessions and plots PnL distribution. Tweak gamma, k, sigma and watch how the spread and skew react.

Known limitations (and what the follow-ups fix)

For a modern RL extension, see javifalces/HFTFramework — reinforcement learning on top of AS for dynamic γ and k.


2. Hawkes processes — order flow is self-exciting

What it is

A Hawkes process is a point process where the arrival of an event temporarily increases the probability of more events. Classic example: earthquakes (a big one triggers aftershocks). In markets: a trade begets more trades. Order flow is demonstrably self-exciting — if you watch BTC-PERP on Hyperliquid for 10 seconds, you see this with your own eyes. Buys cluster. Sells cluster. Quiet periods are quiet.

Avellaneda-Stoikov assumes trades arrive as a plain Poisson process (constant intensity A). That assumption is wrong at HFT timescales. Replacing it with a Hawkes intensity is the single biggest realism upgrade.

How it works — the math

A univariate Hawkes process has a conditional intensity:

λ(t) = μ + Σ_{t_i < t} α · exp(−β (t − t_i))

For order flow, you want a bivariate or multivariate Hawkes with buy and sell streams that excite each other asymmetrically. The 4-dimensional version (bid market orders, ask market orders, bid limit orders, ask limit orders) is the standard for LOB research.

Key intuition

Repos worth pulling

Quick start with tick

from tick.hawkes import HawkesExpKern, SimuHawkesExpKernels
import numpy as np

# Fit a 2D Hawkes (buys, sells) on your trade timestamps
# timestamps = [np.array([buy_times]), np.array([sell_times])]
decays = np.array([[5.0, 5.0], [5.0, 5.0]])  # β per pair
learner = HawkesExpKern(decays, penalty='l2', C=1e3)
learner.fit(timestamps)

# adjacency[i][j] = α from stream j exciting stream i
print("Branching matrix:\n", learner.adjacency)
print("Baseline:", learner.baseline)
print("Spectral radius (stability):", np.max(np.abs(np.linalg.eigvals(learner.adjacency))))

Roll this on 1-minute windows and you have a live order-flow-clustering monitor.


3. Putting them together — the modern MM stack

The research consensus for the last ~8 years has been: replace Avellaneda-Stoikov's constant A, k with Hawkes-derived, regime-aware estimates.

The combined loop:

  1. Maintain a rolling Hawkes fit on trade timestamps (fit every N seconds).
  2. Convert the fitted intensity into a live forecast λ̂(t) for the next fill window.
  3. Feed λ̂ into AS as a time-varying A. Also use branching ratio to modulate γ — get more risk-averse when n → 1.
  4. Skew quotes with the AS reservation price, sized with AS spread, but gated by the Hawkes regime (don't quote when n is critical unless spread compensates).
  5. (Optional) Train an RL agent to choose γ dynamically — that's what javifalces/HFTFramework and im1235/ISAC (Soft Actor-Critic on AS) do.

4. How this plugs into your trading-agent-team

The current trading-agent-team at ~/projects/agents/trading-agent-team/ is built around taker strategies — fast cyclers, graduated TP, ensembles across 8 coins. AS + Hawkes opens a second leg of the strategy: a maker leg that generates uncorrelated PnL from spread capture rather than directional alpha.

Concrete integration ideas:

  1. Hawkes as a regime filter for the existing taker strategies. Fit a bivariate Hawkes on trade tape per coin. When branching ratio spikes above ~0.9, the fast-cycler TP logic should tighten because reversals become more likely. This is a pure overlay — no new strategy, just a kill switch / sizing knob. Lowest-risk way to start.

  2. Order-flow imbalance as a feature for the Oracle Commander / Rolodex selector. Add the ratio of buy-excited to sell-excited intensity as a short-horizon feature. Should add signal to coin rotation decisions at 1-5 minute horizons.

  3. Hyperliquid maker leg. Hyperliquid pays maker rebates. A disciplined AS quoter on a liquid pair (HYPE, SOL) sized small could produce steady edge and double as inventory for the taker strategies — when the maker leg accumulates a long, the taker leg can ride it out instead of fighting it. This is the pattern HL-Delta uses in ./repos/HL-Delta/.

  4. Challenge-game parameter sweep. AS has exactly the shape that /challenge-game was built for: 3 numeric parameters (γ, k, horizon_T), a clear fitness function (Sharpe of simulated PnL), and fast evaluation. Run a tournament against replayed Hyperliquid L2 data.

  5. Watchdog layer for Unicron. The Hawkes branching ratio is a clean scalar "how crazy is order flow right now" number. Pipe it into the Unicron oversight watchdog (project_unicron_oversight_spec.md) as an auto-pause trigger when it crosses a threshold.

Data requirement: All of this needs trade-level tape, not OHLCV. Hyperliquid's WebSocket gives trade ticks for free — that's the data path. For order-book state (needed for full Hawkes LOB models) you need L2 snapshots, which flowsurface in ./repos/flowsurface/ already handles in Rust.


5. Further reading

Sources


Prompt system — run this yourself

Everything below is pasteable into Claude Code, Claude, or any agent. Use the one-shot when you want the whole result in one run; use the step prompts when you want to steer. Every step carries its own audit, and the gap audit at the end makes the agent find and fill what it missed.

Mission. Running this system turns the guide into a tested research stack: a verified Avellaneda-Stoikov (AS) quoting engine, a Hawkes order-flow model fitted and goodness-of-fit tested on real Hyperliquid trade data, and an event-driven paper backtest comparing a fixed-spread quoter, plain AS and Hawkes-modulated AS after fees, queue position and adverse selection. It also tests the lowest-risk integration: a Hawkes branching-ratio regime overlay on the existing taker strategies, run as a paired shadow. It is for a trader with a running crypto bot stack. Done means reproducible results against controls and a written gate: no real money until the paper arm beats its control after costs.

1 · Deeper, not bigger

Before running anything, make the task deeper rather than larger. Ask: what would a top professional do here that I am not asking for; what is the rawest material; which methods and tools; what, other than the model or me, proves it right. Then ask for exactly that.

Rewritten prompt — ask for exactly that:

I have the Avellaneda-Stoikov repo cloned and a Hawkes guide. Act as a market-making quant and build a research stack I can trust, paper only. First read the AS paper, Guéant-Lehalle-Fernandez-Tapia, and Bacry et al. on Hawkes in finance, and verify the repo's src/main.py against the paper's formulas, including whether its spread variable is total or half spread. Then: (1) reproduce the paper's inventory-versus-symmetric simulation; (2) record Hyperliquid trades and L2 for BTC and ETH for at least 7 days, aggregating multi-fill taker orders into single events; (3) fit bivariate exponential Hawkes on buys and sells per window, check fit by time-rescaling and a KS test, and report branching ratio over time; (4) calibrate A and k from hit rate versus quote depth, in tick units; (5) backtest event-driven with queue position, latency and the real fee schedule: fixed-spread control, plain AS, and AS with Hawkes-driven A and gamma, scored on markout PnL at 1, 10 and 60 seconds; (6) run the branching-ratio overlay as a paired shadow on the existing taker bots. No real orders until the paper arm beats its control after costs, with inventory and daily-loss caps written first. Never print API keys.

2 · One-shot prompt

ROLE AND STANCE
You are a market-making quant validating two academic models before any capital touches them. Paper only. You distrust closed forms until units and calibration are checked, and you judge quoting by markouts, not spread captured.

CONTEXT
- Guide: AS reservation price r = s - q * gamma * sigma^2 * (T - t); total spread = gamma * sigma^2 * (T - t) + (2/gamma) * ln(1 + gamma/k); quotes at r +/- spread/2; fills at intensity A * exp(-k * delta).
- Repo: repos/avellaneda-stoikov/ (fedecaccia), about 200 lines, gamma 0.1, k 1.5, sigma 2, Brownian mid, 100 simulated sessions. The code comments its spread variable as a half-spread but computes the total spread formula and then divides by two; verify which is right.
- Known limits listed in the guide: Brownian mid, fixed k, symmetric fills, no queue model, single asset.
- Hawkes: lambda(t) = mu + sum alpha * exp(-beta (t - t_i)); branching ratio n = alpha/beta must be below 1; n near 0.9 means clustered, dangerous to quote. Note: in tick's HawkesExpKern the adjacency matrix is already the kernel's integral, so it is the branching ratio directly; do not divide by beta again.
- Guide's integration idea: a Hawkes branching-ratio overlay on the trading-agent-team taker strategies as a kill switch or sizing knob, lowest risk first.
- Venue: Hyperliquid perpetuals; trades and l2Book websocket streams.

HARD CONSTRAINTS
- No real orders. The gate to real money: paper arm beats both controls on markout-adjusted PnL after fees over at least 14 days of out-of-sample data, bootstrap interval excludes zero, and a 2-week live paper-quoting run (simulated fills against the real tape) agrees. Then start at the minimum order size with a per-coin inventory cap, a daily loss stop at 0.5% of the allocated account and an automatic kill switch, all written before the first order.
- Never promise returns.
- Load keys from environment only; never print, log or commit them.
- Work in tick units for prices and seconds for time; state units for every parameter.

DELIVERABLES, IN ORDER
1. verify_as.md: formulas in src/main.py checked against the paper; reproduction of the paper's inventory versus symmetric table.
2. recorder/: websocket recorder for trades and L2, Parquet output, gap detection.
3. hawkes/: event aggregation (multiple prints from one taker order at one timestamp become one event), bivariate exponential fit per 5-minute window, time-rescaling KS test, branching-ratio series.
4. calib/: A and k per side from hit rate versus depth, rolling.
5. backtest/: event-driven simulator with queue position, latency, fees; arms: fixed spread, plain AS (GLT infinite-horizon form), AS with Hawkes-driven A and gamma that rises as n approaches 1; metrics: PnL, markouts at 1, 10, 60 s, inventory std, max drawdown, fill rate.
6. overlay/: paired shadow of the taker strategies with and without a branching-ratio gate, same period.
7. gate.md: the real-money gate, caps and kill switch, plus the current pass or fail.

GAP ANALYSIS (BUILT IN)
Keep gaps.md: whether tick installs on your Python version; tie handling in timestamps; L2 snapshot frequency versus true queue dynamics; fee tier assumptions; whether the archive covers the period; claims in the guide that you could not reproduce. Fill what you can.

SELF-QUESTIONS
- Are sigma and k in tick and second units, and does the spread come out a plausible number of ticks?
- Did the Hawkes fit pass time-rescaling, or am I reading a branching ratio from a bad model?
- Are ties from one taker order inflating self-excitation?
- Does AS beat the fixed-spread control after markouts, or only before?
- Does the overlay reduce drawdown or just reduce trades?

STOP CONDITIONS
Stop and report if recorded data has gaps over 5% of the window, if no Hawkes fit passes the KS test, or if every arm loses to the fixed-spread control after costs. Otherwise stop when all seven deliverables exist.

RETURN FORMAT
Table of deliverable, path, status; the backtest metrics table per arm with bootstrap intervals; KS pass rate; overlay result; gate status; top five gaps.

3 · Step-by-step prompts

Step 1 — Ground truth: verify the repo against the paper

Goal: Make sure the formulas and code agree before anything is built on them.

Open repos/avellaneda-stoikov/. Read the AS 2008 paper in doc/ and Guéant-Lehalle-Fernandez-Tapia 2013.
1. Transcribe the paper's reservation price and spread formulas and compare with src/main.py line by line. The code comments 'optimal half-spread' but computes gamma*sigma^2*(T-t) + (2/gamma)*ln(1+gamma/k), which the paper gives as the total spread; it then posts r +/- r_spread/2. Decide which is correct and write it down with the equation number.
2. Run python src/main.py with numpy and matplotlib. Then write a reproduction script for the paper's comparison of the inventory strategy against the symmetric strategy at gamma 0.1, 0.01 and 0.5: mean PnL, PnL std, final inventory std over 1,000 simulations. Compare with the paper's table.
3. Write the GLT infinite-horizon approximations for a 24/7 perpetual where T is undefined.
4. Check whether tick installs on your Python; if not, record the error and plan a scipy fallback.

Expect: verify_as.md with line-by-line comparison, a reproduction table next to the paper's, GLT formulas, install notes.

Audit before moving on: Reproduced numbers must be within sampling error of the paper; if not, find the difference (time step, fill model, random seed count) before continuing. Any unresolved formula mismatch blocks the backtest.

Step 2 — Record Hyperliquid trades and L2

Goal: Build the raw dataset everything else is fitted on.

Write recorder/record.py: subscribe to Hyperliquid's public trades and l2Book websocket streams for BTC and ETH. Store each trade with exchange timestamp (ms), side, price, size and any trade or order identifiers the feed gives, and each L2 update with timestamp and top 20 levels. Write hourly Parquet files. Reconnect with backoff and log every disconnect with its duration.
Run it for at least 7 days on a machine that stays up. In parallel, check whether Hyperliquid publishes historical trade or L2 archives you can use to extend the window, and note format and cost.
Write recorder/check.py: per hour, count trades, count disconnect seconds, flag hours with more than 1% missing time, and report the distribution of trades per millisecond timestamp.
Record Hyperliquid's current tick size, lot size and maker and taker fees for each coin, with the doc URL and date. No API keys are needed for public streams; do not load any.

Expect: 7+ days of Parquet data, check.py report, fees_and_ticks.md.

Audit before moving on: If total gap time exceeds 5% of the window, extend recording before fitting. If many trades share a millisecond, they are likely one taker order hitting several makers; plan aggregation in step 3.

Step 3 — Fit and test the Hawkes model

Goal: Get a branching-ratio series you can trust.

Aggregate trades: prints with the same timestamp, side and taker identifier (or same ms and side if no identifier) become one event; keep summed size. Build buy and sell event-time arrays per 5-minute window.
Fit a bivariate exponential Hawkes per window. With tick: HawkesExpKern with a decay grid (for example 1, 5, 20 per second), choose decay by held-out log-likelihood, read adjacency as branching ratios and the spectral radius as overall stability. Without tick: implement the exponential Hawkes log-likelihood with the recursive formula and optimize mu, alpha, beta under the constraint spectral radius below 1.
Goodness of fit: compute the compensator between events (time-rescaling); residuals should be Exp(1). Run a KS test per window and report the pass rate at 5%. Plot QQ for a typical and a volatile window.
Output a series of spectral radius, self-excitation and cross-excitation per window, and its correlation with next-window realized volatility and spread.

Expect: hawkes/ code, fits.parquet, a KS pass-rate table, QQ plots, the branching-ratio series with a volatility correlation.

Audit before moving on: If fewer than 70% of windows pass KS, try a sum-of-exponentials kernel before using the series. Confirm you did not divide tick's adjacency by beta. A branching ratio pinned near 1 in all windows usually means ties were not aggregated.

Step 4 — Calibrate fill intensity A and k

Goal: Replace the repo's fixed A and k with values from the tape.

For each depth delta in ticks from the mid (1 to 30 ticks), estimate how often a resting order at that distance would have been hit per second: count aggregated taker events whose price reached that depth on each side, divided by time. Fit log(lambda) = log(A) - k * delta by weighted least squares per side, per hour.
Report A and k with units (per second, per tick), their rolling stability, and the asymmetry between bid and ask.
Recompute sigma in ticks per square-root second from mid changes over 1-second intervals. Then compute the AS spread in ticks with these units at gamma values that give a spread between 1 and 10 ticks, and record that gamma range.
Relate A to the Hawkes intensity: compare hourly A with the mean fitted lambda; this is the link the guide proposes for a time-varying A.

Expect: calib/ code, A and k series per side, the gamma range table, an A versus Hawkes lambda plot.

Audit before moving on: If the computed spread is thousands of ticks or below one tick for all reasonable gamma, the units are wrong; fix before step 5. Check that fits use enough events per bucket (at least 30).

Step 5 — Event-driven paper backtest with controls

Goal: Compare quoting rules after fees, queues and adverse selection.

Use hftbacktest or write an event-driven simulator on the recorded data with: a queue-position model (your order joins the back of the queue at its price level and fills only after volume ahead is traded), 50-200 ms latency for order placement and cancel, the real maker fee or rebate, and minimum order size.
Arms, identical data and parameters elsewhere:
- Control: fixed symmetric spread of N ticks, N chosen on a training period.
- Plain AS using the GLT infinite-horizon form, constant A, k, sigma from training.
- Hawkes AS: A from the rolling Hawkes intensity, gamma scaled up as spectral radius approaches 1, quoting paused above a threshold chosen on training data.
Split days into training and out-of-sample. Report per arm: PnL after fees, markouts at 1, 10 and 60 s, inventory std, max drawdown, fill rate, and bootstrap 95% intervals across out-of-sample days. Inventory cap per coin identical across arms.

Expect: backtest/ code and results.md with the arm table and intervals, plus a PnL decomposition into spread capture, inventory PnL and fees.

Audit before moving on: If any arm shows profit with negative markouts at 10 s, it is a simulator artifact or luck; check queue logic. Verify no parameter was tuned on out-of-sample days.

Step 6 — Branching-ratio overlay on the taker strategies

Goal: Test the guide's lowest-risk integration as a paired shadow.

Open the trading-agent-team repository (the guide names ~/projects/agents/trading-agent-team/; confirm the path). Identify the taker strategies, their entry and take-profit logic and their trade logs.
Build a paired shadow: replay the same signals over the recorded period twice, once as-is and once gated by the Hawkes spectral radius (for example skip entries or tighten take-profit when the radius exceeds a threshold chosen on training days). Use identical fills and fees in both.
Report per coin: trades taken, PnL after fees, max drawdown, win rate, and the distribution of the radius at entry for winners and losers. Out-of-sample days only for the final numbers.
If the live bots can run a shadow arm without placing orders, propose the code change as a patch file, not an applied change, and describe how it would log its decisions alongside the live arm.

Expect: overlay/ results.md, a patch file for a live shadow arm, no changes applied to the live repo.

Audit before moving on: If the gated arm shows lower drawdown only because it trades far less, report return per trade and per unit of exposure as well. Confirm the live repository is untouched.

Step 7 — Validate against oracles and write the gap report

Goal: Decide what passes the paper gate and what remains unknown.

1. Confirm the AS reproduction matches the paper within sampling error.
2. Confirm the Hawkes KS pass rate on held-out windows.
3. Confirm which arm, if any, beats both controls on markout-adjusted PnL after fees with a bootstrap interval excluding zero.
4. Write gate.md: paper result, then a 2-week live paper-quoting run against the real tape with simulated fills, then minimum order size with per-coin inventory cap, daily loss stop at 0.5% of the allocated account and an automatic kill switch. Mark the current status. No real orders in this session.
5. Write gaps.md: claims from the guide checked (for example 'n near 0.9 is dangerous to quote', 'replacing Poisson with Hawkes is the single biggest realism upgrade', the half-spread comment), data limits, fee assumptions, tick install outcome, untested multi-asset effects. Fix what you can now.

Expect: validation.md, gate.md with status, gaps.md.

Audit before moving on: Re-run the backtest from a clean checkout and confirm identical numbers. Grep the repo and logs for key-like strings; any hit is fixed and rotated immediately.

4 · Gap analysis and audit

Run this after the build. It makes the agent enumerate its own claims, mark what is verified, rank the gaps and fix the top ones.

You are auditing an Avellaneda-Stoikov plus Hawkes research stack you did not build. Read verify_as.md, recorder/ checks, hawkes/, calib/, backtest/results.md, overlay/results.md, gate.md.
1. List every claim: formula correctness, reproduction numbers, KS pass rates, A and k values, arm metrics, overlay effect, gate status.
2. Mark each verified, unverified or false with evidence: a script output, a plot, a table row.
3. Re-check: units are ticks and seconds throughout; ties were aggregated; tick's adjacency was not divided by beta; the simulator models queue position, latency and fees; parameters were chosen on training days only; controls ran on identical data; no real orders were sent; no key appears in code, logs or history.
4. List missing pieces: multi-asset inventory, regime changes outside the window, exchange outages, funding payments on perps.
5. Rank gaps by whether they could flip the gate decision.
6. Fix the top three now.
7. Report what you could not fix and why.

Questions the agent must ask itself before it says done:

5 · What proves it right

6 · Failure modes

7 · Agent direction (unattended runs)

Run step 1 immediately and start the recorder in step 2 as a background service on day one, because every later step waits on data. Cache recorded Parquet under data/hl/ and fitted parameters under hawkes/fits/. Spawn a sub-agent for the simulator in step 5 while Hawkes fitting runs; they share only the data folder, read-only. Never edit the live trading-agent-team repository; produce patch files only. Stop and ask before any authenticated API call, before any real order, and if the recorder machine cannot stay up for 7 days. Load any key from environment only and never print it. If tick will not build after one attempt, switch to the scipy likelihood rather than debugging compilers. Return README.md linking verify_as.md, recorder/, hawkes/, calib/, backtest/results.md, overlay/results.md, gate.md and gaps.md, each with a one-line status and the gate result.

Prompt system updated 2026-10-22 · all prompt systems · JSON