# Change Log

Newest first. Backward-compatibility rules:
[general-info.md § Versioning](/general-info.md#versioning-and-backward-compatibility).

Changes that only **add** a field, an enum value, an endpoint or a stream are listed here but are
not breaking — your client must already tolerate them.

## v1.0.0 (draft) — documentation verified against the implementation, 2026-10-08

No behaviour changed; the documentation was corrected to match what the service does.

- **New page: [Known limitations (pre-release)](/known-limitations.md)** — the first session against
  the new trade server: no dealer answer (non-market operations stay `ACCEPTED` / `NEW`),
  `origClientOrderId` does not resolve a pending order for modify / cancel / query, cancels fail on
  the server, `GET /v1/userTrades` is empty on staging, and the server reports a UTC+3 offset.
- **`DELETE /v1/order` takes `newClientOrderId`** (idempotency key for the cancel); its response
  carries `clientOrderId` and `side`. `PUT /v1/position`'s response carries `clientOrderId`.
- **`DELETE /v1/position` has no `profit` field** — the realised profit is on the `DEAL` event
  (`rp`) and `GET /v1/userTrades`.
- **Order responses omit `sl`, `tp` and `expiration` when none is set** (they were shown as `"0"` /
  `0`). `GET /v1/order` and `GET /v1/openOrders` still always carry them.
- **A pending order is `NEW` from the moment it is queued**, including on `ACK`; `PUT /v1/order`,
  `DELETE /v1/order` and `PUT /v1/position` always wait up to 5 s like `RESULT`.
- **`fillModes` never contains `RETURN`.** Unmapped MT5 values appear as `UNKNOWN_<n>`.
- **`marginLevel` is `"0.00"`** (two decimals) with no open position.
- Error details documented: `-1106` for `timeInForce` on `MARKET` and for a stray `stopLimitPrice`,
  `-1130` for a malformed `timestamp` / `recvWindow`, `-1128` for `endTime` before `startTime`, batch
  envelope errors (`-1130`, `-1131`, `-1132`), WebSocket control-frame codes and close codes. Codes in
  the catalogue that are not currently returned are listed under
  [error-codes.md § Reserved codes](/error-codes.md#reserved-codes).

## v1.0.0 (draft) — second hardening pass, 2026-09-24

Still draft and pre-launch; listed because each is observable.

- **`-1133` on `POST /v1/order` and on each `POST /v1/batchOrders` item.** A `newClientOrderId` your
  key already used for a different kind of operation — or for a **different `login`** — is refused
  (in a batch, as the error object in that item's slot). The login rule now applies to every endpoint
  that takes `newClientOrderId`, including `DELETE /v1/allOpenPositions`.
- **A retried `POST /v1/order` is answered from the stored record before any validation.** A retried
  limit order whose price the market has since crossed returns its stored status instead of `-2021`.
- **An internal fault in the trade executor is reported as `IN_DOUBT` (`-5009`)**, never as a
  retryable `-1000`: the order may have been sent. Retry only with the same `newClientOrderId`.
- **Orders that could not be queued within about a minute are expired, not sent late.** They read
  `REJECTED` with `-1000` "Service is currently unavailable" on `GET /v1/order`; nothing executed.
  Retrying the same `newClientOrderId` sends it as a fresh order. If such an order did reach the trade
  server after all, its fill still lands and the record changes to `FILLED`.
- **A `REJECTED` order can still become `FILLED`** when its rejection was the execution path's own
  refusal (no `mt5RetCode`) and a later attempt under the same `newClientOrderId` executed. A
  rejection carrying an `mt5RetCode` is final, as before.
- **`POST /v1/batchOrders` answers `-5015` when the execution path is not running**, before any item
  is queued or charged — like every other TRADE endpoint. A trade executor that stopped reading its
  queue is now detected within about 30 seconds.
- **`DELETE /v1/allOpenPositions` retries complete an interrupted sweep.** A position in the original
  snapshot whose close was never queued is closed on the retry if it is still open, and reported
  `REJECTED` (`-5006`) if it has closed since. A concurrent identical sweep returns the first sweep's
  snapshot.
- **`C` (`clientOrderId`) on `DEAL` / `ORDER_UPDATE` is only published to the key that owns it.**
  Where two keys share a login, the other key receives the event without `C`.
- **`GET /v1/accounts` and the user data stream list exactly the logins the REST endpoints accept.**
  An archived account is no longer listed or streamed.
- **Market-stream connections are capped** at 20 concurrent per client IP (and by overall capacity);
  an excess connection is refused before the upgrade with `-1008`.
- **A user data stream is also closed (`1011`) when the server's own intake falls behind** for your
  logins, not only when your connection reads too slowly.

## v1.0.0 (draft) — clarifications, 2026-09-19

Still draft, still pre-launch, so none of this is a breaking change to a live client. Listed because
each one is behaviour an integrator can observe.

- **`PARTIALLY_FILLED` is now a market-execution status.** `POST /v1/order` with `type=MARKET`,
  `DELETE /v1/position` and each `DELETE /v1/allOpenPositions` leg can report it. `executedVolume` is
  the sum of the deals that have filled the order and `price` their volume-weighted average; the
  status becomes `FILLED` when the sum reaches the requested `volume`. Previously a partial fill was
  reported as `FILLED` with the volume you _requested_.
- **`DELETE /v1/allOpenPositions` retries replay the original snapshot.** Reusing a
  `newClientOrderId` returns the first sweep's legs and closes nothing opened since — including after
  a sweep that found nothing, which stays `requested: 0`. A fresh sweep needs a fresh id.
- **`PUT /v1/order`: `timeInForce` is conditionally mandatory.** It is required when the service
  cannot determine the order's current expiration mode — typically an order this API did not place.
  `-1102` rather than a guess, because a guess would rewrite a `DAY` order as `GTC`.
- **Retries of `PUT`/`DELETE /v1/order`, `PUT`/`DELETE /v1/position` are answered from the stored
  record**, not re-validated: a retried close whose position has since closed returns its stored
  `FILLED` outcome instead of `-2023`, and a retried modify returns its stored outcome instead of
  `-5005`.
- **A failure to queue an order answers `-5009`** (execution status UNKNOWN), not `-1000`: the write
  may have landed before the error surfaced, so the order is recorded as still open and can still
  fill. Retry it identically, same `newClientOrderId`.
- **TRADE endpoints answer `-5015` when the execution path is not running**, before any order is
  created — instead of accepting an order that would sit queued indefinitely.
- **A user data stream that falls too far behind is CLOSED** (`1011`, "queue overflow; resnapshot")
  rather than silently dropping events. Reconnect and resnapshot via REST, as documented.
- **A user data stream stops delivering for a login that leaves the key's scope**, within about 30
  seconds, instead of at the listenKey's 24-hour expiry. The socket closes when nothing remains in
  scope.
- **A malformed control frame no longer closes the connection.** A wrong-typed `method` or `params`
  item answers the documented error frame and the socket stays open.

## v1.0.0 (draft) — initial contract

**Status: draft.** The service is being built to this document. Endpoints, field names and error
codes are the contract; base URLs are placeholders pending DNS. Nothing is live yet.

**REST**

- General: `GET /v1/ping`, `GET /v1/time`, `GET /v1/exchangeInfo`.
- Market data: `GET /v1/ticker/price`, `GET /v1/ticker/bookTicker`, `GET /v1/ticker/24hr`,
  `GET /v1/klines`, `GET /v1/ticks`.
- Trade: `POST /v1/order`, `PUT /v1/order`, `DELETE /v1/order`, `GET /v1/order`,
  `GET /v1/openOrders`, `GET /v1/allOrders`, `GET /v1/positions`, `PUT /v1/position`,
  `DELETE /v1/position`, `DELETE /v1/allOpenPositions`, `POST /v1/batchOrders`.
- Account: `GET /v1/account`, `GET /v1/accounts`, `GET /v1/userTrades`.
- User data stream: `POST /v1/listenKey`, `PUT /v1/listenKey`, `DELETE /v1/listenKey`.

**WebSocket**

- Market streams: `<SYMBOL>@tick`, `<SYMBOL>@bookTicker`, `<SYMBOL>@kline_<interval>`,
  `<SYMBOL>@ticker`, `<SYMBOL>@depth` (optional, enabled on request).
- User data stream events: `DEAL`, `ORDER_UPDATE`, `POSITION_UPDATE`, `ACCOUNT_UPDATE`,
  `listenKeyExpired`.

**Model**

- Binance-style conventions: `X-YBX-APIKEY`, HMAC-SHA256 over query string + body, `recvWindow`,
  weight and order-count headers, 429 → 418 escalation, `{"code","msg"}` errors,
  SUBSCRIBE/UNSUBSCRIBE/LIST_SUBSCRIPTIONS, combined `{"stream","data"}` frames.
- MT5 semantics: lots, bid/ask, tickets, SL/TP on the position, swaps. No mark price, no funding, no
  liquidation engine.
- **Hedging only** — many position tickets per symbol, no `positionSide`.
- **A mandatory `login` on every account-scoped call** — one key trades many MT5 accounts. The one
  deliberate departure from Binance's key-is-an-account model.
- **`newClientOrderId` is the idempotency key.** A retry with the same id never creates a second
  order.
- Order lifecycle: `newOrderRespType` `ACK` (default) / `RESULT`, with `ACCEPTED`, `FILLED`,
  `REJECTED` and `IN_DOUBT` statuses for market executions.
- `mt5RetCode` passthrough on execution errors.
- Sandbox is demo-group MT5 logins on the same endpoints — there is no separate testnet host.

**Known limits in v1**

- No account provisioning. Accounts are created by the broker.
- No deep tick history — `GET /v1/ticks` serves an in-memory ring buffer.
- No `1w` / `1M` kline intervals; `1d` buckets are UTC-aligned, not broker-session aligned.
- `ACCOUNT_UPDATE` carries balance and credit only; equity and margin need `GET /v1/account`.
- **No commission on an open position.** Commission is booked on deals — read it from
  `GET /v1/userTrades` or the `DEAL` event's `n` field. `GET /v1/positions` carries `profit` and
  `swap` only.
- The trading surface can be switched off independently of market data and account reads; `TRADE`
  endpoints and the user data stream then return `-1017`.
- `bookTicker` sizes are always `"0"` — MT5 quotes carry no top-of-book size.
- `@depth` is off unless the broker enables it for the symbol. Subscribing always succeeds and then
  delivers nothing — silence means "not enabled", not "not subscribed".
- `GET /v1/exchangeInfo` is **not** filtered to your key's scope. Sending your key changes
  `rateLimits[]` only; the symbol list is every instrument on the trade server, because the Manager
  API has no bulk per-group symbol read.
- `GET /v1/userTrades` returns **closing deals only** (`entry` is `OUT`, `INOUT` or `OUT_BY`, never
  `IN`), and its `side` is the direction **as stored** by the broker's history writer, which is not
  always the deal's own direction. Use the `DEAL` event's `S` for an unambiguous deal direction.
- On `ORDER_UPDATE`, `tif` is inferred (`GTD` when an expiration is set, `GTC` otherwise — `DAY` and
  `GTD_DAY` are not distinguishable), `sp` is always `"0"`, and `ot` may be `BUY`/`SELL` for a market
  order passing through the book.
- `ticker/24hr.priceChangePercent` (and `@ticker`'s `P`) has two provenances — the trade server's own
  statistic on the read path, derived from open/last on the streamed path. They agree to rounding.
- A market-data or account read that cannot reach the trade server returns `-5015` with no
  `mt5RetCode`.
