Instrument configuration
Users can get the exchange instruments configuration from GET /api/v5/public/instruments.
Subsequent instrument updates, such as tick size changes and new listings will be published from the websocket instruments channel.
Market data
Users can receive real time market data updates from websocket channels.
bbo-tbt and books5 are depth snapshots that are published every 10ms and 100ms. New snapshots are not sent when there is no change in the orderbook.
books, books-l2-tbt, and books50-l2-tbt are incremental order book channels. books publishs the changes in the order book every 100ms. books-l2-tbt and books50-l2-tbt push changes every 10ms. In order to use books-l2-tbt and books50-l2-tbt, users must login before subscribing and are limited to VIP levels 5 and 4, respectively.
Order book data is created once every 10ms internally and relevant data is sent out depending on the subscribed channel. Users receive the same order book image from all websocket connections and channels.
No update is sent if the depth changes from A -> B -> A during the interval. If there are no updates to the depth for an extended period, the system resends the current depth for snapshot channels, a message with no depth updates for incremental channels, to inform users that the connection is still active.
Order management
Trade mode
For spot orders, users must set trade mode (tdMode) to cash.
The following table shows tdMode values that must be set:
| Account Mode | Instrument Type | Trade Mode (tdMode) |
|---|---|---|
| Spot mode | Spot | cash |
Subscribing to the orders channel
Before placing an order, users must subscribe to the orders channel with WebSocket to monitor the order state changes (e.g. live, filled) and take action if necessary (e.g. place a new order after execution).
There are several subscription granularities when subscribing to the orders channel.
To subscribe to the above-mentioned BTC-JPY order updates, users can send any of the following requests after connecting and logging in to the private WebSocket:
| Instrument Type | Instrument Type + Instrument ID | |
| Request | { "op": "subscribe", "args": [ { "channel": "orders", "instType": "SPOT" } ] } |
{ "op": "subscribe", "args": [ { "channel": "orders", "instType": "SPOT", "instId": "BTC-JPY" } ] } |
| Successful response | { "event": "subscribe", "arg": { "channel": "orders", "instType": "SPOT" } } |
{ "event": "subscribe", "args": [ { "channel": "orders", "instType": "SPOT", "instId": "BTC-JPY" } ] } |
Note that the orders channel does not publish any initial snapshot of user orders before the subscription. It only publishes whenever the order state changes (e.g. from live to canceled).
If users want to obtain all the live order details before subscribing, users can invoke the following API:
GET /api/v5/trade/orders-pending
Placing an order
To better identify the order in the system, it is recommended to provide a Client Order ID as assigned by the client (clOrdId) when placing the order. The Client Order ID as assigned by the client should be case-sensitive, and have a maximum of 32 alphanumeric characters.
clOrdId uniqueness check is only applied towards all pending orders. It is recommended to use a unique clOrdId at all times for troubleshooting etc.
In the following example, clOrdId is assigned as testBTC0123.
After subscribing to the orders channel, users can place the BTC-JPY order.
Users can use REST or WebSocket to place orders.
REST API
Users can invoke the following REST API and the server will acknowledge the request with an order ID (ordId):
| REST API | POST /api/v5/trade/order |
| Request body | { "instId": "BTC-JPY", "tdMode": "cash", "clOrdId": "testBTC0123", "side": "buy", "ordType": "limit", "px": "50912.4", "sz": "1" } |
| Successful response | { "code": "0", "msg": "", "data": [ { "clOrdId": "testBTC0123", "ordId": "288981657420439575", "tag": "", "sCode": "0", "sMsg": "" } ] } |
Note that this only indicates that the exchange has received the request successfully with an order ID assigned. The order may not entered matching engine at this point in time. Users should check the order state.
WebSocket
Users can place the order via WebSocket, which is, in theory, more efficient than using REST API with less overhead.
Since WebSocket operation is asynchronous, users will also need to provide the message ID (id) to identify the corresponding response.
After logging into the private WebSocket, users can send the following WebSocket message:
| { "id": "NEWtestBTC0123", "op": "order", "args": [ { "instId": "BTC-JPY", "tdMode": "cash", "clOrdId": "testBTC0123", "side": "buy", "ordType": "limit", "px": "50912.4", "sz": "1" } ] } |
The server will acknowledge the request with the following sample response with the same message ID (i.e.NEWtestBTC0123), along with an order ID (ordId) assigned by the exchange:
| { "id": "NEWtestBTC0123", "op": "order", "data": [ { "clOrdId": "", "ordId": "288981657420439575", "tag": "", "sCode": "0", "sMsg": "" } ], "code": "0", "msg": "" } |
Note that this only indicates that the exchange has received the request successfully with an order ID assigned. The order may not entered matching engine at this point in time. Users should check the order state.
Checking order state
After placing the order, if the order does not return any error ("sCode": "0"), okj always sends out "state": "live" from websocket.
Sample message (subscribed to orders channel by instrument type + instrument ID):
| { "arg": { "channel": "orders", "instType": "SPOT", "instId": "BTC-JPY", "uid": "614488474791936" }, "data": [ { "accFillSz": "0", "algoClOrdId": "", "algoId": "", "amendResult": "", "amendSource": "", "avgPx": "", "cancelSource": "", "category": "normal", "clOrdId": "testBTC0123", "code": "0", "cTime": "1615170596148", "execType": "", "fee": "0", "feeCcy": "BTC", "fillFee": "0", "fillFeeCcy": "BTC", "fillPx": "", "fillSz": "0", "fillTime": "", "instId": "BTC-JPY", "instType": "SPOT", "msg": "", "ordId": "288981657420439575", "ordType": "limit", "px": "50912.4", "rebate": "0", "rebateCcy": "JPY", "reqId": "", "side": "buy", "attachAlgoClOrdId": "", "slOrdPx": "", "slTriggerPx": "", "slTriggerPxType": "last", "source": "", "state": "live", "sz": "1", "tag": "", "tdMode": "cash", "tgtCcy": "", "tpOrdPx": "", "tpTriggerPx": "", "tpTriggerPxType": "last", "attachAlgoOrds": [], "tradeId": "", "lastPx": "", "uTime": "1615170596148", "isTpLimit": "false", "linkedAlgoOrd": {"algoId": ""} } ] } |
After the order is filled, the following sample message is pushed with the state changed to filled, along with other fill-related fields.
If the order is partially or fully filled, websocket returns state = partially_filled and filled respectively.
For immediate or cancel, fill or kill, and post only orders where orders may be rejected by the matching engine, users will see a live then canceled state.
User orders can be canceled by the system for various reasons, such as liquidation or self-trade prevention. Users can refer to cancelSource to see the reason for order cancellation.
The terminal state of an order is either canceled or filled.
A Trade ID (tradeId) will also be set for this fill and can be used for reconciliation with position as shown below.
| { "arg": { "channel": "orders", "instType": "SPOT", "instId": "BTC-JPY", "uid": "614488474791936" }, "data": [ { "accFillSz": "1", "algoClOrdId": "", "algoId": "", "amendResult": "", "amendSource": "", "avgPx": "50912.4", "cancelSource": "", "category": "normal", "clOrdId": "testBTC0123", "code": "0", "cTime": "1615170596148", "execType": "M", "fee": "-0.001", "feeCcy": "BTC", "fillFee": "-0.001", "fillFeeCcy": "BTC", "fillPx": "50912.4", "fillSz": "1", "fillTime": "1615170598021", "instId": "BTC-JPY", "instType": "SPOT", "msg": "", "ordId": "288981657420439575", "ordType": "limit", "px": "50912.4", "rebate": "0", "rebateCcy": "JPY", "reqId": "", "side": "buy", "attachAlgoClOrdId": "", "slOrdPx": "", "slTriggerPx": "", "slTriggerPxType": "last", "source": "", "state": "filled", "sz": "1", "tag": "", "tdMode": "cash", "tgtCcy": "", "tpOrdPx": "", "tpTriggerPx": "", "tpTriggerPxType": "last", "attachAlgoOrds": [], "tradeId": "60477021", "lastPx": "50912.4", "uTime": "1615170598021", "isTpLimit": "false", "linkedAlgoOrd": {"algoId": ""} } ] } |
Possible order states:
- Rejected at entry,
sCodeis not 0, no updates from websocket orders channel - Placed an order and immediately fully filled:
live->filled - Placed an order and immediately filled by multiple trades:
live->partially_filled-> ... ->filled - Placed an order but immediately canceled by matching engine (such as IOC, FOK, post only):
live->canceled(cancel reason can be referred from cancelSource) - Placed an IOC, partially filled, then canceled by system because there is not enough depth in price defined:
live->partially_filled->canceled
Amending an order
Order amendment is supported for all instrument types, allowing price amendment (newPx) and/or amount (newSz) of the order. The cancel on fail (cxlOnFail) parameter is also available for order cancellation if the amendment fails.
REST:
POST /api/v5/trade/amend-order
WebSocket operation (op) argument:
Similar to placing orders, users should expect an acknowledgement after sending the amend request through REST or WebSocket. Users should refer to amendResult from Websocket orders channel to determine the outcome of the amendment request.
Note that the order cannot be amended once it is fully filled or canceled.
Successful response only means the request has been accepted by the exchange. Users should refer to websocket orders for the amendment result.
Canceling an order
Similarly users can cancel the order using REST or WebSocket.
REST:
POST /api/v5/trade/cancel-order
WebSocket operation (op) argument:
An acknowledgement will be received after sending the cancel request. The order is only canceled when users receive the order update from the WebSocket orders channel with "state": "canceled".
Note that an order cannot be canceled when it is fully filled or is already canceled.
Successful response only means the request has been accepted by the exchange. Users should refer to websocket orders for cancellation confirmation.
Batch operations
Batch operations are available for placing, amending, and canceling orders and supports a maximum of 20 orders per request. Orders in each batch request can be of different instrument types.
REST:
| Place | POST /api/v5/trade/batch-orders |
| Amend | POST /api/v5/trade/amend-batch-orders |
| Cancel | POST /api/v5/trade/cancel-batch-orders |
WebSocket operation (op) argument:
| Place | "op": "batch-orders" |
| Amend | "op": "batch-amend-orders" |
| Cancel | "op": "batch-cancel-orders" |
The batch operation is not all-or-nothing, i.e. it allows part of the order operations to be successful. Upon receiving the acknowledgment after sending a request, users should check the individual sCode and sMsg fields for each of the orders.
Order timestamp
There are multiple timestamps within orders data for users to keep track of order states and latency measurements.
cTime is the order creation time after risk checks.
uTime is the last order updated time. It is updated after order amendment, trade fills, and order cancellation.
fillTime is the time when the order is matched. The fillTime is the same time as found in the market data trades data.
inTime is the time when the request is received at WebSocket / REST gateway. For REST, the time is recorded after authentication.
outTime is the time when the response is sent at WebSocket / REST gateway.
Pagination
OKJ offers a pagination function to facilitate users in accessing specific data from extensive datasets. The relevant request parameters are outlined below.
| Parameters | Types | Required | Description |
|---|---|---|---|
| before | String | No | Pagination of data to return records newer than the requested ordId, billId, tradeId, ts etc. |
| after | String | No | Pagination of data to return records earlier than the requested ordId, billId, tradeId, ts etc. |
| limit | String | No | Number of results per request. The maximum is 100. The default is 100. |
For better using this function, please refer to the tips below, assuming the original dataset is [10, 9, 8, 7, 6, 5, 4, 3, 2, 1].
| Tips | Examples |
|---|---|
| No matter how users are passing through request parameters, we always return the newest data to users. |
We will always return the newly created records at the beginning, e.g. [10, 9, 8, 7, ...] |
| before and after are not included. | If before=6, after=10, the data returned will be [9, 8, 7]. |
| If the record amount between before and after is above the limit, we return the records that are closer to after. |
If before=2, after=9, limit=3, the data returned will be [8, 7, 6]. |
| If only before is passed through without after, the data closed to before will be returned. |
If before=6, limit=3, the data returned will be [9, 8, 7]. This feature is not applicable for position history, it will return [10, 9, 8]. |
To get data within a specific time range, we also offer timestamp filtering if before/after is used to do the pagination of IDs.
| Parameters | Types | Required | Description |
|---|---|---|---|
| begin | String | No | Filter with a begin timestamp. Unix timestamp format in milliseconds, e.g. 1597026383085 |
| end | String | No | Filter with an end timestamp. Unix timestamp format in milliseconds, e.g. 1597026383085 |
| limit | String | No | Number of results per request. The maximum is 100. The default is 100. |
The tips differ slightly when using begin/end.
| Tips | Examples |
|---|---|
| begin and end are included. | If begin=6, end=10, the data returned will be [10, 9, 8, 7, 6]. |
| If the record amount between begin and end is above the limit, we return the records that are closer to end. If only begin is passed through without after, the data closed to begin will be returned. |
If begin=6, limit=3, the data returned will be [8, 7, 6]. This is not applicable to fills, it will return [10, 9, 8]. |
When both "begin/end" and "before/after" parameters are provided, we will first filter the timestamps based on "begin/end" criteria and then paginate the results according to "before/after".
The trading endpoints that have pagination function are listed below.
- GET / Order List
- GET / Order history (last 7 days)
- GET / Order history (last 3 months)
- GET / Transaction details (last 3 days)
- GET / Transaction details (last 3 months)
- Get bills details (last 7 days)
- Get bills details (last 3 months)
Self trade prevention
The trading platform imposes mandatory self trade prevention at master account level, which means the accounts under the same master account, including master account itself and all its affiliated sub-accounts, will be prevented from self trade. The default STP mode is Cancel Maker.
STP Modes
Cancel Maker
This is the default STP mode. To prevent self-trading, the maker order will be canceled, then the taker order will continue to match with the next order in the price-time priority.
Trading account and positions information
Account
WebSocket subscription
It is recommended to subscribe to the account channel using WebSocket for receiving account updates. The account channel provides the optional parameter ccy to specify the currency of the account.
This endpoint returns equity value in USD and other parameters which are constantly updated due to mark price changes. OKJ sends updated data to users regularly upon valuation changes.
Here is a sample request and response after connecting to and logging into the private WebSocket:
| Account | Account with Specific currency | |
| Request | { "op": "subscribe", "args": [ { "channel": "account" } ] } |
{ "op": "subscribe", "args": [ { "channel": "account", "ccy": "BTC" } ] } |
| Successful response | { "event": "subscribe", "arg": { "channel": "account" } } |
{ "event": "subscribe", "arg": { "channel": "account", "ccy": "BTC" } } |
Initial snapshot
Unlike the orders channel, the account channel will publish an initial snapshot for the currencies with a non-zero balance, i.e. non-zero equity, available equity, or available balance.
If the user has too many currencies and the data is too large to be sent in a single push message, it will be split into multiple messages.
Consider an example account with a non-zero balance on BTC and JPY. Users should expect the following sample message from the account channel:
| Account | Account with Specific Currency |
| { "arg": { "channel": "account" }, "data": [ { "adjEq": "30979.1086748182657014", "details": [ { "availBal": "", "availEq": "18962.59868274799", "ccy": "JPY", "crossLiab": "0", "disEq": "18978.5272656414983116", "eq": "18962.59868274799", "frozenBal": "0", "interest": "0", "isoEq": "0", "isoLiab": "0", "liab": "0", "mgnRatio": "", "ordFrozen": "0", "upl": "0" }, { "availBal": "", "availEq": "0", "ccy": "BTC", "crossLiab": "0.509575622217854", "disEq": "-25408.4180739947324516", "eq": "-0.5096053466363398", "frozenBal": "0", "interest": "0.0000297244184858", "isoEq": "0", "isoLiab": "0", "liab": "0.509575622217854", "mgnRatio": "", "ordFrozen": "0", "upl": "0" } ], "imr": "8469.4726913315758219", "isoEq": "0", "mgnRatio": "39.9556239578938079", "mmr": "762.252542219842", "totalEq": "44480.5383005753085878", "uTime": "1615190165641" } ] } |
{ "arg": { "channel": "account", "ccy": "BTC" }, "data": [ { "adjEq": "30979.1086748182657014", "details": [ { "availBal": "", "availEq": "0", "ccy": "BTC", "crossLiab": "0.509575622217854", "disEq": "-25408.4180739947324516", "eq": "-0.5096053466363398", "frozenBal": "0", "interest": "0.0000297244184858", "isoEq": "0", "isoLiab": "0", "liab": "0.509575622217854", "mgnRatio": "", "ordFrozen": "0", "upl": "0" } ], "imr": "8469.4726913315758219", "isoEq": "0", "mgnRatio": "39.9556239578938079", "mmr": "762.252542219842", "totalEq": "44480.5383005753085878", "uTime": "1615190165641" } ] } |
Subsequent updates
Subsequently users will receive account updates driven by the following:
| Event-driven updates | Updates driven by events such as placing and canceling orders. Multiple events (e.g. multiple orders
being executed at the same time) may be aggregated into one single account update. Only data of the affected currency will be published, including when the currency balance changes to zero. |
| Fixed time updates | Updates pushed at a regular interval (5 seconds as of writing). Similar to the initial snapshot, all currencies (or specified currencies with the ccy parameter) with non-zero balance will be pushed. |
REST API
Alternatively, users can still invoke the REST API to get the balance for currencies with non-zero balance:
Users can pass an optional parameter using ccy with a single currency (e.g. BTC) or multiple currencies (no more than 20) separated with commas (e.g. BTC,JPY,ETH). For example:
GET /api/v5/account/balance?ccy=BTC,JPY,ETH
Unlike the account channel in WebSocket, however, the currency balance, regardless of zero balance or not, will always be returned if it is specified using the ccy parameter in the REST API, as long as users have possessed that currency before.
Maximum available tradable amount
In this case, it is useful to retrieve the max available tradable amount of the instrument including the available equity and loanable amount from the exchange.
To do this, poll the following REST API in regular interval:
GET /api/v5/account/max-avail-size
See the following sample request and response for BTC-JPY with cross-margin mode under Multi-currency margin mode:
| Request | GET /api/v5/account/max-avail-size?instId=BTC-JPY&tdMode=cash |
| Successful Response | { "code": "0", "data": [ { "availBuy": "213800.4239369798722052", "availSell": "1.3539405224369181", "instId": "BTC-JPY" } ], "msg": "" } |
For Spot instruments, availBuy is in quote currency and availSell is in base currency.
The above response shows a maximum of 213,800.42 JPY is available to buy BTC-JPY, and a maximum of 1.35394052 BTC is available to sell BTC-JPY. This should be the same as the amount users see when trading on the web UI.
Maximum withdrawal amount
In order to find out the maximum withdrawable amount from the trading account or one of their sub-accounts, users can fetch the amount from GET /api/v5/account/max-withdrawal.
The data returned from this endpoint factors in the outstanding loan and margin in use.
Balance and position
Data will be pushed when triggered by events such as filled order and funding transfer.
The balance and position channel applies to getting the account cash balance and the change of position asset.
If the user has too many currencies and the data is too large to be sent in a single push message, it will be split into multiple messages.
Upon changes in account balance or position, this channel with less fields provides the best latency data, compared to the accounts channel and positions channel, in order to push the changes to customers with the lowest latency.
Identifiers
| Identifier | Description |
|---|---|
| ordId | Order ID, globally unique |
| clOrdId | Client Order ID as assigned by the client, unique across all pending orders of all symbols |
| billId | Bill ID, globally unique |
| tradeId | Last trade ID, unique per symbol. For liquidation and ADL scenarios, the tradeId will be assigned a negative value to distinguish it from other matching transaction scenarios. |
| posId | Position ID. This is uniquely generated by combining the below attributes: mgnMode, posSide, instId, and ccy. It does not change after closing and reopening the position. A new position ID may be generated for positions that have been closed for a long period of time; when switching account mode or position mode, the system will also generate a new position ID. |
System status
Users can get the exchange status from GET /api/v5/system/status.
Subsequent updates will be published from the websocket status channel.
Planned system maintenance that may result in short interruption (lasting less than 5 seconds) or websocket disconnection (users can immediately reconnect) will not be announced. The maintenance will only be performed during times of low market volatility.