A fully automated market maker bot for OP perpetual futures on Hyperliquid, using Binance as the fair value oracle.
Built in Python with async architecture, REST-based fill detection, and real-time risk management.
The bot continuously quotes bid and ask orders around the Binance mid price on Hyperliquid's OP-PERP market. Every order uses ALO (Add Liquidity Only) to guarantee 100% maker fills — no taker fees during normal operation.
Binance OP/USDT (fair value)
│
▼
┌───────────────────────────────────┐
│ QuoteCalculator │
│ spread + quadratic skew + clamp │
├───────────────────────────────────┤
│ MarketMaker Engine │
│ place/cancel ALO · fill polling │
│ force-close · inventory sync │
├───────────────────────────────────┤
│ Risk Management │
│ daily loss · stale price guard │
│ max inventory · capital check │
└───────────────────────────────────┘
│
▼
Hyperliquid OP-PERP (execution)
- Read fair value — latest OP price from Binance Futures WebSocket
- Stale price guard — if no Binance update in 10s, cancel all quotes
- Poll fills — REST API call to detect new fills (replaces broken HL WebSocket)
- Force-close check — if inventory exceeds 80% of max, send taker IOC to reduce
- Calculate quotes — QuoteCalculator computes optimal bid/ask with spread + skew
- Place/reprice orders — cancel and replace only if price moved > 2 ticks
- Periodic inventory sync — every 30 cycles, verify local inventory matches exchange
| File | Lines | Description |
|---|---|---|
hl_config.py |
~130 | All parameters: spread, skew, inventory limits, fees, credentials |
hl_analysis.py |
~190 | QuoteCalculator — spread calculation, quadratic inventory skew, book clamping |
hl_execution.py |
~750 | MarketMaker class — order management, REST fill polling, force-close, PnL tracking |
hl_main.py |
~500 | Entry point — 2 async tasks: Binance WebSocket + MM loop, dashboard export |
| File | Description |
|---|---|
dashboard.html |
Dark theme live dashboard with WebSocket auto-refresh |
dashboard_server.py |
HTTP server (port 8080) + WebSocket (port 8081) for real-time state |
hl_discord_notifier.py |
Discord webhook notifications for fills and alerts |
| File | Description |
|---|---|
check_position.py |
View current position, open orders, and recent fills |
check_wallet.py |
View account balance (perp + spot USDC) |
_balance.py |
Quick balance check |
_close_position.py |
Manually close a position via market order |
_force_close.py |
Force-close with IOC order |
_check.py |
Quick connectivity check |
The bot quotes with a minimum spread of 8 basis points (0.08%) around the Binance mid price. With a maker fee of 1.44 bps on Hyperliquid, this yields a theoretical ~5.12 bps net profit per round-trip (buy maker fill + sell maker fill).
◄─── 8 bps spread ───►
│ │
our bid ──── FV (Binance) ──── our ask
$0.12265 $0.12270 $0.12275
When the bot accumulates inventory (directional exposure), it shifts both quotes to incentivize fills that reduce the position. The skew is quadratic — gentle at low inventory, very aggressive at high inventory.
norm_inventory = (net_inventory × fair_value) / max_inventory_usd # [-4, +4]
skew_bps = sign(norm) × norm² × 12.0 # Quadratic, 12 bps per unit²| Inventory (% of max) | Skew Applied |
|---|---|
| 25% | 0.75 bps (barely noticeable) |
| 50% | 3.0 bps (gentle push) |
| 100% | 12.0 bps (strong push) |
| 200% | 48.0 bps (very aggressive) |
When inventory exceeds 80% of max ($20 of $25), the bot sends an IOC reduce-only order as a taker to close the entire excess above 50% of max in one shot. This prevents runaway accumulation but costs taker fees (4.32 bps).
Before placing orders, the bot checks that its quotes don't cross the Hyperliquid orderbook (which would cause ALO rejection). If the bid would cross the best ask, it clamps to the best bid. Same logic for the ask side.
| Parameter | Value | Description |
|---|---|---|
MM_MIN_SPREAD_BPS |
8.0 | Minimum spread in basis points |
MM_INVENTORY_SKEW_BPS |
12.0 | Skew intensity (quadratic) |
MM_MAX_INVENTORY_USD |
$25 | Max directional exposure |
MM_FORCE_CLOSE_PCT |
80% | Threshold to trigger taker force-close |
MM_MAX_DAILY_LOSS_USD |
$2.00 | Daily loss limit — stops quoting |
MM_QUOTE_SIZE_USD |
$10.50 | Notional per side (above HL $10 minimum) |
MM_QUOTE_REFRESH_SEC |
2.0s | Cycle interval |
MM_LEVERAGE |
5x | Cross-leverage on Hyperliquid |
- Stale price guard: cancels all quotes if Binance price is older than 10 seconds
- Cancel-fail protection: if a cancel fails, verifies via REST if the order is still live before cleaning tracking (prevents double-orders)
- Daily loss limit: stops quoting when PnL drops below -$2.00, resumes only when PnL returns to $0
- Capital check: stops the bot entirely if USDC balance drops below $3
- Double-read inventory sync: reads position twice with 1s delay to detect API inconsistencies
- Atomic dashboard writes: uses temp file + rename to prevent corrupted reads
- Python 3.10+
- Hyperliquid account with API wallet configured
- Binance Futures WebSocket access (no API key needed, public stream)
git clone https://github.com/TheFraTo/Trading-bot-v1.2.git
cd Trading-bot-v1.2
pip install hyperliquid-python-sdk websockets aiohttp python-dotenv eth-account
cp .env.example .env
# Edit .env with your credentialsCreate a .env file:
HL_WALLET_ADDRESS=0xYourMainWalletAddress
HL_PRIVATE_KEY=your_api_wallet_private_key
INITIAL_CAPITAL=10.0
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... # optional# Start the bot
python hl_main.py
# In production (with screen)
screen -dmS hlbot bash -c "cd /path/to/bot && python3 hl_main.py 2>&1 | tee -a logs/system.log"
# Start the dashboard
screen -dmS dashboard bash -c "cd /path/to/bot && python3 dashboard_server.py"
# Access at http://your-server:8080# Check position & open orders
python check_position.py
# Check balance
python check_wallet.py
# View live logs
tail -f logs/system.log
# Emergency close position
python _close_position.pyThe Hyperliquid Python SDK's WebSocket fill subscription (userFills) is unreliable — callbacks often never fire, causing the bot to miss fills entirely. We replaced it with REST polling via info.user_fills_by_time() every cycle, deduplicated by trade ID (TID). This adds ~1 API call per cycle but guarantees no fills are missed.
Binance Futures has the deepest OP/USDT liquidity globally. Using its aggTrade stream gives us a reliable, low-latency fair value reference. The bot doesn't need a Binance API key — it connects to the public WebSocket stream.
Linear skew (constant bps per unit of inventory) doesn't punish large positions enough. With quadratic skew, holding 50% of max inventory costs only 3 bps, but holding 100% costs 12 bps and 200% costs 48 bps. This allows the bot to hold small positions comfortably while aggressively unwinding large ones.
ALO (Add Liquidity Only) orders on Hyperliquid guarantee maker execution at 1.44 bps fee instead of taker at 4.32 bps. If the price moves and our order would cross the book, it's simply rejected — no accidental taker fills. The only taker orders come from force-close, which is an explicit risk management decision.
This bot is not designed to make significant money. It was built as a learning project to understand market making mechanics, order management, and exchange microstructure on Hyperliquid.
The core issue is economic, not technical. The code works correctly — the problem is the environment it operates in:
-
Hyperliquid charges maker fees (1.44 bps). Most profitable market makers operate on exchanges that pay makers (rebates of 1-2 bps). On HL, every fill costs you money instead of earning it. Your spread needs to cover both sides' fees before you see any profit.
-
OP at $0.12 with a $10 minimum order = no granularity. Each order is ~85 OP ($10.50), which is already 42% of the $25 max inventory. Two consecutive fills on the same side trigger a force-close in taker (4.32 bps fee + slippage). In live testing, the bot did 4 taker force-closes for every 1 complete maker round-trip — meaning it pays more in force-close fees than it earns in spread.
-
The HL orderbook on OP is already extremely tight (~0.8 bps spread). Professional market makers with co-located servers and much larger capital are quoting at the inside. Our 8 bps spread puts us deep in the book where we only get filled by large directional sweeps — which is textbook adverse selection (the taker knows more than us about where the price is going).
-
$10 of capital is not enough. With $10 at 5x leverage, the buying power is $50. The $10 minimum notional per order means each fill consumes a huge fraction of the portfolio. There's no room to absorb inventory without force-closing.
| Capital | Max Inventory | Fill Size vs Max | Force-Close Frequency | Expected Outcome |
|---|---|---|---|---|
| $10 | $25 | 42% | Every 2 fills | Guaranteed loss — force-close fees > spread profit |
| $50 | $125 | 8.4% | Every ~12 fills | Breakeven possible — but fees still eat most profit |
| $200 | $500 | 2.1% | Rare | Marginal profit possible — ~$0.05-0.20/day on a good day |
| $500+ | $1,250+ | 0.8% | Very rare | Realistic territory — force-closes nearly eliminated, spread capture works |
Even at $500, the expected daily return is small (pennies to low single digits) because HL's negative maker fees and tight OP spread leave very little edge. To make meaningful money market making, you'd need either: positive maker rebates, a faster fair value signal, or significantly more capital on a coin with wider natural spreads.
Use this bot to learn how market making works — order management, inventory risk, adverse selection, fill detection, exchange APIs. The architecture and code are production-quality. The economics just don't work at this scale on this venue.
- Minimum notional: Hyperliquid requires $10 minimum per order. At OP ~$0.12, each order is ~85 OP ($10.50), which is 42% of the max inventory. This causes frequent force-closes.
- Market microstructure: HL's OP market has ~0.8 bps spread with deep liquidity. Our 8 bps spread places us deep in the book, leading to asymmetric fills (adverse selection from directional sweeps).
- Negative maker fees: Unlike exchanges with maker rebates, HL charges 1.44 bps per maker fill. This reduces the edge significantly.
- Best suited for: higher capital ($200+) or higher-priced coins where $10 minimum is a small fraction of max inventory.
- Stale price guard: cancel quotes if Binance price > 10s old
- Cancel-fail protection: REST verification before clearing order tracking
- Aggressive force-close: close entire excess above 50% of max (was 1/3 of quote size)
- Daily loss reset at $0: no more oscillation between stop/start (was -50%)
- Cash flow fix: handle missing
entryPxat startup with fair value proxy - Inventory sync safety: skip sync if no valid price available for cash flow adjustment
- Spread increased from 5 to 8 bps
- Inventory skew increased from 3 to 12 bps (quadratic)
- Max inventory reduced from $40 to $25
- Force-close taker mechanism added (80% threshold)
- Daily loss limit increased from $0.50 to $2.00
- Initial market maker on OP
- REST fill polling (replaced broken HL WebSocket)
- Double-read inventory sync at startup
- ALO-only quoting with Binance fair value
- Live dashboard with WebSocket auto-refresh
This project is for educational and personal use. Use at your own risk. Trading cryptocurrencies involves significant risk of loss.