Skip to content

v0.8.2 — Rate limit counts REST submissions + /status hardening

Choose a tag to compare

@ChronoCoders ChronoCoders released this 17 May 00:16
· 36 commits to main since this release

Two fixes bundled. Commit message highlights the risk-layer change; the dashboard backend hardening landed alongside it (was uncommitted since earlier in the same session).

Headline fix — rate limit counts actual REST submissions

pre_trade_check was recording one rate-limit attempt per evaluation (twice per quote tick — bid side + ask side). With the strategy emitting quotes on every books5 message (~10/sec), the binary recorded ~40 attempts/sec → 2,400 in the 60-second window → tripped rate_limit_threshold = 1000 → 10 consecutive REJECTED_RATE → CircuitBreaker consecutive_rejections trigger → halt. Nothing ever reached the exchange.

Root cause: rate limiting was measuring strategy evaluation rate, not exchange-bound traffic. Pre-trade checks are cheap local gates; only orders that survive the requote logic and slot bookkeeping actually become REST calls.

Fix:

  • RiskManager::pre_trade_check no longer calls record_attempt. The rate-limit branch is READ-ONLY — it trims stale entries and compares the count against the threshold.
  • New RiskManager::record_submission() is the only writer of the rate-limit window. Takes mu_, calls record_attempt(now).
  • OrderManager calls rm_.record_submission() immediately before every REST write (place_order, cancel_order, place_market_order). Read-only query_positions / query_open_orders (reconcile, every 300s) are excluded — negligible against the window.
  • config/config.toml: rate_limit_threshold = 1500 (safe margin under OKX's ~3,000/min for trade writes).
  • tests/test_risk.cpp::RateLimit: rewritten to populate the window via record_submission() and verify pre_trade_check blocks correctly.

Bundled — /api/v5/status hardening + tests

(Landed in the same commit because the working tree was dirty.)

  • db.Ping() → db.PingContext(ctx) with a 2-second deadline so a hung Postgres can't stall /status forever.
  • Silent error swallow → HTTP 500 with JSON {"error": "..."} on lastSnapshotTs failure or pg_ping failure. Surfaces operational degradation instead of returning stale ws_connected=true with no signal.
  • New statusReader interface (same optional-capability pattern as haltedReader / fillStatsReader) so test stubs can inject failures without spinning up Postgres.
  • New helper writeStatusError(w, code, msg) next to writeJSON.
  • Three new Go tests:
    • TestStatusHappyPath — 200 + JSON shape check + pg_connected: true.
    • TestStatusTsQueryFailure — lastSnapshotTs injected error → 500 + body contains lastSnapshotTs.
    • TestStatusPingFailure — dbPing injected error → 500 + body contains pg_ping.

Tests

  • C++: 8/8 pass (test_orderbook, test_spsc, test_strategy, test_risk, test_execution, test_reporter, test_backtest, test_integration) under -O3 -march=native -Wall -Wextra -Werror. Zero warnings.
  • Go: 9/9 pass (added TestStatusHappyPath, TestStatusTsQueryFailure, TestStatusPingFailure).

Known issues in this tag

  • config/config.toml was committed with [backtest].enabled = true and [testnet].enabled = true — the operator-side toggles from the live OKX demo session leaked into the commit. Production default is false. Either flip locally before deploying, or expect a chore follow-up that restores the defaults.
  • Commit message describes only the rate-limit change — bundled dashboard-backend hardening is real but not reflected in the message.

Expected runtime behavior after this tag

With record_submission only counting actual REST writes:

  • Steady-state quote rate: ~10/s strategy evaluations → after should_requote gating, ~1–2 actual place/cancel cycles per second per side
  • Per-side requote = 1 cancel + 1 place = 2 REST writes
  • Steady-state: ~4 writes/sec = 240/min — comfortably under the 1,500/min threshold

The consecutive_rejections circuit breaker trip on rate-limit should not recur in normal operation.