Skip to main content
A production market maker on OCX is defined less by its pricing and more by how it behaves when things go wrong — a lost feed, a disconnect, a runaway inventory. This page collects the operational patterns that keep a quoting bot safe.

Inventory and risk

Every fill moves your inventory. Read it from the fill stream position deltas and let it drive your next ladder.
As you accumulate a long position, lower both sides of your ladder slightly so you are more likely to sell and less likely to buy more (and vice versa when short). This mean-reverts your inventory toward flat without crossing the spread.
Reduce quoted quantity — or widen — as your position approaches your self-imposed risk limit, so you stop adding to a one-sided book.
Tag exit-only orders reduceOnly so they can only shrink a position, never flip it. Use POST {API_BASE}/perps/positions/close to flatten fully or partially.
OCX enforces portfolio margin server-side: every order passes a margin gate before acceptance. A rejected order returns an insufficient-margin error — handle it, don’t retry blindly. Watch your headroom from GET {API_BASE}/perps/balances and GET {API_BASE}/perps/positions and de-risk proactively rather than waiting for rejections. Positions that breach maintenance margin are liquidated by the exchange.
The margin gate only rejects on business rules (insufficient margin). Distinguish those from transport/parse errors in your client — a margin reject is a signal to de-risk, not to reconnect.

Dead-man switch

Wire a kill-switch that pulls all your quotes the moment your bot can no longer be sure they still reflect a live price — feed loss, an unhandled error, or shutdown. The clean primitive is cancel-all:
POST {API_BASE}/perps/orders/cancel-all
{ }   # omit body to pull everything, or scope by marketId
1

Detect the trigger

Your reference feed goes stale, the fill stream stops heartbeating, a place/replace call errors repeatedly, or you receive SIGTERM.
2

Cancel everything immediately

Fire cancel-all (unscoped) before doing anything else, so no stale quote can be hit while you are blind.
3

Stand down until healthy

Do not re-quote until your feed is fresh and your streams are reconnected and re-snapshotted.
Never leave resting quotes behind a dead price feed. If your fair value goes stale and you keep quoting off it, you will be adversely selected. Treat a stale reference feed as a hard stop: cancel-all, then re-quote only once the feed recovers.
Because a quotes/bulk call with cancelAll: true already replaces your prior ladder atomically, your steady-state loop is self-cleaning — the standalone cancel-all is reserved for the abort path.

Reconnect and heartbeat

SSE streams are long-lived and diff server-side. Build your client to survive drops without leaving orphaned quotes.
On connect you get a full snapshot; thereafter fold in delta/update events. Keep local book, trade, and position state and mutate it in place.
Private streams carry a monotonically increasing eventSeq (and the public book stream an SSE id:). If a sequence jumps, close and reconnect — you will get a fresh snapshot to re-sync from.
Idle streams send a comment heartbeat (:heartbeat / : keepalive) roughly every 15s. If heartbeats stop arriving, consider the connection dead and reconnect — do not wait for a TCP timeout.
On any disconnect, reconnect with exponential backoff and jitter. On reconnect, re-snapshot before trusting your local state, and re-run your quoting cycle only once state is consistent.

Idempotency and identifiers

clientOrderId

Attach a clientOrderId to each order/quote level for idempotent client-side tracking, so a retried request doesn’t double-place.

quoteId

Tag each quoting cycle with a quoteId to group and replace a whole batch, and to scope a targeted cancel.

Rate limits

The gateway rate-limits per client; authenticated API-key traffic receives a higher budget than anonymous traffic. Rate-limit state is returned in standard X-RateLimit-* response headers — back off when X-RateLimit-Remaining reaches 0, and handle 429 with backoff rather than tight retries.

Key hygiene

Give an executing bot a trade-scoped key; a monitoring-only process gets read. A key cannot mint or revoke another key.
Pin allowedIps to your egress addresses and set expiresInDays so keys rotate. Revoke with DELETE {API_BASE}/perps/me/api-keys/{id}; list metadata (never the secret) with GET {API_BASE}/perps/me/api-keys.
The plaintext secret is returned only at creation. Keep it in a secret store, never in code or logs.

A resilient maker loop

1

Start up

Authenticate, load the market list and your fee tier, connect one fill stream per market, and re-snapshot state.
2

Each cycle

Compute fair value from your reference feed → (optionally) previewquotes/bulk with cancelAll: true.
3

On fill

Update inventory from the position delta, then skew/shrink and re-quote.
4

On trouble

Stale feed, disconnect, repeated errors, or shutdown → cancel-all, back off, reconnect, re-snapshot, and only then resume.