Does the Spread Pay?

Overnight audit of the pyramid market maker on Hyperliquid's NASDAQ‑100 and gold perpetuals · recorded 8–9 October 2026 (Pacific)

Bottom line

No. On about 8 hours of real data, a market maker on these two markets does not earn the spread after fees. On the holdout, none of the 96 settings made money from market making in either market. The setting that looked best on development data (+$1.15) lost $13.86 once prices fell.

Three things stop it: fees bigger than the spread, a newcomer's place at the back of the queue, and speed. Order-book imbalance really does predict the next tick, but the edge is a fraction of the fee. For a paper this is a useful negative result: a tested explanation of why a retail-speed market maker can't make money here, and of how an ordinary backtest would have said it could.

NASDAQ‑100: most per fill0.18 bphalf the spread, vs a 0.3 bp maker fee
Gold: most per fill0.13 bphalf the spread, vs a 3.0 bp maker fee
Holdout: settings that make money0 of 192market-making P&L after fees, both markets
Data recorded7.9 h172,126 quote updates, 22,822 trades

What I did overnight

The two markets

Both markets almost always sit at a one-tick spread: the tightest possible. That is the first problem. A market maker earns at most half the spread per fill, which here is under 0.2 basis points (0.002%), while a new account pays a maker fee of 0.3 bp on NASDAQ‑100 and 3 bp on gold. The fee figures come from each market's own settings: NASDAQ‑100 is in Hyperliquid's discounted "growth mode", gold is not, and both add the deployer's fee share.

NASDAQ‑100
Development
NASDAQ‑100
Holdout
NASDAQ‑100
US open
Gold
Development
Gold
Holdout
Gold
US open
Hours recorded2.944.991.492.944.991.49
Quote updates per second3.074.436.541.512.462.98
Time at a one-tick spread96%92%86%99%98%97%
Average spread (bp)0.3390.3580.3870.2430.2520.255
Trades per minute25.937.166.315.714.715.8
Dollars traded per hour$7.3M$11.1M$22.6M$1.2M$1.8M$2.8M
Realized volatility (bp per hour)9.114.320.48.313.917.2
Maker / taker fee, new account (bp)0.3 / 0.90.3 / 0.90.3 / 0.93.0 / 9.03.0 / 9.03.0 / 9.0

1. Who makes money providing liquidity?

This test uses no strategy. For every real trade, it asks whether the order that was resting in the book made money afterwards. Orders at the best price earn the half-spread (about 0.16 bp on NASDAQ‑100) the moment they fill, then give it back as the price moves against them. A minute later they are flat or negative, nowhere near the fee. Orders filled 2+ ticks deep by a sweep do better on NASDAQ‑100 (+0.35 bp at 60 s on the holdout), but the interval includes zero, so the pre-registered test (H1) did not confirm they beat the fee.

NASDAQ‑100: average profit of the resting order 60 s after it was filled, by how far from the mid price the trade printed. Whiskers are 95% intervals; numbers under the bars are trade counts.
-0.50+0.5+1.035868332At best price43915891–2 ticks out2231133Sweeps, 2+ ticks424811054All fillsmaker fee 0.3 bpbp
DevelopmentHoldoutMaker fee
Gold: average profit of the resting order 60 s after it was filled, by how far from the mid price the trade printed. Whiskers are 95% intervals; numbers under the bars are trade counts.
-10+1+2+323473731At best price681781–2 ticks out125485Sweeps, 2+ ticks25404394All fillsmaker fee 3.0 bpbp
DevelopmentHoldoutMaker fee

2. Our strategy, with real fees, queues and delay

The replay runs the pyramid strategy on the recorded quotes and trades. It joins the back of the queue, its orders go live 0.5 s after it decides, and it pays real fees. Net P&L is split into the edge earned at each fill, profit or loss from holding inventory while the price moves, and fees. Market-making P&L marks every fill 60 s later and subtracts fees, which removes luck from longer price drifts. On development data the best net P&L came almost entirely from holding inventory while the price rose; on the holdout the price fell and the same setting lost.

Across all 96 settings (real fees)

