prsweet's GitHub avatar

prsweet

backend engineer

I build the parts of software you don’t see.

    cex-y

    In development Source

    A crypto exchange, split the way production exchanges are. A stateless gateway authenticates and validates orders. Kafka carries them as commands. A Rust engine owns the order books and balances, and nothing else can touch them.

    Request

    1. 1Client places an order over REST.
    2. 2Gateway verifies the JWT, validates the schema and publishes a command.
    3. 3Kafka stores it in an ordered, durable log.
    4. 4Consumer routes it by symbol and asks the BalanceActor to lock funds.
    5. 5Once funds are locked, the order goes to its market's SymbolActor to be matched.

    Response

    1. 6SymbolActor emits fills back to the BalanceActor.
    2. 7BalanceActor settles each fill from locked to available, then replies.
    3. 8Consumer sends the result back to the gateway.
    4. 9Client receives the order ID, status and fills.

    Durability

    • SSnapshot. Books and balances are written to a snapshot periodically, tagged with the Kafka offset they reflect.
    • RQueue replay. On restart, the engine loads the latest snapshot and replays Kafka from that offset, so no command is lost or applied twice.
    • DInterval DB writes. Trades and balances are flushed to Postgres in batches on a timer, keeping database I/O off the matching path.

    One actor per market

    Each symbol's book belongs to a single Tokio task fed by an mpsc channel. The hot path needs no mutexes, and separate markets run on separate cores. Actors are spawned lazily the first time a symbol appears.

    Funds are locked before matching

    Every order first goes to the BalanceActor as CheckAndLock, which replies over a oneshot channel. Only approved orders reach the book. Fills settle from locked into the counterparty's available balance, and a cancel releases the lock.

    O(log n) best price, O(1) cancel

    Bids and asks are BTreeMap<u64, PriceLevel>. Each level is a doubly linked FIFO indexed by order ID in a HashMap, so removing an order from the middle of the queue never means a scan.

    Money is an integer

    Prices and quantities are u64 ticks. There are no floats anywhere near a balance.

    The log is the source of truth

    State in memory is fast but disposable. A snapshot plus Kafka replay rebuilds it exactly, so the database can lag behind on an interval without ever being wrong. The gateway stays stateless, and malformed messages are logged and skipped instead of crashing the loop.

    Try the matching loop.

    This is the engine's match_order, ported line for line to TypeScript and running in your browser. It uses the same rules: price-time priority, limit remainders rest on the book, and market remainders are cancelled.

    PriceSizeOrders
    —
    Trades
    1. No trades yet.

    DSA.

    Order books, priority queues and routing started out as contest problems for me. Every number below is pulled live from the judges' APIs through this site's /api/cp.

    Activity

    Submissions and contributions over the last year

    LessMore

    Codeforces

    prsweet
    Rating—
    Max—
    Solved—
    Contests—

    AtCoder

    prsweet
    Rating—
    Max—
    Accepted—
    Contests—

    Difficulty solved

    Codeforces problem rating

    Most-solved topics

    Codeforces tags

      LeetCode

      prsweet
      Solved—
      Contest rating—
      Contests—

      Tools, with receipts.

      Every tool here is linked to the section where it's used.