Introduction
Market makers earn by providing liquidity — posting both buy and sell limit orders simultaneously and capturing the bid-ask spread when both sides fill. On Hyperliquid's perpetual futures market, professional market makers also earn maker rebates of -0.025% of trade value: rather than paying a fee, they receive a credit each time a limit order they posted gets filled. This passive rebate income on top of spread capture makes market making one of the most systematic and scalable strategies available to AI agents.
This guide builds a professional-grade market making agent from first principles — covering spread sizing, inventory management, delta hedging, and a complete Python implementation using Purple Flea's trading API.
The Economics of Market Making
Understanding the P&L components of market making is essential before writing a single line of code:
- Gross spread income: (sell price – buy price) × volume filled on both sides
- Rebate income: –0.025% × volume (you receive this as a credit, not a payment)
- Inventory risk loss: when your accumulated inventory moves against you before you can hedge it
- Net PnL = spread income + rebates – inventory losses
In a perfectly balanced world where every buy order is matched by an equal sell order at a slightly different price, inventory never accumulates and you capture pure spread. In reality, fills are asymmetric — you might fill 10 buys and 3 sells in a row. Managing this inventory imbalance before it becomes a directional loss is the primary skill of a market maker.
Spread Sizing: The Core Decision
Getting the spread right is the most important decision in market making. Too narrow and you get adversely selected — informed traders (or other algorithms) pick off your quotes before you can update them, leaving you holding inventory at bad prices. Too wide and your orders never get filled.
The Fundamental Framework
spread_bps = base_spread + vol_premium + inventory_premium
- base_spread: The minimum spread you are willing to offer — typically 5–10 bps for liquid perps
- vol_premium: A component that widens the spread when volatility is high. A simple formula: 50% of the realized hourly volatility expressed in bps
- inventory_premium: Widens the spread when inventory is large, reducing the chance of accumulating more of the same position
During a quiet low-volatility market, your spread might be 5 bps. During high volatility when BTC moves 2% per hour, it would widen to 15–20 bps automatically. This dynamic sizing protects against being picked off during volatile conditions.
Inventory Management
The goal of inventory management is to stay as close to delta-neutral as possible — meaning you have equal exposure to both long and short positions, netting close to zero directional risk.
After asymmetric fills, your inventory accumulates in one direction. Left unchecked, a long inventory position in a falling market produces losses that can dwarf the spread income. The response is inventory leaning: when you are long, adjust your quotes to offer a better ask price (to attract sellers) and a worse bid price (to discourage more buyers). This naturally rebalances the inventory via market forces.
When leaning is not fast enough, you hedge with a market order — accepting the taker fee as the cost of eliminating inventory risk.
Full Market Maker Implementation
import requests
import time
import math
PF_BASE = "https://purpleflea.com/api"
PF_KEY = "your-api-key"
HEADERS = {"Authorization": f"Bearer {PF_KEY}", "Content-Type": "application/json"}
class PerpMarketMaker:
def __init__(self, symbol: str, base_spread_bps: float = 5.0):
self.symbol = symbol
self.base_spread_bps = base_spread_bps
self.inventory = 0.0 # Current directional exposure in USD
self.max_inventory = 1000 # $1000 max
self.quote_size = 100 # $100 per side
def get_mid_price(self) -> float:
r = requests.get(f"{PF_BASE}/v1/markets/{self.symbol}/price", headers=HEADERS)
return r.json()["mid"]
def get_realized_vol_1h(self) -> float:
r = requests.get(f"{PF_BASE}/v1/markets/{self.symbol}/volatility",
params={"period": "1h"}, headers=HEADERS)
return r.json()["realized_vol"] # As a decimal, e.g. 0.015 = 1.5%
def get_spread(self) -> float:
"""Dynamic spread: base + vol premium + inventory premium"""
vol = self.get_realized_vol_1h()
vol_premium = vol * 100 * 0.5 # 50% of hourly vol in bps
inv_ratio = abs(self.inventory) / self.max_inventory
inventory_premium = inv_ratio * 5 # Up to 5 extra bps at max inventory
total = self.base_spread_bps + vol_premium + inventory_premium
return min(total, 50.0) # Hard cap at 50 bps
def refresh_quotes(self):
mid = self.get_mid_price()
spread = self.get_spread()
half = mid * (spread / 10000) / 2
# Lean quotes based on inventory to encourage rebalancing
# Positive inventory (long) → lean prices down to attract sellers
inventory_lean = -(self.inventory / self.max_inventory) * half * 0.3
bid = mid - half + inventory_lean
ask = mid + half + inventory_lean
# Cancel all existing orders, then post fresh quotes
requests.delete(f"{PF_BASE}/v1/mm/cancel-all",
json={"symbol": self.symbol}, headers=HEADERS)
requests.post(f"{PF_BASE}/v1/mm/quote",
json={
"symbol": self.symbol,
"bid": round(bid, 2),
"ask": round(ask, 2),
"size": self.quote_size
}, headers=HEADERS)
print(f"[{self.symbol}] mid={mid:.2f} spread={spread:.1f}bps bid={bid:.2f} ask={ask:.2f} inv=${self.inventory:.0f}")
def update_inventory(self):
"""Fetch current net position from exchange"""
r = requests.get(f"{PF_BASE}/v1/positions/{self.symbol}", headers=HEADERS)
pos = r.json()
self.inventory = pos.get("net_usd", 0.0)
def hedge_inventory(self):
"""Reduce inventory with a market order when it exceeds threshold"""
if abs(self.inventory) < self.max_inventory * 0.8:
return
hedge_side = "short" if self.inventory > 0 else "long"
hedge_size = abs(self.inventory) * 0.5 # Hedge 50% of excess
requests.post(f"{PF_BASE}/v1/trade",
json={"symbol": self.symbol, "side": hedge_side, "size_usd": hedge_size, "leverage": 1},
headers=HEADERS)
print(f"Hedged ${hedge_size:.0f} {hedge_side} to reduce inventory")
def run(self):
print(f"Market maker starting on {self.symbol}")
while True:
try:
self.refresh_quotes()
self.update_inventory()
self.hedge_inventory()
except Exception as e:
print(f"Error: {e}")
time.sleep(3) # Refresh every 3 seconds
if __name__ == "__main__":
mm = PerpMarketMaker("BTC-PERP", base_spread_bps=5.0)
mm.run()
Delta Hedging
Delta hedging is the process of actively offsetting inventory risk using market orders when leaning alone is not sufficient to restore neutrality quickly enough.
The key decisions are:
- When to hedge: When inventory exceeds 80% of the maximum. At this point the directional risk outweighs the cost of the taker fee.
- How much to hedge: Hedging 50% of the excess is a good starting point — it reduces risk without fully closing the position (which could reverse immediately if the market turns).
- Hedge cost: A taker fee of 0.025% on a $500 hedge costs $0.125. If holding the position would expose you to 0.1% adverse moves on $500 (= $0.50 loss), the hedge is worth it.
Hypothetical Backtest Results
To calibrate expectations, here is a hypothetical scenario for a $10,000 capital base running on BTC-PERP with a 5 bps spread, operating 8 hours per day:
Breaking down the daily income: spread income of ~$25 (0.05% on $50K volume) plus rebate income of ~$12.50 (0.025% on $50K) equals $37.50 gross. After inventory losses of approximately $10 on active trading days, the net is roughly $27.50 per day. Annualized across 365 days with an average of 8 active hours, that approaches $10,000 — a 100% APY on the deployed capital. These numbers are illustrative; actual results depend heavily on fill rates, volatility regime, and competition from other market makers.
Risk Controls Checklist
- Maximum single position: never exceed 20% of total capital in one direction
- Volatility circuit breaker: widen spread to 50 bps or stop quoting entirely during extreme moves (>5% per hour)
- Daily loss limit: kill switch activates if daily PnL exceeds –3% of capital
- Stale quote protection: cancel and repost all quotes every 3 seconds regardless of fills
- Rate limit monitoring: track API call frequency to stay within exchange limits
- Error handling: always cancel outstanding orders on any unhandled exception
Adverse selection is the hidden cost. If your fill rate is suspiciously high — say 90%+ of your quotes filling immediately — it likely means informed traders are picking off your stale prices rather than genuine two-sided flow. Widen your spread and add a small random delay to quote timing to reduce predictability.
Conclusion
Market making is one of the few strategies where an AI agent's advantages — speed, consistency, 24/7 operation, and zero emotional bias — directly translate to better execution than human traders. A well-configured market making agent running on perpetual futures can generate substantial yield on deployed capital while providing a genuine service to the market.
The key to success is disciplined spread sizing, aggressive inventory management, and a robust risk framework that prevents any single adverse event from undoing weeks of accumulated gains.
Get started with the market maker API, trading API, and orderbook API. Register your agent at purpleflea.com/register.