Orders, positions & deals
The lifecycle
- An order is an instruction. A market order fills right away; a pending order waits for its price.
- A filled order opens a position: an open trade with floating P/L that you modify (SL/TP) and close.
- Every fill and close books a deal. Entry deals have
direction: 0; exit deals havedirection: 1and carry the realizedprofit,commissionandswap. Your trade history is your list of deals.
Orders are confirmed asynchronously
This is the most important thing to understand before you write a bot.
POST /api/v1/orders/accounts/me only checks that the request is well formed,
then hands the order to the trading engine. A 201 means "accepted for
processing", not "filled":
{ "success": true, "code": 201, "data": "new market_order BUY 0.1 EURUSD @ 1.08234" }
The response has no order ID. The engine's result arrives on the WebSocket, usually within milliseconds:
| Outcome | What you receive |
|---|---|
| Market order filled | order_create and order_update (status 3 filled), position_create with the new position (its open_price is your fill), and a deal_create entry. Don't depend on their exact order. |
| Pending order placed | order_create with the order (including its id) |
| Rejected by a trading rule (margin, lot size, SL/TP, market closed…) | bad_request (or forbidden) with payload.reason: "order_create" and the reason in payload.message — or order_rejected with rejection_msg |
So a bot should:
- keep the WebSocket open before it trades,
- send the order and mark it "in flight",
- resolve it on
position_create/order_create/ a rejection for that symbol, and - if nothing arrives within a few seconds, check
GET /api/v1/positions/accounts/meandGET /api/v1/orders/accounts/me.
Tag orders with a unique comment (e.g. "bot1-7f3a"): it's copied onto the
order and position, so you can match events to the request that caused them.
The same pattern applies to modifying and closing positions.
Place an order
POST /api/v1/orders/accounts/me
| Field | Type | Required | Notes |
|---|---|---|---|
symbol_id | integer | yes | From /symbols/me. |
type | integer | yes | See order types. |
side | integer | market orders | 0 buy, 1 sell. For pending orders the side comes from type. |
volume | number | yes | Lots. Must be within the symbol's min_value–max_value; round to step yourself. |
order_price | number | yes | Never 0. Market: the price you expect (current ask to buy, bid to sell). Pending: the trigger price. |
stop_loss | number | null | no | Absolute price. null or omitted = none. |
take_profit | number | null | no | Absolute price. null or omitted = none. |
comment | string | no | Copied to the order and position. Use it to tag your bot's trades. |
expiration_policy | integer | no | Pending orders only. See expiration. Default 0 (GTC). |
expiry_at | integer | with policy 2 or 3 | Unix time in milliseconds. |
{
"symbol_id": 26100001,
"type": 0,
"side": 0,
"volume": 0.10,
"order_price": 1.08234,
"stop_loss": 1.08034,
"take_profit": 1.08634,
"comment": "bot1-7f3a"
}
A market order fills at the current price (ask for a buy, bid for a sell) with
your account's spread. order_price is recorded but there is no slippage
limit — if the price has moved, you get the new price. Check the fill in the
position_create event's open_price.
Order types
type | Name | Triggers when |
|---|---|---|
0 | Market | Fills immediately. Needs side. |
1 | Buy Limit | Ask falls to order_price (below the market). |
2 | Buy Stop | Ask rises to order_price (above the market). |
3 | Sell Limit | Bid rises to order_price (above the market). |
4 | Sell Stop | Bid falls to order_price (below the market). |
Types 5 and 6 (stop-limit) are accepted by the API but not executed by the
engine — don't use them.
Pending orders reserve no margin until they fill. Placing one on the wrong
side of the market is rejected, e.g. buy limit price 1.09 must be less than or equal 1.08234.
Expiration
expiration_policy | Meaning |
|---|---|
0 | Good till cancelled (default). |
1 | Until the end of today's session. |
2 | Until expiry_at (at least 5 minutes from now). |
3 | Until the end of the day given by expiry_at. |
The symbol's expiration field lists which policies are allowed.
Stop loss & take profit rules
SL and TP are absolute prices. They must be at least the symbol's stops level away from the reference price:
- distance =
stop_level × 10^-digits(forstop_level: 20and 5 digits, 0.00020). - reference = the price you'd close at: the bid for a buy, the ask for a sell. For a pending order, the order's own price.
| Side | Take profit | Stop loss |
|---|---|---|
| Buy | ≥ reference + distance | ≤ reference − distance |
| Sell | ≤ reference − distance | ≥ reference + distance |
A violation is rejected with a message like
stop loss price 1.0820 must be less than or equal 1.08014. Round every price
to the symbol's digits.
Modify a pending order
PUT /api/v1/orders/{order_id}/accounts/me
{
"type": 1,
"order_limit_price": 1.07400,
"volume": 0.10,
"stop_loss": 1.06900,
"take_profit": 1.08900,
"expiration_policy": 0
}
This replaces the order. Any field you leave out is cleared — omit
stop_loss and the order loses its stop. Read the order first and send it back
with your changes. type is required and must be 1–4.
Only orders with status 1 (placed) can be changed; others return
you can't update the order at this point.
Cancel a pending order
POST /api/v1/orders/{order_id}/accounts/me with no body. You get order_cancel
on the WebSocket.
Modify a position (SL/TP)
PUT /api/v1/positions/{position_id}/accounts/me
{ "stop_loss": 1.08100, "take_profit": 1.08700, "comment": "bot1-7f3a" }
This also replaces: to move only the stop, send the current take_profit
too, or the take profit is removed. Send null to remove one on purpose.
The SL/TP rules above apply; a violation arrives as a WebSocket bad_request
with reason: "position_update".
Close a position
POST /api/v1/positions/{position_id}/accounts/me
{ "volume": 0.10 }
volumeis required. Send the full position volume to close it, or less for a partial close — the rest stays open under the sameposition_id.- The close fills at the current market price.
- You get
position_closeand adeal_create(exit, withprofit).
Close several at once with POST /api/v1/positions/close-many/accounts/me:
{ "positions": [ { "position_id": 260610016, "volume": 0 }, { "position_id": 260610017, "volume": 0.05 } ] }
Here volume: 0 means "close fully". The response reports each one:
{ "closed": 1, "failed": 1, "results": [ { "position_id": 260610017, "ok": false, "error": "position not found" } ] }.
Reference: enums
Order status
| Value | Meaning |
|---|---|
0 | started — being processed |
1 | placed — pending, waiting to trigger |
2 | partially filled |
3 | filled |
4 | cancelled |
5 | rejected (see rejection_msg) |
6 | expired |
GET /api/v1/orders/accounts/me returns statuses 0–2 by default; add
?active=false for 3–6.
Position status: 0 open, 1 closing, 2 closed. GET /api/v1/positions/accounts/me returns every open position; add
?active=false for closed ones (paged).
side: 0 buy, 1 sell.
Deal direction: 0 entry, 1 exit. Deal status: 0 active,
1 closing, 2 closed, 3 cancelled.
Deal / position close reason:
| Value | Closed by |
|---|---|
0 | you (client) |
2 | the dealer |
3 | stop loss |
4 | take profit |
6 | stop-out (margin) |
Lists, paging and dates
Order, closed-position, deal and journal lists share these query parameters:
| Parameter | Default | Notes |
|---|---|---|
page | 1 | 1-based. |
limit | 500 | Up to 100000. |
from | — | RFC 3339, e.g. 2026-06-01T00:00:00Z. Filters on creation time. |
to | — | RFC 3339. Only applied together with from. |
data is a plain array — keep paging until you get fewer than limit rows.
Timestamps such as created_at are Unix nanoseconds.