A full-stack paper trading platform powered by real-time Hyperliquid and Binance market data. Trade 70+ crypto perpetual futures with $100k virtual USDC, track PnL with proper margin and liquidation math, and compete on a global leaderboard — all backed by Supabase with atomic database transactions and live WebSocket streaming.
- Features
- Tech Stack
- Architecture
- Prerequisites
- Setup
- Environment Configuration
- Database Setup
- Trading Engine
- Market Data Pipeline
- WebSocket Protocol
- API Reference
- Stress Testing
- Development
- Deployment
- Project Structure
- Real-Time Market Data: Live prices via Hyperliquid
allMidsWebSocket, L2 orderbook depth (15 levels), and trade feeds for 70+ assets - Dual Candle Sources: Historical candles from CryptoCompare REST API with real-time 1m candle streaming from Binance US WebSocket — merged seamlessly with cache invalidation and fallback generation
- Paper Trading Engine: Market orders with configurable leverage (1–50×), proper margin accounting, PnL calculation, and automatic liquidation detection
- Atomic Transactions: All order execution and position closing runs through PostgreSQL stored procedures (
execute_market_order,close_position_atomic) with row-level locking to prevent race conditions - TradingView Charts: Lightweight Charts integration with 6 timeframes (1m, 5m, 15m, 1h, 4h, 1d), crosshair tooltips, and live candle updates
- Live Orderbook: 15-level bid/ask depth with spread calculation, streamed directly from Hyperliquid L2 data
- Whale Tracking: Follow positions of known Hyperliquid whale addresses with labeled identities
- Leaderboard: Global rankings by PnL %, win rate, and trade count with user rank calculation via PostgreSQL window functions
- Account Management: $100k starting balance, account reset with trade history wipe, and persistent stats tracking
- Stress Testing: Built-in TPS simulator (10–1,000 TPS) that generates synthetic trades to demonstrate WebSocket throughput
- Supabase Auth: JWT-based authentication with Bearer token middleware, Row Level Security policies on all tables, and optional WebSocket token validation
- Rate Limiting: In-memory IP-based rate limiter (100 req/min) with
X-RateLimit-*response headers - CI/CD Pipeline: GitHub Actions workflow with lint, typecheck, test, and build stages
- Docker Support: Multi-stage Dockerfile with non-root user, healthcheck, and optimized layer caching
| Layer | Technology |
|---|---|
| Frontend | React 18 + TypeScript + Vite |
| Styling | Tailwind CSS |
| Charts | TradingView Lightweight Charts |
| State | Zustand |
| Routing | React Router v6 |
| Backend | Node.js + Express + TypeScript |
| WebSocket | ws (server) + native WebSocket (client) |
| Database | Supabase (PostgreSQL) with RLS |
| Auth | Supabase Auth (JWT) |
| Validation | Zod |
| Security | Helmet + CORS + rate limiting |
| Market Data | Hyperliquid WebSocket + CryptoCompare REST + Binance US WebSocket |
| Testing | Jest + ts-jest |
| CI/CD | GitHub Actions |
| Deployment | Netlify (client) + Render (server) + Docker |
┌──────────────────────────────────────────────────────────────────┐
│ Client (React) │
│ TradingView Charts · Orderbook · Order Form · Leaderboard │
│ WebSocket Client · Zustand Store · Supabase Auth │
└──────────────┬──────────────────────────────────┬────────────────┘
│ REST (Express) │ WebSocket (ws)
┌──────────────▼──────────────────────────────────▼────────────────┐
│ Server (Node.js) │
│ Auth Middleware · Rate Limiter · Zod Validation · Error Handler │
│ │
│ ┌─────────────────┐ ┌──────────────────┐ ┌────────────────┐ │
│ │ Trading Engine │ │ Market Service │ │ Stress Test │ │
│ │ OrderExecutor │ │ HyperliquidSvc │ │ TPS Generator │ │
│ │ PositionManager │ │ BinanceKlineSvc │ │ Stats Tracker │ │
│ │ PnlCalculator │ │ Candle Cache │ │ │ │
│ │ AccountManager │ │ Orderbook Cache │ │ │ │
│ └────────┬────────┘ └────────┬─────────┘ └────────────────┘ │
│ │ │ │
└───────────┼────────────────────┼─────────────────────────────────┘
│ │
┌───────────▼──────┐ ┌─────────▼──────────────────────────────────┐
│ Supabase │ │ External APIs │
│ PostgreSQL + RLS │ │ Hyperliquid WS (allMids, l2Book, trades) │
│ Auth (JWT) │ │ CryptoCompare REST (historical candles) │
│ Stored Procs │ │ Binance US WS (real-time klines) │
└──────────────────┘ └────────────────────────────────────────────┘
- Node.js 18+ and npm
- Supabase account with a project created
- Git for cloning the repository
git clone https://github.com/your-username/hyperliquid-trading-sim.git
cd hyperliquid-trading-sim
# Install all dependencies (root, client, server)
npm run install:allcp client/.env.example client/.env
cp server/.env.example server/.envEdit both files with your Supabase credentials (see Environment Configuration).
Apply the SQL migrations in supabase/migrations/ to your Supabase project, either through the Supabase dashboard SQL editor or the CLI:
npx supabase db push# Both client and server concurrently
npm run dev
# Or separately:
npm run dev:client # http://localhost:5173
npm run dev:server # http://localhost:3001VITE_API_URL=http://localhost:3001
VITE_WS_URL=ws://localhost:3001
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-keyPORT=3001
NODE_ENV=development
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_KEY=your-service-role-keyThe application uses 5 tables and 3 atomic stored procedures. Migrations are in supabase/migrations/ and should be run in order:
| Table | Purpose | RLS |
|---|---|---|
profiles |
Username, avatar | Users can view all, update own |
accounts |
Balance, reset count | Users can view/update own |
positions |
Open/closed trades with margin, PnL, liquidation price | Users can view/insert/update own |
trades |
Closed position history (entry, exit, PnL) | Users can view/insert own |
leaderboard_stats |
Aggregated PnL, win rate, drawdown | Anyone can view, users update own |
| Function | Purpose |
|---|---|
execute_market_order |
Locks account row, checks balance, deducts margin, creates position — all atomically |
close_position_atomic |
Locks position, updates status, returns margin + PnL to balance, records trade |
liquidate_position_atomic |
Closes position with liquidation status, zeroes margin |
All procedures use FOR UPDATE row locking and SECURITY DEFINER to prevent concurrent modification.
- Client submits market order (asset, side, size, leverage)
- Server validates inputs with Zod schemas and checks asset support
OrderExecutorcalculates notional value, required margin, and liquidation price- PostgreSQL
execute_market_orderatomically: locks account → checks balance → deducts margin → creates position - Position returned to client with WebSocket broadcast
The PnlCalculator handles all trade math:
| Metric | Formula |
|---|---|
| PnL (Long) | (currentPrice - entryPrice) × size |
| PnL (Short) | (entryPrice - currentPrice) × size |
| PnL % | ((priceDiff / entryPrice) × 100) × leverage |
| Liquidation (Long) | entryPrice × (1 - (1 - maintenanceMargin) / leverage) |
| Liquidation (Short) | entryPrice × (1 + (1 - maintenanceMargin) / leverage) |
| Win Rate | winningTrades / totalTrades × 100 |
| Profit Factor | grossProfit / grossLoss |
| Max Drawdown | Peak-to-trough analysis on cumulative PnL |
| Parameter | Value |
|---|---|
| Initial Balance | $100,000 USDC |
| Min Order Size | 0.001 |
| Max Leverage | 50× |
| Default Leverage | 10× |
| Maintenance Margin | 5% |
| Maker Fee | 0.02% |
| Taker Fee | 0.05% |
The server aggregates data from three sources with intelligent caching and fallback:
- Source: Hyperliquid
allMidsWebSocket (single subscription for all assets) - Delivery: Broadcast to all subscribed clients on every tick
- Source: Hyperliquid
l2BookWebSocket (subscribed on-demand per asset) - Depth: 15 levels bid/ask with cumulative totals and spread calculation
- Subscription Queue: Assets are subscribed with 1-second delays to avoid rate limiting
- Historical: CryptoCompare REST API with per-timeframe cache TTLs (30s for 1m up to 1hr for 1d)
- Live Updates: Binance US WebSocket klines merged into cached historical data
- Fallback: If both APIs fail, generates synthetic candles based on the last known WebSocket price
- Cache Validation: Invalidates if cached price drifts >30% from current WebSocket price
- Deduplication: Pending fetch promises tracked to prevent duplicate API calls for the same asset/timeframe
70+ perpetual futures including BTC, ETH, SOL, XRP, DOGE, AVAX, LINK, ARB, OP, SUI, PEPE, WIF, TRUMP, HYPE, TAO, and more. The asset list is fetched from Hyperliquid on startup with a hardcoded fallback.
{ "type": "subscribe", "channel": "orderbook:BTC" }
{ "type": "unsubscribe", "channel": "candles:ETH" }| Channel Pattern | Data |
|---|---|
price:{asset} |
{ asset, price, timestamp } |
orderbook:{asset} |
{ bids, asks, timestamp } — each level as [price, size] |
candles:{asset} |
{ time, open, high, low, close, volume } |
trades:{asset} |
{ id, price, size, side, timestamp } |
positions |
Position updates for authenticated users |
stress_test |
TPS stats when stress test is active |
Connections support wildcard subscriptions (e.g., price:*), heartbeat pings every 30 seconds, and optional JWT authentication via query parameter.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register |
No | Create account (email + password) |
| POST | /api/auth/login |
No | Login, returns JWT |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/account |
Yes | Get balance, equity, margin usage |
| POST | /api/account/reset |
Yes | Reset to $100k, close positions, clear history |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/trading/order |
Yes | Place market order { asset, side, size, leverage } |
| GET | /api/trading/positions |
Yes | Get open positions |
| POST | /api/trading/close/:id |
Yes | Close position at current market price |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/market/candles |
No | Historical candles ?asset=BTC&timeframe=1h&limit=500 |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/leaderboard |
No | Global rankings by PnL %, win rate, or trade count |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/stress-test |
No | Set speed: off, slow (10 TPS), medium (100), fast (500), max (1000) |
The built-in stress test generates synthetic trade messages to demonstrate WebSocket throughput:
| Speed | TPS | Description |
|---|---|---|
| Slow | 10 | Baseline performance |
| Medium | 100 | Moderate load |
| Fast | 500 | High throughput |
| Max | 1,000 | Maximum stress |
Stats broadcast every second: current TPS, peak TPS, total messages, average latency, and connected client count.
npm run dev # Start client + server concurrently
npm run dev:client # Vite dev server (port 5173)
npm run dev:server # tsx watch (port 3001)
npm run build # Build client + server
npm run lint # Lint client + server
npm run typecheck # TypeScript check client + server
npm test # Jest tests (server)
npm run test:coverage # Jest with coverage reportServer-side tests cover the trading engine core:
calculations.test.ts— PnL, margin, liquidation price mathorderExecutor.test.ts— Order validation and executionpnlCalculator.test.ts— Win rate, profit factor, max drawdown
Runs on push/PR to main: lint → typecheck → test with coverage → build. Coverage reports are uploaded as artifacts.
A netlify.toml is included in client/. Connect your repo and set build command to npm run build with publish directory dist.
A render.yaml blueprint is included. Set SUPABASE_URL and SUPABASE_SERVICE_KEY as environment variables.
docker build \
--build-arg VITE_API_URL=https://your-api.com \
--build-arg VITE_WS_URL=wss://your-api.com/ws \
--build-arg VITE_SUPABASE_URL=https://your-project.supabase.co \
--build-arg VITE_SUPABASE_ANON_KEY=your-key \
-t hyperliquid-trading-sim .
docker run -p 3001:3001 \
-e SUPABASE_URL=https://your-project.supabase.co \
-e SUPABASE_SERVICE_KEY=your-key \
hyperliquid-trading-simThe image runs as non-root, includes a healthcheck against /health, and uses multi-stage builds to minimize final image size.
hyperliquid-trading-sim/
├── client/ # React frontend (Vite)
│ ├── src/
│ │ ├── components/
│ │ │ ├── auth/ # LoginForm, RegisterForm, AuthGuard
│ │ │ ├── chart/ # PriceChart, CandleTooltip, ChartControls
│ │ │ ├── layout/ # Header, Sidebar, MainLayout, MobileNav
│ │ │ ├── leaderboard/ # Leaderboard, LeaderboardRow, LeaderboardTabs
│ │ │ ├── orderbook/ # Orderbook, OrderbookRow, OrderbookSpread
│ │ │ ├── participants/ # ParticipantsTable, FollowButton (whale tracking)
│ │ │ ├── stress-test/ # StressTestPanel, TPSDisplay, StressTestStats
│ │ │ ├── trades/ # RecentTrades, TradeRow
│ │ │ ├── trading/ # OrderForm, PositionPanel, OpenOrders, AccountStats
│ │ │ └── ui/ # Button, Input, Modal, Toast, Tooltip, Tabs, etc.
│ │ ├── config/ # Asset definitions, constants, timeframes
│ │ ├── context/ # Auth, Market, Trading, WebSocket, Toast providers
│ │ ├── hooks/ # useAuth, useMarketData, usePositions, useWebSocket, etc.
│ │ ├── lib/ # API client, Supabase client, WebSocket manager, utils
│ │ ├── pages/ # Home, Trading, Leaderboard, Profile, Login, Register
│ │ ├── styles/ # Global CSS + Tailwind
│ │ └── types/ # Market, trading, user, WebSocket type definitions
│ ├── netlify.toml
│ └── vite.config.ts
├── server/ # Node.js backend (Express)
│ ├── src/
│ │ ├── __tests__/ # Jest tests for trading engine
│ │ ├── config/ # Assets, constants, whale addresses
│ │ ├── lib/ # Supabase client, logger, custom errors
│ │ ├── middleware/ # Auth (JWT), rate limiting, validation, error handler
│ │ ├── routes/ # Auth, trading, market, leaderboard, account, stress test
│ │ ├── services/
│ │ │ ├── binance/ # Binance US WebSocket kline streaming
│ │ │ ├── hyperliquid/ # Hyperliquid WS + CryptoCompare REST + candle caching
│ │ │ ├── leaderboard/ # Ranking queries and stat aggregation
│ │ │ ├── stress-test/ # Synthetic trade generator with TPS tracking
│ │ │ └── trading/ # OrderExecutor, PositionManager, PnlCalculator, AccountManager
│ │ ├── types/ # Server-side type definitions
│ │ ├── utils/ # Calculation helpers
│ │ └── websocket/ # WebSocket server with pub/sub, heartbeat, auth
│ └── tsconfig.json
├── shared/ # Shared TypeScript types (client + server)
├── supabase/
│ ├── migrations/ # 7 SQL migrations (tables, RLS, stored procedures)
│ └── seed/ # Seed data
├── .github/workflows/ci.yml # GitHub Actions CI pipeline
├── Dockerfile # Multi-stage production Docker build
└── render.yaml # Render deployment blueprint
MIT