Order Entry Protocol
The Order Entry Protocol is the message-based interface a client uses to manage orders. It carries place, amend, replace, and cancel requests in one direction, and order updates, fills, position changes, wallet balances, and rate-limit reports back the other.
The shape is consistent: a request specifies what to do; the corresponding response confirms what happened (or what failed); separate event messages report state changes that arise asynchronously.
Connecting to a trading adapter
Each account is served by exactly one trading adapter. Resolve which one through the backend, then query the adapter for its endpoints.
Resolve the adapter for an account
GET http://<backend-host>/api/accounts/{accountId}/tradingservers
The response always carries a single entry: the adapter's host and port.
{
"status": 200,
"result": "success",
"count": 1,
"data": [
{
"server_id": 1,
"exchange_id": 42,
"host": "10.0.0.42",
"port": 3000,
"ip": "10.0.0.42"
}
]
}
Query the adapter's endpoints
GET http://{host}:{port}/api/info
{
"process_id": "ta-binance",
"version": "1.0.0",
"endpoints": [
{
"endpoint": "/api/v2",
"protocol_version": "2"
}
],
"domain_socket": {
"path": "/tmp/ta-binance.socket",
"protocol_version": "2"
}
}
domain_socket is only present when the request comes from localhost
and the adapter has domain sockets enabled.
Connect
This page describes protocol version 2. Connect over the matching endpoint:
-
WebSocket:
ws://{host}:{port}{endpoint}(for examplews://10.0.0.42:3000/api/v2). -
Domain socket: the
pathfromdomain_socket(for example/tmp/ta-binance.socket).
Welcome
The trading adapter sends a welcome message immediately after the
connection is established. It carries the version of the adapter and the
version of the protocol it is serving. Read the protocolVersion here
and verify it before going further; the rest of this page assumes a
compatible version.
{
"msgType": "welcome",
"processId": string,
"version": string,
"protocolVersion": string
}
{
"msgType": "welcome",
"processId": "trading-adapter-1",
"version": "4.32.0",
"protocolVersion": "2.1"
}
Note
Welcome is the first message after a connection is established.
Verify protocolVersion for compatibility before sending login.
Login
Establishes a session for an account and returns the current account state.
Request
{
"msgType": "login",
"accountId": integer,
"requestId": string,
"processId": string,
"version": string,
"options": {
"includeContext": boolean,
"filterByProcessId": boolean,
"enableCorporateActions": boolean,
"useLegacyPositionEncoding": boolean
}
}
Login options:
{
"msgType": "login",
"accountId": 42,
"requestId": "login-req-001",
"processId": "strategy-server-1",
"version": "1.0.0",
"options": {
"includeContext": false,
"filterByProcessId": false
}
}
Response
{
"msgType": "loginResponse",
"accountId": integer,
"requestId": string,
"result": {
"status": string,
"orders": array,
"instrumentPositions": array,
"isolatedInstrumentPositions": array,
"walletPositions": array,
"maintenanceMargin": array,
"totalBalances": array,
"rateLimitLoads": array,
"capabilities": object
},
"error": object
}
Result object:
Error object:
{
"msgType": "loginResponse",
"accountId": 42,
"requestId": "login-req-001",
"result": {
"status": "OK",
"orders": [],
"instrumentPositions": [],
"walletPositions": [
{
"underlyingId": 1,
"availableAmount": "10000.00",
"amountBlockedForMargin": "0.00",
"marginBalance": "10000.00",
"walletType": "ACCOUNT"
}
],
"maintenanceMargin": [],
"totalBalances": [],
"rateLimitLoads": [
{
"id": "spot",
"p": 0.0,
"a": 0.0,
"r": 0.0,
"c": 0.0
}
],
"capabilities": {
"amend": true,
"replace": false,
"cancelAll": true,
"closeOnly": true,
"reduceOnly": true,
"displayQuantity": true,
"orderTypes": [
"LIMIT",
"MARKET",
"STOP_LIMIT"
],
"timeInForces": [
"GOOD_TILL_CANCEL",
"IMMEDIATE_OR_CANCEL"
],
"walletTypes": [
"ACCOUNT"
]
}
}
}
Note
The login response carries the full account state: every open order, every position, every wallet balance, and the exchange capabilities. A reconnecting client uses this snapshot to sync its own state without any extra round trips.
Logout
Ends the session for the given account. The server does not send a response.
{
"msgType": "logout",
"accountId": integer,
"requestId": string
}
{
"msgType": "logout",
"accountId": 42,
"requestId": "logout-req-001"
}
Place
Submits one or more new orders to the exchange. Every order in the
request must target the same instrumentId.
Request
{
"msgType": "place",
"accountId": integer,
"entries": [
{
"ownOrderId": string,
"clientOrderId": string,
"instrumentId": integer,
"type": string,
"side": string,
"timeInForce": string,
"price": string,
"totalQuantity": string,
"postOnly": boolean,
"displayQuantity": string,
"triggerPrice": string,
"pegOffsetValue": string,
"openClose": string,
"walletType": string,
"closeOnly": boolean,
"reduceOnly": boolean,
"autoBorrow": boolean,
"autoRepay": boolean,
"useSpare": boolean,
"owner": object,
"context": object
}
],
"tracing": object
}
Entry object:
{
"msgType": "place",
"accountId": 42,
"entries": [
{
"ownOrderId": "123e4567-e89b-12d3-a456-426614174000",
"clientOrderId": "123e4567-e89b-12d3-a456-426614174000",
"instrumentId": 123,
"type": "LIMIT",
"side": "BID",
"timeInForce": "GOOD_TILL_CANCEL",
"price": "50000.00",
"totalQuantity": "0.01",
"postOnly": true
}
]
}
Response
{
"msgType": "placeResponse",
"accountId": integer,
"adapterTimestampNs": integer,
"results": [
{
"ownOrderId": string,
"clientOrderId": string,
"exchangeTimestampNs": integer,
"result": object,
"error": object
}
]
}
Result entry (success):
Result entry (failure):
{
"msgType": "placeResponse",
"accountId": 42,
"adapterTimestampNs": 1677681492196000000,
"results": [
{
"ownOrderId": "123e4567-e89b-12d3-a456-426614174000",
"clientOrderId": "123e4567-e89b-12d3-a456-426614174000",
"exchangeTimestampNs": 1677681492190000000,
"result": {
"exchangeOrderId": "EXCH-12345",
"instrumentId": 123,
"type": "LIMIT",
"side": "BID",
"timeInForce": "GOOD_TILL_CANCEL",
"price": "50000.00",
"totalQuantity": "0.01",
"postOnly": true
}
}
]
}
Amend
Modifies existing orders atomically. Only available where the
exchange supports it; otherwise use
Request
{
"msgType": "amend",
"accountId": integer,
"entries": [
{
"ownOrderId": string,
"currentClientOrderId": string,
"newClientOrderId": string,
"exchangeOrderId": string,
"price": string,
"totalQuantity": string,
"triggerPrice": string,
"pegOffsetValue": string,
"context": object
}
],
"tracing": object
}
Entry object:
{
"msgType": "amend",
"accountId": 42,
"entries": [
{
"ownOrderId": "123e4567-e89b-12d3-a456-426614174000",
"currentClientOrderId": "123e4567-e89b-12d3-a456-426614174000",
"newClientOrderId": "123e4567-e89b-12d3-a456-426614174999",
"price": "51000.00"
}
]
}
Response
{
"msgType": "amendResponse",
"accountId": integer,
"adapterTimestampNs": integer,
"results": [
{
"ownOrderId": string,
"clientOrderId": string,
"exchangeTimestampNs": integer,
"result": object,
"error": object
}
]
}
Result entry:
{
"msgType": "amendResponse",
"accountId": 42,
"adapterTimestampNs": 1677681492196000000,
"results": [
{
"ownOrderId": "123e4567-e89b-12d3-a456-426614174000",
"clientOrderId": "123e4567-e89b-12d3-a456-426614174999",
"exchangeTimestampNs": 1677681492190000000,
"result": {
"price": "51000.00"
}
}
]
}
Replace
Replaces an order by cancelling the existing one and placing a new one. Use this where the exchange does not support amend; the trade-off is that this is a non-atomic modify.
Replace is not a universal fallback: it needs exchange support of its own.
Check the replace capability in the login response before relying on it,
the same way you would check amend. An exchange can support neither, in
which case cancel and place the order yourself.
Request
{
"msgType": "replace",
"accountId": integer,
"entries": [
{
"currentOwnOrderId": string,
"newOwnOrderId": string,
"currentClientOrderId": string,
"newClientOrderId": string,
"exchangeOrderId": string,
"price": string,
"totalQuantity": string,
"triggerPrice": string,
"context": object
}
],
"tracing": object
}
Entry object:
{
"msgType": "replace",
"accountId": 42,
"entries": [
{
"currentOwnOrderId": "123e4567-e89b-12d3-a456-426614174000",
"newOwnOrderId": "123e4567-e89b-12d3-a456-426614174999",
"currentClientOrderId": "123e4567-e89b-12d3-a456-426614174000",
"newClientOrderId": "123e4567-e89b-12d3-a456-426614174999",
"price": "51000.00",
"totalQuantity": "0.02"
}
]
}
Response
{
"msgType": "replaceResponse",
"accountId": integer,
"adapterTimestampNs": integer,
"results": [
{
"exchangeOrderId": string,
"ownOrderId": string,
"clientOrderId": string,
"exchangeTimestampNs": integer,
"result": object,
"error": object
}
]
}
{
"msgType": "replaceResponse",
"accountId": 42,
"adapterTimestampNs": 1677681492196000000,
"results": [
{
"exchangeOrderId": "EXCH-12346",
"ownOrderId": "123e4567-e89b-12d3-a456-426614174999",
"clientOrderId": "123e4567-e89b-12d3-a456-426614174999",
"exchangeTimestampNs": 1677681492190000000,
"result": {
"price": "51000.00",
"totalQuantity": "0.02"
}
}
]
}
Warning
Replace is non-atomic. The cancel can succeed while the place fails, leaving the order canceled with no replacement on the book. Where amend is available, prefer it; fall back to replace only when the exchange does not expose amend.
Cancel
Cancels one or more existing orders.
Request
{
"msgType": "cancel",
"accountId": integer,
"entries": [
{
"ownOrderId": string,
"exchangeOrderId": string
}
],
"tracing": object
}
Entry object:
{
"msgType": "cancel",
"accountId": 42,
"entries": [
{
"ownOrderId": "123e4567-e89b-12d3-a456-426614174000",
"exchangeOrderId": "EXCH-12345"
}
]
}
Response
{
"msgType": "cancelResponse",
"accountId": integer,
"adapterTimestampNs": integer,
"results": [
{
"ownOrderId": string,
"exchangeTimestampNs": integer,
"result": object,
"error": object
}
]
}
{
"msgType": "cancelResponse",
"accountId": 42,
"adapterTimestampNs": 1677681492196000000,
"results": [
{
"ownOrderId": "123e4567-e89b-12d3-a456-426614174000",
"exchangeTimestampNs": 1677681492190000000,
"result": {}
}
]
}
Cancel All
Cancels every open order on the account, where the exchange supports this operation.
Request
{
"msgType": "cancelAll",
"accountId": integer,
"requestId": string,
"createTimestampNs": integer,
"tracing": object
}
{
"msgType": "cancelAll",
"accountId": 42,
"requestId": "e84ebdd5-1c22-43df-a85d-f28bb9d38e34",
"createTimestampNs": 1677681492196000000
}
Response
{
"msgType": "cancelAllResponse",
"accountId": integer,
"requestId": string,
"adapterTimestampNs": integer,
"exchangeTimestampNs": integer,
"result": object,
"error": object
}
{
"msgType": "cancelAllResponse",
"accountId": 42,
"requestId": "e84ebdd5-1c22-43df-a85d-f28bb9d38e34",
"adapterTimestampNs": 1677681492196000000,
"exchangeTimestampNs": 1677681492190000000,
"result": {}
}
Order Context
Updates the context of one or more existing orders. The context is
returned on subsequent order updates and fills, so this is the way to
attach (or change) application-side metadata after an order has already
been placed.
Request
{
"msgType": "orderContext",
"accountId": integer,
"entries": [
{
"ownOrderId": string,
"clientOrderId": string,
"context": object
}
]
}
Entry:
Response
{
"msgType": "orderContextResponse",
"accountId": integer,
"results": [
{
"ownOrderId": string,
"clientOrderId": string,
"result": { "context": object },
"error": object
}
]
}
Each result entry carries exactly one of result (success) or error
(failure, with type from
OrderErrorType and source from
ErrorSource).
Fill Context
Updates the context of a fill. Same shape as Order Context, keyed by
ownFillId.
Request
{
"msgType": "fillContext",
"accountId": integer,
"entries": [
{
"ownFillId": string,
"context": object
}
]
}
Response
{
"msgType": "fillContextResponse",
"accountId": integer,
"results": [
{
"ownFillId": string,
"result": { "context": object },
"error": object
}
]
}
Per-entry errors carry type from
FillErrorType.
Account Status
Sent when the connection to the exchange is lost or restored, or when the account status changes for any other reason.
{
"msgType": "accountStatus",
"accountId": integer,
"message": string,
"status": string,
"orders": array,
"instrumentPositions": array,
"isolatedInstrumentPositions": array,
"walletPositions": array,
"maintenanceMargin": array,
"totalBalances": array,
"rateLimitLoads": array
}
{
"msgType": "accountStatus",
"accountId": 42,
"message": "Connected to exchange",
"status": "OK",
"orders": [],
"instrumentPositions": [],
"walletPositions": [],
"maintenanceMargin": [],
"totalBalances": [],
"rateLimitLoads": []
}
Warning
When status changes to DISCONNECTED, expect order requests to fail
until the connection is restored and the status returns to OK.
Order Update
Sent whenever an order is placed, modified, cancelled, filled, or otherwise changed.
{
"msgType": "orderUpdate",
"accountId": integer,
"adapterTimestampNs": integer,
"data": [
{
"ownOrderId": string,
"exchangeOrderId": string,
"clientOrderId": string,
"exchangeTimestampNs": integer,
"instrumentId": integer,
"type": string,
"side": string,
"state": string,
"timeInForce": string,
"price": string,
"totalQuantity": string,
"executedQuantity": string,
"postOnly": boolean,
"closeOnly": boolean,
"reduceOnly": boolean,
"displayQuantity": string,
"triggered": boolean,
"triggerPrice": string,
"pegOffsetValue": string,
"openClose": string,
"walletType": string,
"owner": object,
"context": object
}
]
}
Order data:
{
"msgType": "orderUpdate",
"accountId": 42,
"adapterTimestampNs": 1677681492196000000,
"data": [
{
"ownOrderId": "123e4567-e89b-12d3-a456-426614174000",
"exchangeOrderId": "EXCH-12345",
"clientOrderId": "123e4567-e89b-12d3-a456-426614174000",
"exchangeTimestampNs": 1677681492190000000,
"instrumentId": 123,
"type": "LIMIT",
"side": "BID",
"state": "CONFIRMED",
"timeInForce": "GOOD_TILL_CANCEL",
"price": "50000.00",
"totalQuantity": "0.01",
"executedQuantity": "0.00",
"postOnly": true
}
]
}
Fill
Sent whenever an order receives a fill, or when the data of an already-published fill is updated.
{
"msgType": "fill",
"accountId": integer,
"adapterTimestampNs": integer,
"data": [
{
"ownFillId": string,
"exchangeTradeId": string,
"exchangeTimestampNs": integer,
"ownOrderId": string,
"exchangeOrderId": string,
"clientOrderId": string,
"instrumentId": integer,
"side": string,
"price": string,
"quantity": string,
"fees": string,
"feeUnderlyingId": integer,
"addedLiquidity": boolean,
"usdFees": string,
"usdTurnover": string,
"fxRate": string,
"openClose": string,
"type": string,
"update": boolean,
"context": object,
"owner": object
}
]
}
Fill data:
{
"msgType": "fill",
"accountId": 42,
"adapterTimestampNs": 1677681492196000000,
"data": [
{
"ownFillId": "fill-001",
"exchangeTradeId": "TRADE-12345",
"exchangeTimestampNs": 1677681492190000000,
"ownOrderId": "123e4567-e89b-12d3-a456-426614174000",
"exchangeOrderId": "EXCH-12345",
"clientOrderId": "123e4567-e89b-12d3-a456-426614174000",
"instrumentId": 123,
"side": "BID",
"price": "50000.00",
"quantity": "0.01",
"fees": "0.50",
"feeUnderlyingId": 1,
"addedLiquidity": true,
"update": false
}
]
}
Note
addedLiquidity reports maker (true) or taker (false) on the
trade. Most exchanges price fees differently for the two.
Tip
When update is true, the message contains updated information for
a fill the client already received. This typically happens when fee
information arrives a moment after the initial fill report.
Position Update
Sent whenever any position changes.
The wire shape depends on the login option useLegacyPositionEncoding.
The default is true (legacy format) for backwards compatibility; new
integrations should set it to false and use the new format.
In dual/hedge mode, long and short positions coexist on the same instrument without netting; this is what the docs call isolated positions. Non-isolated positions net into a single long/short pair.
useLegacyPositionEncoding: false)
{
"msgType": "positionUpdate",
"accountId": integer,
"adapterTimestampNs": integer,
"positions": [
{
"instrumentId": integer,
"exchangeTimestampNs": integer,
"shortPosition": string,
"longPosition": string,
"liquidationPrice": string,
"unrealizedPnl": string,
"averageEntryPrice": string
}
],
"isolatedPositions": [
{
"instrumentId": integer,
"exchangeTimestampNs": integer,
"long": {
"size": string,
"liquidationPrice": string,
"averageEntryPrice": string,
"unrealizedPnl": string
},
"short": {
"size": string,
"liquidationPrice": string,
"averageEntryPrice": string,
"unrealizedPnl": string
}
}
]
}
positions and isolatedPositions are always present; either array can
be empty. Inside isolatedPositions, the long and short sub-objects
are always both present (with size 0 if there is no exposure on that
side).
useLegacyPositionEncoding: true, default)
{
"msgType": "positionUpdate",
"accountId": integer,
"adapterTimestampNs": integer,
"data": [
{
"instrumentId": integer,
"exchangeTimestampNs": integer,
"shortPosition": string,
"longPosition": string,
"liquidationPrice": string,
"unrealizedPnl": string,
"averageEntryPrice": string
}
]
}
data is renamed to positions in the new format and isolated
positions can no longer be carried alongside regular positions in the
same update.
Position entry (positions[] and legacy data[]):
Isolated position entry (isolatedPositions[], new format only):
Note
For instruments with non-isolated positions, only one of
shortPosition or longPosition is greater than zero. That value is
the net position.
Wallet Update
Sent whenever any wallet balance changes.
{
"msgType": "walletUpdate",
"accountId": integer,
"adapterTimestampNs": integer,
"maintenanceMargin": array,
"totalBalances": array,
"data": [
{
"type": string,
"instrumentId": integer,
"underlyingId": integer,
"exchangeTimestampNs": integer,
"availableAmount": string,
"amountBlockedForMargin": string,
"marginBalance": string,
"maintenanceMargin": string,
"borrowed": string,
"cashBalance": string,
"unrealizedPnl": string,
"availableBorrow": string
}
]
}
Wallet entry:
{
"msgType": "walletUpdate",
"accountId": 42,
"adapterTimestampNs": 1677681492196000000,
"maintenanceMargin": [],
"totalBalances": [],
"data": [
{
"underlyingId": 1,
"exchangeTimestampNs": 1677681492190000000,
"availableAmount": "9500.00",
"amountBlockedForMargin": "500.00",
"marginBalance": "10000.00"
}
]
}
Rate Limit Load Update
Sent whenever any rate-limit load changes.
{
"msgType": "rateLimitLoad",
"accountId": integer,
"adapterTimestampNs": integer,
"rateLimitLoads": [
{
"id": string,
"p": number,
"a": number,
"r": number,
"c": number
}
]
}
Rate limit load:
{
"msgType": "rateLimitLoad",
"accountId": 42,
"adapterTimestampNs": 1677681492196000000,
"rateLimitLoads": [
{
"id": "spot",
"p": 0.25,
"a": 0.10,
"r": 0.05,
"c": 0.15
}
]
}
Load values range from 0.0 (no usage) to 1.0 (limit fully used).
See Concepts for the exchange-specific
rate-limit keys.
Corporate Action
Published only when the enableCorporateActions login option is set.
Notifies the strategy of stock splits, dividends, and similar events.
{
"msgType": "corporateAction",
"accountId": integer,
"adapterTimestampNs": integer,
"instrumentId": integer,
"split": {
"lastPrice": string,
"buyVolume": string,
"sellVolume": string,
"liquidateFractionalShares": boolean
},
"dividend": {
"underlyingId": integer,
"highPrice": string,
"lowPrice": string
},
"comment": string
}
split.liquidateFractionalShares is true when fractional shares from
the split are liquidated, false when they are rounded.
Invalid Request
Sent when the server cannot parse or route a request. Carries the offending payload so the client can correlate the failure with what it sent.
{
"msgType": "invalidRequest",
"message": string,
"request": string
}
Rate Limit Errors
When an internal rate limit is exceeded, the response carries retry
timing information. The same shape applies whether the error type is
RATE_LIMIT_EXCEEDED (the limit itself was hit) or
RATE_LIMIT_BLOCKED_AFTER_EXCEEDED (the adapter is in a follow-on
block after a prior exceeded limit).
Error structure:
{
"error": {
"type": "RATE_LIMIT_EXCEEDED",
"message": "rate limit exceeded",
"source": "INTERNAL",
"retryTimeNs": 1677681492196000000
}
}
retryTimeNs is a nanosecond-since-epoch timestamp giving the earliest
moment at which there should be enough rate-limit budget for a retry.
The retryTime capability flag tells you whether the adapter populates
this field.
Note
After an internal rate-limit violation, wait until retryTimeNs
before retrying. Earlier retries waste both rate-limit budget and CPU.
Warning
retryTimeNs is not a reservation. It only tells you when the budget
is expected to free up. If another operation has consumed the
budget by the time you retry, the retry will still fail.