# Order Lifecycle

Base URL `https://trade-api.yellowboxmarkets.com`. Conventions, signing and error format:
[General Info](/general-info.md).

**Every endpoint on this page takes a mandatory `login`** — the MT5 account the call applies to.
See [the `login` parameter model](/general-info.md#the-login-parameter).

| Endpoint | Security | Weight | Counts against `ORDERS` |
| - | - | - | - |
| [`POST /v1/order`](/trade/new-order.md) | `TRADE` | 1 | 1 |
| [`PUT /v1/order`](/trade/modify-pending-order.md) | `TRADE` | 1 | 1 |
| [`DELETE /v1/order`](/trade/cancel-pending-order.md) | `TRADE` | 1 | 1 |
| [`GET /v1/order`](/trade/query-order.md) | `USER_DATA` | 1 | — |
| [`GET /v1/openOrders`](/trade/current-pending-orders.md) | `USER_DATA` | 1 | — |
| [`GET /v1/allOrders`](/trade/all-orders.md) | `USER_DATA` | 5 | — |
| [`GET /v1/positions`](/trade/open-positions.md) | `USER_DATA` | 1 | — |
| [`PUT /v1/position`](/trade/modify-position-sltp.md) | `TRADE` | 1 | 1 |
| [`DELETE /v1/position`](/trade/close-position.md) | `TRADE` | 1 | 1 |
| [`DELETE /v1/allOpenPositions`](/trade/close-all-open-positions.md) | `TRADE` | 5 | 1 |
| [`POST /v1/batchOrders`](/trade/place-multiple-orders.md) | `TRADE` | 1 per item | 1 per item |

> **Pre-release.** On the current trade server several of these operations do not yet reach the
> statuses described below. Read [Known limitations](/known-limitations.md) before testing.

Every `TRADE` endpoint answers `-1017` while the trading surface is switched off, and `-5015` when
the execution path is not running — both before anything is queued and before the `ORDERS` bucket
is charged. The `USER_DATA` reads on this page stay available in both cases.

## Order lifecycle

**Read this before writing any order code.** The execution model is the one part of this API that
has no Binance equivalent.

### How an order reaches the market

An order is not executed inside the HTTP request. The API validates it, assigns it a client order
id, and writes it to a **durable queue**. A trade executor drains that queue and issues the
operation against the MT5 trade server. The fill is confirmed from the **deal record** the trade
server produces.

That means the HTTP response tells you how far the order got by the time we answered — not
necessarily its final state.

### `newOrderRespType`

| Value | Behaviour |
| - | - |
| `ACK` | **Default. Recommended.** Returns as soon as the order is durably queued. `status` is `ACCEPTED` for a market order and `NEW` for a pending order. |
| `RESULT` | Waits up to **5 seconds** for the trade server's answer, then returns the terminal state if it arrived. |

`newOrderRespType` is accepted by `POST /v1/order`, `DELETE /v1/position` and `POST /v1/batchOrders`
only; any other value than `ACK` or `RESULT` is `-1130`. `PUT /v1/order`, `DELETE /v1/order` and
`PUT /v1/position` take no `newOrderRespType` and **always** behave like `RESULT` — they wait up to 5
seconds and then answer with whatever status the operation has reached. `DELETE /v1/allOpenPositions`
always behaves like `ACK`.

With `RESULT` there are three outcomes:

| Outcome | HTTP | Body |
| - | - | - |
| The server filled it | `200` | `status: "FILLED"` with `dealId`, `orderId`, `positionId`, `price`, `executedVolume`. |
| The server rejected it | `4XX` | The error envelope plus `status: "REJECTED"`, `clientOrderId`, `login`, and `mt5RetCode`. |
| No answer within 5 s | `200` | `status: "ACCEPTED"`. **The order has very likely executed.** Confirm on the user data stream or with `GET /v1/order`. |

> **`RESULT` is not a guarantee of a synchronous answer.** Whether the trade server acknowledges an
> operation in-band is a property of the server, not of this API. If it does not, `RESULT` behaves
> exactly like `ACK` after burning 5 seconds of your latency budget. Confirm executions from the
> user data stream, and treat `RESULT` as a convenience for low-rate, interactive use.
>
> The current trade server is such a server for every operation that creates no deal — see
> [Known limitations § The trade server sends no dealer answer](/known-limitations.md#the-trade-server-sends-no-dealer-answer).

Use `ACK` for bursts. One signal fanned across many accounts should be `POST /v1/batchOrders` with
`ACK`; the fills arrive as `DEAL` and `POSITION_UPDATE` events.

### Status values

**Market execution** (`POST /v1/order` with `type=MARKET`, `DELETE /v1/position`,
`DELETE /v1/allOpenPositions`):

| Status | Meaning | Terminal |
| - | - | - |
| `ACCEPTED` | Durably queued. Not yet confirmed by the trade server. | no |
| `PARTIALLY_FILLED` | Part of the requested volume executed. `executedVolume` is what has filled so far; more `DEAL` events may follow. | no |
| `FILLED` | Executed. `dealId` / `positionId` are populated and `executedVolume` equals `volume`. | yes |
| `REJECTED` | The trade server refused it. `mt5RetCode` says why. | yes |
| `IN_DOUBT` | The operation may or may not have reached the trade server, and the service cannot determine which. **Do not retry with a new client order id.** Check `GET /v1/positions` and the user data stream; retrying the _same_ `newClientOrderId` is safe. | no |

**Pending orders** (`type` other than `MARKET`):

| Status | Meaning | Terminal |
| - | - | - |
| `NEW` | Accepted. Reported from the moment the placement is durably queued, and while the order rests on the server. `NEW` alone does not prove the order reached the book — an `ORDER_UPDATE` with its `orderId`, or `GET /v1/openOrders`, does. | no |
| `PARTIALLY_FILLED` | Part of the volume has executed; the remainder is still resting. | no |
| `FILLED` | Fully executed. A position now exists. | yes |
| `CANCELED` | Cancelled by you or by the broker. | yes |
| `EXPIRED` | Reached its expiration. | yes |
| `REJECTED` | Refused at placement. | yes |

New status values may be added. Treat an unrecognised status as non-terminal and keep polling
`GET /v1/order`.

**`executedVolume` accumulates across deals.** MT5 may fill one order with several deals. Each one
arrives as its own `DEAL` event, and `executedVolume` on `GET /v1/order` is their sum while `price`
is their **volume-weighted average**. Until the sum reaches the requested `volume` the status is
`PARTIALLY_FILLED`; a redelivered deal never double-counts.

### Idempotency

`newClientOrderId` **is** the idempotency key.

- It must be unique per API key, up to 36 characters, `[A-Za-z0-9-_.]`.
- Resending a request with a `newClientOrderId` the key has already used **never creates a second
  order**. You get back the stored outcome — `ACCEPTED` if it is still in flight, or the terminal
  result if it has one.
- This is what makes a retry after a timeout, a `503`, or a dropped connection safe.

**A retry is answered from the stored record, not re-validated.** This matters most on the
operations whose target moves: retrying a `DELETE /v1/position` whose position has since closed
returns the stored `FILLED` outcome rather than `-2023`, and retrying a `PUT /v1/order` or
`PUT /v1/position` whose change has already been applied returns the stored outcome rather than
`-5005`. The same holds for `POST /v1/order` and every `batchOrders` item: a retried `BUY_LIMIT`
whose price the market has since crossed returns its stored `NEW` (or later) outcome, not `-2021`.
The response is built from the record, so a field the first response read from a live snapshot may
be absent on the replay. A `newClientOrderId` this key already used for a _different kind_ of
operation (an id that placed an order, sent on a close) **or for a different `login`** is refused
with `-1133` on every endpoint — in a batch, as the error object in that item's slot — and is
neither replayed as this operation nor executed again.

**Always set it.** If you omit it the service generates one, and you lose the ability to retry
safely, because a retry then looks like a brand new order — including on `DELETE /v1/position`,
`PUT /v1/order`, `PUT /v1/position` and `DELETE /v1/allOpenPositions`.

Idempotency records are retained for at least 24 hours. After that a reused id is treated as new.

### What to do on each failure mode

| Situation | Do |
| - | - |
| Connection dropped, no response | Retry the identical request, same `newClientOrderId`. |
| `503` with "execution status is UNKNOWN" (`-5009`) | Same: retry identically, or check `GET /v1/order?origClientOrderId=`. This also covers a failure to queue the order — it may still have been queued, so it is recorded as open, not rejected. |
| `503` with `-5015` "no connection to the trade server" on a TRADE endpoint | The execution path is not running; **nothing was queued and no order exists**. Retry with backoff. |
| `200` with `status: "ACCEPTED"` and no fill event after a few seconds | `GET /v1/order?origClientOrderId=`. Do not send a second order. |
| `GET /v1/order` shows `REJECTED` with `-1000` "Service is currently unavailable" | The service could not queue the order within about a minute of accepting it (for example the trade executor was down), so it **expired it unexecuted** rather than send it late at a different price. Nothing executed. Retrying the **same** `newClientOrderId` sends it as a fresh order — decide against the current market first. |
| `status: "IN_DOUBT"` | Reconcile against `GET /v1/positions` before doing anything else. |
| `4XX` with a validation code (`-11xx`, `-40xx`) | Fix the request. The order does not exist. |
| `4XX` with `-5001` (requote) or `-5002` (price changed) | The order did not execute. Re-price and send a **new** `newClientOrderId`. |

### Comments and deal attribution

The MT5 `comment` field is how an execution is traced back to the request that caused it, so the
service **reserves part of it**.

- Your `comment` may be up to **24 characters**.
- The service appends a short correlation tag, making the stored MT5 comment
  `<your comment><tag>`. The tag is 7 characters and is not documented as a parseable format —
  do not build against it.
- On the user data stream, **`DEAL.c` is the MT5 comment as stored, tag included.** Strip the last
  7 characters to recover what you sent, or simply ignore `c` and use `C`.
- **`DEAL.C` is the resolved `clientOrderId`** — the field to route on. The service resolves it
  from the tag, or directly from the trade server's answer when the server provides one.
- The tag is also set on **closes and SL/TP modifications** this API issues, so `DEAL.c` on an
  API-initiated close carries a tag too, and that close is attributable to the
  `newClientOrderId` you sent on `DELETE /v1/position`.

A deal produced by the account holder, the broker, or a stop loss or take profit firing carries no
tag. Those deals arrive with no `C`, which is how you tell them apart from your own.

### Hedging

Positions are **hedged**. A `BUY` on a symbol that already has a `SELL` position opens a **second,
independent position** with its own `positionId`; it does not net against the first. Closing is
always by `positionId`.

There is no `positionSide` parameter and no netting mode.