Market · dataNet P&L > 0Market-making P&L > 0Best market-making P&L
NASDAQ‑100 · Development14%0%−$0.25
NASDAQ‑100 · Holdout4%0%−$4.73
NASDAQ‑100 · US open0%0%−$3.81
Gold · Development0%0%−$11.63
Gold · Holdout0%0%−$57.77
Gold · US open0%0%−$25.04

NASDAQ‑100: frozen picks, where the money came from

SettingFillsNet P&LFill edgeInventoryFeesMarket-making P&LBreak-even fee (bp)
Best net P&L on development
off2 lv1 skew0.0 nopause nolean · Development
114+$1.15+$0.58+$1.35−$0.71−$0.98-0.12
Best net P&L on development
off2 lv1 skew0.0 nopause nolean · Holdout
709−$13.86+$3.09−$12.02−$4.69−$11.10-0.41
Best net P&L on development
off2 lv1 skew0.0 nopause nolean · US open
498−$10.42+$1.92−$8.81−$3.29−$9.15-0.53
Best market-making P&L on development
off3 lv4 skew0.0 pause nolean · Development
53+$0.18+$0.19+$0.33−$0.20−$0.25-0.08
Best market-making P&L on development
off3 lv4 skew0.0 pause nolean · Holdout
528−$0.92+$1.96−$0.75−$1.99−$5.58-0.54
Best market-making P&L on development
off3 lv4 skew0.0 pause nolean · US open
445−$7.27+$1.66−$7.09−$1.70−$4.45-0.49
Baseline: join best price, 1 level
off0 lv1 skew0.5 nopause nolean · Development
1543−$19.07+$1.21−$10.21−$10.00−$19.73-0.29
Baseline: join best price, 1 level
off0 lv1 skew0.5 nopause nolean · Holdout
4159−$64.51+$3.16−$40.45−$27.16−$64.31-0.41
Baseline: join best price, 1 level
off0 lv1 skew0.5 nopause nolean · US open
1935−$36.64+$1.53−$25.09−$13.02−$36.62-0.54

Gold: frozen picks

SettingFillsNet P&LFill edgeInventoryFeesMarket-making P&LBreak-even fee (bp)
Best net P&L on development
off3 lv1 skew1.0 pause lean · Development
116−$14.59+$0.76−$4.48−$10.76−$13.82-0.85
Best net P&L on development
off3 lv1 skew1.0 pause lean · Holdout
499−$58.37+$3.37−$15.38−$46.36−$59.68-0.86
Best net P&L on development
off3 lv1 skew1.0 pause lean · US open
194−$23.18+$1.43−$6.21−$18.13−$25.04-1.14
Best market-making P&L on development
off3 lv1 skew0.0 pause lean · Development
99−$18.21+$1.01−$7.23−$9.24−$11.63-0.78
Best market-making P&L on development
off3 lv1 skew0.0 pause lean · Holdout
495−$40.75+$4.52+$2.67−$45.86−$59.30-0.88
Best market-making P&L on development
off3 lv1 skew0.0 pause lean · US open
206−$16.94+$1.85+$0.31−$19.00−$26.14-1.13
Baseline: join best price, 1 level
off0 lv1 skew0.5 nopause nolean · Development
419−$47.22−$0.27−$10.71−$36.05−$46.07-0.83
Baseline: join best price, 1 level
off0 lv1 skew0.5 nopause nolean · Holdout
1434−$163.82−$1.69−$38.00−$123.73−$166.64-1.04
Baseline: join best price, 1 level
off0 lv1 skew0.5 nopause nolean · US open
507−$55.35−$0.96−$11.20−$42.66−$63.17-1.44

3. Speed decides it

The slower the strategy moves its orders, the more fills it gets and the worse they are: faster traders hit our old prices before we can move them. With no delay the deep levels catch real overshoots, but even then the strategy only about breaks even after fees. True zero delay can't be tested with this data, because new orders go live at the next recorded update, 0.1–0.3 s later.

NASDAQ‑100, quoting 2 ticks back with 4 levels: average profit of our fills 5 s later, against how long an order takes to go live. Numbers under the axis are fill counts (development first, holdout second).
-0.6-0.4-0.20+0.2+0.4+0.60 s0.25 s0.5 s1 s2 s39111171232381239847121716001653bp
DevelopmentHoldout

