# Build a trading bot

> Stream prices, place orders, and handle disconnects and ambiguous fills correctly.

Source: https://tickwire.io/docs/guides/trading-bot/

## Stream what you need

```json
{
  "type": "subscribe",
  "marketData": [
    { "symbol": "EURUSD", "subscriptions": [
      { "type": "quotes", "intervalInMilliseconds": 250 },
      { "type": "candles", "timeframe": "1m" }
    ] }
  ]
}
```

You'll get `prices` and `candles` frames. Each `prices` frame also carries the account's `equity`,
`margin` and `freeMargin`, read from the terminal. See [Streaming](https://tickwire.io/docs/streaming/).

## Don't trade on stale prices

Check the `health` frames. If the account is `stale`, prices have stopped moving; if it's
`disconnected`, orders will fail. Pause the strategy rather than trading on a frozen quote.

## Place orders without duplicates

Give every order an idempotency key derived from your strategy's own decision id:

```json
{
  "actionType": "ORDER_TYPE_BUY_LIMIT",
  "symbol": "EURUSD",
  "volume": 0.1,
  "openPrice": 1.0712,
  "stopLoss": 20,
  "stopLossUnits": "RELATIVE_PIPS",
  "takeProfit": 40,
  "takeProfitUnits": "RELATIVE_PIPS",
  "expiration": { "type": "ORDER_TIME_SPECIFIED", "time": "2026-09-23T18:00:00Z" },
  "clientId": "bot1_d8812"
}
```

Stops and targets can be absolute prices or relative (price, points, pips, account currency, or
percent of balance).

## Read the response properly

`POST /trade` returns HTTP 200 even when the broker rejects. Success codes are 10008 (placed),
10009 (done), 10010 (partial) and 10025 (no changes); anything else is a failure. Then check our
`outcome`. If it's `unknown`, the broker link dropped mid-order: retry with the same key, never a
new one. See [Idempotency](https://tickwire.io/docs/idempotency/).

## After a reconnect

Resume the stream with `lastSeq` so you don't miss fills. After a broker reconnect, wait for a
fresh tick before sending new orders.
