Skip to content

Architecture

AceTrader is a global low-latency cryptocurrency price feed and paper trading service built on Cloudflare Workers. It provides real-time and historical price data for 26+ crypto assets via Hyperliquid exchange.

Key Patterns

Event-Driven Order Execution

All orders flow through a deterministic pricing pipeline. Every order uses Math.floor(Date.now() / 1000) as its target timestamp — no "latest available" price, always a specific second.

Market Order Flow

1. User submits market order at T=100
2. UserPortfolio queries PriceAlert.getPriceData(T=100)
3a. Price available → Execute immediately
3b. Price NOT available →
    ├─ Store as PENDING in SQLite
    ├─ Register price arrival alert
    └─ Return { status: 'PENDING', orderId }
4. PriceAlert alarm fetches T=100 from PriceCollector
5. PriceAlert triggers UserPortfolio.onPriceArrival(T=100, prices)
6. Execute all pending orders for T=100

Limit Order Flow

1. User submits limit order at T=100
2. UserPortfolio queries PriceAlert.getPriceData(T=100)
3a. Price available + target met → Execute immediately
3b. Otherwise →
    ├─ Store limit order with reserved margin
    ├─ Register LIMIT_ORDER alert
    └─ Return limitOrderId + status=PENDING
4. PriceAlert evaluates when price crosses threshold
5. PriceAlert triggers UserPortfolio.onAlert()
6. Execute limit order with trigger price

PriceAlert as Read Replica

UserPortfolio calls getPriceAlertDO().getPriceData() for all price queries. The in-memory cache in PriceAlert reduces load on PriceCollector.

Two Alert Types

  • PRICE_CONDITION: Triggers when price crosses a threshold (limit orders, stop loss, take profit)
  • PRICE_ARRIVAL: Triggers when a timestamp's price data arrives (market orders, close orders)

Geolocation Routing

DOs use automatic location hint detection via Cloudflare cf properties. Self-discovery via cdn-cgi/trace. Naming convention: ${locationHint} (e.g., wnam, weur, apac).

AccountValueTracker Callback Flow

UserPaperTradePortfolio          AccountValueAggregationTracker        PriceCollector
        │                                    │                              │
        │ subscribe(capital, userId)         │                              │
        │───────────────────────────────────>│                              │
        │                                    │                              │
        │ updateCoefficients(snapshot)       │                              │
        │───────────────────────────────────>│ (on each position change)    │
        │                                    │                              │
        │                                    │ alarm() every 5 min          │
        │                                    │    getPriceData(range) ──────>│
        │                                    │    <── prices[]               │
        │                                    │                              │
        │                                    │ Calculate account values     │
        │                                    │ using coefficient formula    │
        │                                    │                              │
        │ onHourlyExtremes(params)           │ (at hour boundary)           │
        │<───────────────────────────────────│                              │
        │                                    │                              │
        │ onDailyExtremesFinalized()         │ (at UTC midnight)            │
        │<───────────────────────────────────│                              │
        │                                    │                              │
        │ Check MLL breach                   │                              │
        │ if bottom <= minBalance:           │                              │
        │   failChallenge()                  │                              │

Formula: accountValue = constantTerm + Σ(coefficient[token] × price[token])