4. Order-book imbalance is real but small

When much more size waits on the bid than the ask, the price is more likely to tick up next. The 1-second correlation is about 0.3 on NASDAQ‑100 and 0.2 on gold, stable from development to holdout and strongest after the US open. It fades within a minute. Leaning quotes toward it slightly improved fill quality, but not by enough to cover fees.

Market · data1 s5 s30 s60 s
NASDAQ‑100 · Development0.31 [0.30, 0.33]0.29 [0.25, 0.33]0.13 [-0.01, 0.26]0.09 [-0.12, 0.27]
NASDAQ‑100 · Holdout0.32 [0.30, 0.33]0.27 [0.24, 0.30]0.18 [0.11, 0.26]0.11 [-0.01, 0.23]
NASDAQ‑100 · US open0.38 [0.37, 0.39]0.30 [0.24, 0.34]0.20 [0.12, 0.33]0.09 [-0.10, 0.28]
Gold · Development0.14 [0.13, 0.16]0.23 [0.20, 0.27]0.29 [0.18, 0.38]0.32 [0.20, 0.43]
Gold · Holdout0.19 [0.18, 0.22]0.25 [0.23, 0.27]0.22 [0.16, 0.29]0.15 [0.02, 0.28]
Gold · US open0.21 [0.17, 0.25]0.25 [0.20, 0.29]0.25 [0.14, 0.36]0.05 [-0.19, 0.32]

Pre-registered hypotheses

Written down before the holdout was opened and scored automatically against the stated test. H1 came from scanning 48 results on development data, which is why it needed the holdout to count.

  1. H1not confirmed

    xyz:XYZ100 maker fills printed 2+ ticks from the pre-trade mid have a mean +60s markout above the 0.30 bp maker fee (lower end of the 95% block-bootstrap interval > 0.30).

    Holdout: +0.35 bp [-0.20, +0.86], n = 1133, vs fee 0.3 bp.

  2. H2supported

    Maker fills at the touch (<= 0.75 tick from mid) do not cover the maker fee at +60s in either market (upper end of the 95% interval < fee).

    Holdout: NASDAQ‑100 -0.18 bp [-0.33, -0.01] vs fee 0.3; Gold -0.01 bp [-0.24, +0.22] vs fee 3.0.

  3. H3supported

    Top-of-book imbalance has a positive 1-second correlation with the next mid move in both markets (lower end of the 95% interval > 0).

    Holdout 1 s correlation: NASDAQ‑100 0.316 [0.302, 0.333]; Gold 0.195 [0.176, 0.216].

  4. H4supported

    No setting in the 96-setting grid earns positive market-making P&L (fills marked 60s later, minus tier-0 fees) in either market; the frozen best_mm settings lose on HOLDOUT too.

    Holdout: NASDAQ‑100: 0% of 96 settings positive, frozen pick −$5.58; Gold: 0% of 96 settings positive, frozen pick −$59.30.

  5. H5supported

    For xyz:XYZ100 quoting 2 ticks back with 4 levels, the 5s markout of our fills falls as latency rises (0s > 0.5s > 2s).

    Holdout 5 s markout of our fills: 0 s -0.19, 0.5 s -0.41, 2 s -0.43 bp.

Everything I found wrong

Every flaw I found overnight, in the data and in my own work. Critical means it changes the conclusion. The same list is in docs/audit_findings.md.

