HERMES

A live algorithmic trading platform — designed, built, and operated solo, 24/7.

TypeScript · PostgreSQL · Binance Futures · Running in production since 2026.

01 / Architecture

Architecture

Hermes system architecture Binance Futures feeds the backend over REST and WebSocket. The backend writes to PostgreSQL and drives a React dashboard, a Discord bot, and an LLM analysis layer. The dashboard and the Discord bot stay in sync over a bidirectional alert channel. REST + WebSocket alert sync Binance Futures Market data via WebSocket; orders via REST — through a rotating proxy pool. Backend Node.js/TypeScript · Railway Single service: ingestion, execution, reconciliation, health watchdogs. PostgreSQL Prisma Source of truth for setups, orders, and fills. React dashboard Operational cockpit — live positions, charts, controls. Discord bot Alerts and remote control from the same state machine. LLM analysis Claude API Contextual market analysis and alert triage. Hermes system architecture Binance Futures feeds the backend over REST and WebSocket. The backend writes to PostgreSQL and drives a React dashboard, a Discord bot, and an LLM analysis layer. The dashboard and the Discord bot stay in sync over a bidirectional alert channel. REST + WebSocket alert sync Binance Futures Market data via WebSocket; orders via REST — through a rotating proxy pool. Backend Node.js/TypeScript · Railway Single service: ingestion, execution, reconciliation, health watchdogs. PostgreSQL Prisma Source of truth for setups, orders, and fills. React dashboard Operational cockpit — live positions, charts, controls. Discord bot Alerts and remote control from the same state machine. LLM analysis Claude API Contextual market analysis and alert triage.

02 / Highlights

Engineering highlights

  • Backtesting engine

    Custom engine validated bar-by-bar against research notebooks. Models fees, slippage, and funding — not idealized fills.

  • Idempotent execution

    Every external side effect wrapped in idempotency guards with state reconciliation against the exchange. Restarts are boring.

  • Incident response

    Failures are documented, root-caused, and regression-guarded. See the incident log below.

  • LLM analysis layer

    Claude API integrated into the pipeline for contextual market analysis and alert triage.

03 / Incidents

Incident log

Real systems fail. These are some of the failures, and what they changed.

INC-001 WebSocket event handler was dead code
Impact
Order-fill detection silently ran on a 30-second polling fallback instead of sub-second WebSocket events. The reconciliation safety net masked the failure — nothing visibly broke, which is exactly why it lasted.
Root cause
The exchange client library transforms raw WebSocket payloads before delivery — renaming and flattening fields. The handler checked a raw-format field that no longer existed, so it never matched and never ran.
Fix
Aligned the event interface and field access with the library's actual transformed shape.
Prevention
Runtime payload shapes are verified at integration boundaries instead of trusted from docs; a log at the callback boundary now exposes shape drift immediately.
INC-002 False-positive stream-silence alerts
Impact
Recurring "stream silent — forcing reconnect" alerts whenever a pending order was resting. No real outage ever occurred.
Root cause
The watchdog treated silence on the user-data stream as a dead connection. That stream only emits on account state changes — with a pending order resting, silence is the expected state, not a failure.
Fix
Liveness is now derived from a continuously-emitting market stream; the user-data check became a precautionary key refresh plus a forced reconciliation sweep.
Prevention
Connectivity is monitored unconditionally, and "no data received" is no longer conflated with "connection is dead."
INC-028 Dual-ID mismatch on stop-loss fills
Impact
Stop-loss fills executed correctly on the exchange, but the primary WebSocket handler exited silently without matching them; the reconciliation net closed the records roughly 30 minutes late.
Root cause
The exchange uses two identifiers for conditional orders — placement returns an algo ID, while the fill event carries a regular order ID. The handler queried storage by the fill's order ID, which never matched the stored algo ID, and returned without logging anything.
Fix
Both identifiers became first-class schema columns, normalized at the library boundary; the handler matches on either ID and backfills the missing one on first fill.
Prevention
Unit and integration tests replay the exact mismatch, and unmatched fill events now log a warning — any future ID-matching bug is immediately visible.

04 / Stack

  • TypeScript
  • Node.js
  • PostgreSQL
  • Prisma
  • React
  • Lightweight Charts
  • Railway
  • Cloudflare
  • Webshare Proxies
  • Binance Futures API
  • Discord API
  • Claude API