NAV
V3
V5
中文

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:

  1. Rejected at entry, sCode is not 0, no updates from websocket orders channel
  2. Placed an order and immediately fully filled: live -> filled
  3. Placed an order and immediately filled by multiple trades: live -> partially_filled -> ... -> filled
  4. Placed an order but immediately canceled by matching engine (such as IOC, FOK, post only): live -> canceled (cancel reason can be referred from cancelSource)
  5. 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: 

"op": "amend-order"

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: 

"op": "cancel-order"

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.

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:

GET /api/v5/account/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.