Data and venue

  1. critical Fees exceed the spread. Real fees for a new account, from the markets' own settings and Hyperliquid's fee docs: NASDAQ‑100 (xyz:XYZ100, growth mode, deployer scale 1.0) maker ≈ 0.30 bp, taker ≈ 0.90 bp. Gold (xyz:GOLD, not in growth mode, deployer scale 1.0) maker ≈ 3.0 bp, taker ≈ 9.0 bp. Time-weighted spread ≈ 0.35 bp (XYZ100) and ≈ 0.24 bp (gold), so a maker earns at most ≈ 0.17 / 0.12 bp per fill before adverse selection. Every earlier result ignored fees.
  2. major The 5.4 s book feed is stale. The l2Book websocket sends on a fixed ~5.4 s timer. 27% (gold) and 44% (XYZ100) of trades printed at prices the last snapshot did not show as best. The first replay (replay.py) decided on that stale view. Fixed: record the bbo channel (~0.1 s updates) and rebuild the replay on it (replay2.py); replay.py deleted.
  3. minor Feed delay. Quotes arrive ≈ 236 ms (median) after their exchange timestamp. The replay now adds 0.5 s latency by default and tests 0.25 s and 1 s.
  4. checked, OK Trade side meaning. "B" trades print at or above the ask, so "B" = buyer aggressed. The fill logic had it the right way round.
  5. major (framing) These are crypto-venue markets. Liquid is a front end over decentralized exchanges: xyz: markets are Hyperliquid HIP‑3 perpetuals deployed by "XYZ"; LIT:QQQ is on Lighter. Exposure is to stocks/gold, but settlement, fees and order books are crypto infrastructure (USDC on Hyperliquid). The project set out to avoid crypto markets, so the paper must say this.
  6. minor Perpetual funding. Funding was 0.000625%/hour on both markets (≈ 0.06 bp/hour on inventory). Negligible next to fees for positions held minutes; ignored, stated here.
  7. minor Overnight session. Most data is recorded while the NASDAQ cash market is closed; the perp then tracks an oracle (likely futures). XYZ100 traded 2.7 bp above oracle at 07:05 UTC. US-open data is reported separately.

Simulation (earlier work)

  1. major Volatility calibration was 1.58× too high. I treated ATR(14, 1h) as one standard deviation of hourly moves. For a random walk the mean hourly high-low range is ≈ 1.58 sd (measured; theory 1.596). Correcting it makes the simulation *more* profitable (the hand-picked settings, flow 0.3: +27 → +187).
  2. major "Out-of-sample" seeds were reused. Seeds 1000–1049 were used again while designing the imbalance fix. On 100 fresh seeds (5000–5099): hand-picked settings flow 2.0 +473 (claimed +504), flow 0.3 +27 ± 14 (claimed +41), storm baseline −147 (claimed −167), both upgrades in storms +5 ± 26, win 50% (claimed +18, 58%) — the "profitable in storms" claim is retracted.
  3. major Simulation is circular. Storms and a predictive imbalance were built into the simulated market, then strategies built for them won. Only real data can test them.
  4. major Simulated fills are generous. A quote fills whenever the next simulated price crosses it, with no queue and full size; plus random "noise" fills whose rate is a guess.
  5. minor Earlier sweep confound (already fixed then). kappa set both quote width and trader price-sensitivity.

Statistics and reporting

  1. major Overlapping windows inflated imbalance correlations. analyze_book.py's gold "0.72 at 60 s" used overlapping windows on ~30 snapshots. Replaced by imbalance.py (non-overlapping samples, 10-minute block bootstrap); old code removed.
  2. minor Cherry-picked summary line. markouts.py printed the best of 18 depth×horizon cells. Replaced with a fixed, pre-chosen test (all fills, +60 s, vs fee).
  3. process Pre-registered split. DEV = before 10:00 UTC, HOLDOUT = after; US open from 13:30 UTC. Fixed in protocol.py before any overnight data was analysed.

Replay code (my own bugs, caught by tests)

  1. major Filled orders were never re-posted (strategy silently stopped quoting a side).
  2. major Partly filled orders were topped up while keeping queue place (optimistic).
  3. minor Recording gaps would let stale orders "fill" when data resumed. Now any gap > 30 s cancels everything.
  4. process Tests were too weak at first. Mutation testing showed 4 deliberate bugs passing the first 12 tests; now 19 tests catch all of them (one more added for the P&L split).

