> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ocx.global/llms.txt
> Use this file to discover all available pages before exploring further.

# Best practices

> Inventory and risk, the dead-man switch, reconnect and heartbeat handling for a resilient market-making bot

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](/market-makers/fill-stream)
position deltas and let it drive your next ladder.

<AccordionGroup>
  <Accordion title="Skew quotes as inventory builds" icon="scale-unbalanced">
    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.
  </Accordion>

  <Accordion title="Shrink size near limits" icon="down-left-and-up-right-to-center">
    Reduce quoted quantity — or widen — as your position approaches your
    self-imposed risk limit, so you stop adding to a one-sided book.
  </Accordion>

  <Accordion title="Use reduceOnly for exits" icon="arrow-right-from-bracket">
    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.
  </Accordion>

  <Accordion title="Track your own margin headroom" icon="gauge-high">
    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.
  </Accordion>
</AccordionGroup>

<Note>
  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.
</Note>

## 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:

```bash theme={null}
POST {API_BASE}/perps/orders/cancel-all
{ }   # omit body to pull everything, or scope by marketId
```

<Steps>
  <Step title="Detect the trigger">
    Your reference feed goes stale, the fill stream stops heartbeating, a
    place/replace call errors repeatedly, or you receive SIGTERM.
  </Step>

  <Step title="Cancel everything immediately">
    Fire `cancel-all` (unscoped) before doing anything else, so no stale quote can
    be hit while you are blind.
  </Step>

  <Step title="Stand down until healthy">
    Do not re-quote until your feed is fresh and your streams are reconnected and
    re-snapshotted.
  </Step>
</Steps>

<Warning>
  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.
</Warning>

<Tip>
  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.
</Tip>

## Reconnect and heartbeat

SSE streams are long-lived and diff server-side. Build your client to survive
drops without leaving orphaned quotes.

<AccordionGroup>
  <Accordion title="Snapshot then delta" icon="layer-group">
    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.
  </Accordion>

  <Accordion title="Detect gaps with eventSeq" icon="hashtag">
    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.
  </Accordion>

  <Accordion title="Watch the heartbeat" icon="heart-pulse">
    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.
  </Accordion>

  <Accordion title="Reconnect with backoff" icon="arrows-rotate">
    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.
  </Accordion>
</AccordionGroup>

## Idempotency and identifiers

<CardGroup cols={2}>
  <Card title="clientOrderId" icon="fingerprint">
    Attach a `clientOrderId` to each order/quote level for idempotent client-side
    tracking, so a retried request doesn't double-place.
  </Card>

  <Card title="quoteId" icon="tag">
    Tag each quoting cycle with a `quoteId` to group and replace a whole batch, and
    to scope a targeted cancel.
  </Card>
</CardGroup>

## 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

<AccordionGroup>
  <Accordion title="Least privilege" icon="lock">
    Give an executing bot a `trade`-scoped key; a monitoring-only process gets
    `read`. A key cannot mint or revoke another key.
  </Accordion>

  <Accordion title="Pin and expire" icon="shield-halved">
    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`.
  </Accordion>

  <Accordion title="Store the secret once" icon="key">
    The plaintext secret is returned only at creation. Keep it in a secret store,
    never in code or logs.
  </Accordion>
</AccordionGroup>

## A resilient maker loop

<Steps>
  <Step title="Start up">
    Authenticate, load the market list and your fee tier, connect one fill stream
    per market, and re-snapshot state.
  </Step>

  <Step title="Each cycle">
    Compute fair value from your reference feed → (optionally) `preview` →
    `quotes/bulk` with `cancelAll: true`.
  </Step>

  <Step title="On fill">
    Update inventory from the position delta, then skew/shrink and re-quote.
  </Step>

  <Step title="On trouble">
    Stale feed, disconnect, repeated errors, or shutdown → `cancel-all`, back off,
    reconnect, re-snapshot, and only then resume.
  </Step>
</Steps>