DEV results (2.94 h, 07:03–10:00 UTC) — findings about the strategy itself

  1. critical The "profitable" settings were directional luck. Best NASDAQ‑100 setting on DEV (2 ticks back, no skew) made +$1.15 net: +$0.58 fill edge, +$1.35 from holding a long position while the mid rose 6.8 bp, −$0.71 fees. Its market-making P&L (fills marked 60 s later, minus fees) was −$0.98. A backtest that only reports net P&L would have called it a win.
  2. critical 0 of 96 settings earn positive market-making P&L after fees on DEV, in either market. Gold: 0% even with zero fees. NASDAQ‑100: 4% with zero fees, 0% with real fees.
  3. major Back of the queue is the worst seat. Market-wide, gold makers are roughly flat 60 s after a fill; our strategy's gold fills lose ≈ 0.8 bp. A newcomer joins behind everyone, so it mostly fills right before the price moves through its level.
  4. major Speed decides it. NASDAQ‑100, 2 ticks back, 4 levels: as order delay rises from 0 → 0.5 → 2 s, fills rise 39 → 171 → 381 and the 5 s markout falls +0.57 → −0.20 → −0.29 bp. The extra fills are stale quotes picked off. Even at ~0 delay it only breaks even after fees.
  5. limitation "Zero delay" isn't really zero. New orders go live at the next recorded event (~0.1–0.3 s apart), so the replay can't test true microsecond speed.
  6. interesting Sweep fills look profitable market-wide. NASDAQ‑100 makers filled 2+ ticks from the mid earn +0.54 bp at 60 s [+0.2, +0.9] vs a 0.30 bp fee. Found by scanning 48 cells, so pre-registered as H1 for the holdout. Our slow strategy can't capture it (finding 23).
  7. critical Even the best fee tier can't save it. Break-even maker fee (bp) on DEV is negative for every setting but one: you'd need a rebate of ≈ 0.1–0.3 bp (NASDAQ‑100) or ≈ 0.5–0.9 bp (gold). Hyperliquid's largest rebate is −0.003% (−0.3 bp), only above 3% of all exchange maker volume, and growth mode (NASDAQ‑100) cuts rebates by ≥ 90% (≈ −0.03 bp). Only one NASDAQ‑100 setting (2 back, 4 levels, lean) has a positive break-even, +0.06 bp.
  8. checked Imbalance is real. 1 s correlation 0.31 [0.30, 0.33] NASDAQ‑100, 0.14 [0.13, 0.16] gold; fades within ~30 s. My simulated ρ = 0.2 was a reasonable guess. It still doesn't rescue the strategy: the lean helped fill quality slightly but not enough to cover fees.

HOLDOUT results (10:00–15:00 UTC, settings frozen)

  1. critical Confirmed on the holdout. HOLDOUT (10:00–15:00 UTC, 4.99 h, settings frozen): 0 of 96 settings earn positive market-making P&L in either market, overnight or after the US open. The development "winner" (+$1.15) lost −$13.86 when the price fell 40 bp.
  2. major The US open is worse for makers. NASDAQ‑100 orders filled at the best price lose 0.36 bp a minute later after 06:30 Pacific, against 0.06 bp in development. More informed traders are active then.
  3. note One small positive pocket. In the overnight half of the holdout, 7% of NASDAQ‑100 settings were slightly positive (best +$0.27). Out of 96 tries, a few will look good by chance, and this wasn't a pre-registered test, so it is not evidence of an edge.
  4. note H1 not confirmed. Deep sweep fills on NASDAQ‑100 averaged +0.35 bp at 60 s on the holdout, above the 0.30 bp fee, but the 95% interval [−0.20, +0.86] includes zero.

Corrections to what I told you earlier

What this means for the paper

Reproduce it

python src/recorder.py --minutes 120      # record (Ctrl+C to stop)
python src/audit_data.py                  # data-quality checks
python src/analyze_book.py --window dev   # market statistics
python src/imbalance.py --window dev      # does imbalance predict the next move?
python src/markouts.py --window dev       # do resting orders make money? (no strategy)
python src/evaluate.py dev                # 96-setting search, freezes picks
python src/evaluate.py holdout            # one frozen test on later data
python src/audit_sim.py                   # re-checks the simulation claims
python -m unittest discover -s tests      # 19 tests for the backtester
python src/make_report.py                 # rebuilds this page

Raw data is in data/raw/ (not in git; about 20 MB for the night). The full findings log is docs/audit_findings.md.