
Start trading cryptocurrency on OKX
x
A technical introduction and guide to trading with OKX API v5
With the introduction of OKX’s new Unified Account trading system, we’ve been working on upgrading our API with new features and enhancements. In this document, we will outline the major changes in v5, the latest API version, and walk traders through the various configuration parameters now available via the OKX trading API.
Unlike API v3, the endpoints in API v5, such as placing orders and retrieving positions are unified across products.
For example, when placing an order, we only need to invoke the following URL and specify the product (i.e. instrument type) in the request body:
POST /api/v5/trade/order |
The same request and response data models are used across all instrument types in the same API, which means we no longer need to model each of the product APIs separately.
In API v5, camelCase is used in field names with abbreviations to save bandwidth and memory consumption.
For example:
Field | V5 API | V3 API |
Currency | ccy | currency |
Instrument ID | instId | instrument_id |
Underlying | uly | underlying |
Unrealized PnL | upl | unrealized_pnl |
Manual decompression when receiving messages in API v3 is no longer required in v5, and is replaced with the standard WebSocket extension “Per-Message Deflate.”
To leverage the WebSocket compression, make sure the extension is enabled on the client side, i.e. “permessage-deflate” should be present in the request header.
WebSocket channels are now divided into public channels (e.g. tickers, candle) and private channels (e.g. positions, account) with different URLs.
When connecting to the public WebSocket, no login request is required before subscription or the server will reject the subscription request.
In addition to REST, now it is possible to place/amend/cancel orders through WebSocket. For details please refer to the trading section referenced in this document.
REST authentication in API v5 is the same as in API v3 (i.e. sign in the request header).
WebSocket authentication is also very similar to API v3 (i.e. send login request) with format changes to key-value pairs:
{ “op”: “login”, “args”: [ { “apiKey”: “975d5d66-57ce-40fb-b714-afc0b66518083”, “passphrase”: “123456”, “timestamp”: “1538054050”, “sign”: “7L+zFQ+CEgGu5rzCj4+BdV2/uUHGqddA9pI6ztsLLPs=” } ] } |
With API v5 it is possible to CRUD (create, read, update, delete) the API Keys of sub accounts when using the master account (i.e. main account).
Create | POST /api/v5/users/subaccount/apikey |
Read | GET /api/v5/users/subaccount/apikey |
Update | POST /api/v5/users/subaccount/modify-apikey |
Delete | POST /api/v5/users/subaccount/delete-apikey |
For security reasons, it is strongly recommended to bind the API Keys to specific IP addresses.
After creating sub accounts and their API Keys, we can configure the main account and sub accounts via the API before we trade.
Account config of each account/sub account can be retrieved via the REST API:
GET /api/v5/account/config |
It will return (1) account mode, (2) position mode, (3) auto borrow setting, and (4) option Greeks type.
In OKX’s Unified Account trading system, we have (i) Simple mode, (ii) Single-currency margin mode, and (iii) Multi-currency margin mode.
We can only change these modes via the web UI as the process requires user action.
The Net position was introduced along with OKX’s Unified Account trading system and works in addition to the existing Long/Short position.
Net position | Positions can be held in one side only. Exchange will open/close the position automatically depends on the position (positive/negative) you specified |
Long/Short position | Positions can be held in both sides at the same time |
To change the position mode, we can invoke the following REST API. Note that all positions have to be closed first.
POST /api/v5/account/set-position-mode |
Auto borrow is a new feature only applicable to the Multi-currency margin mode and can only be changed via the web UI.
We can set the option Greeks type via REST API in v5 similar to v3:
POST /api/v5/account/set-greeks |
In OKX’s Unified Account trading system, we have the flexibility of trading the same instrument with both cross (single-currency and multi-currency) margin and isolated (i.e. fixed) margin at the same time.
As a result, there is no API for setting margin mode per underlying anymore. Now we specify the margin mode (i.e. trade mode) when placing an order instead.
We can retrieve the leverage information via the following REST API:
GET /api/v5/account/leverage-info |
Currently, there is no global leverage setting, and the leverage can be set in different scopes.
For margin instrument type:
For other instrument types:
After getting the leverage info, we can set the leverage accordingly:
POST /api/v5/account/set-leverage |
With these 2 APIs, we can write a program to set the leverage of each instrument beforehand.
For example, consider the following scenario:
For spot/margin instruments, since the leverage is set per coin, we can extract the currency pair and set each coin accordingly, i.e. BTC, USDT, EOS, and LTC.
Sample request body of setting BTC leverage to 3.0 (applicable to selling BTC-USDT and buying LTC-BTC):
{ “lever”: “3.0”, “mgnMode”: “cross”, “ccy”: “BTC” } |
The request bodies for setting USDT, EOS, and LTC are similar.
Next, we want to set the leverage of BTC-USD-210319, BTC-USD-210326, and BTC-USD-210625. Since they share the same underlying, i.e. BTC-USD, we only need to set the leverage once with one of the instruments.
{ “lever”: “3.0”, “mgnMode”: “cross”, “instId”: “BTC-USD-210326” } |
Finally, we want to set the leverage of BTC-USD-SWAP. Despite having the same underlying (BTC-USD) as the above futures, the leverage is separated between futures and swap.
To do so, we can invoke the API with the following request body:
{ “lever”: “3.0”, “mgnMode”: “cross”, “instId”: “BTC-USD-SWAP” } |
Now we should be all set with the above 6 API calls for the 8 instruments.
With the instructions above, users should be able to set up sub accounts with the new API, and configure accounts to suit their trading preferences.
With the flexibility of placing orders with cross and isolated margin mode, we need to specify trade mode (tdMode).
The following table shows tdMode values that need to be set:
Let’s say we want to place the following order as an example:
By looking at the trading mode table above, tdMode should be set to “cross.”
To better identify the order in the system, it is recommended to provide a client-supplied order ID (clOrdId) when placing the order. The client-supplied order ID should be case-sensitive alphanumerics/alphabets beginning with a letter and going up to 32 characters.
Let’s assign clOrdId as testBTC0123 in this example.
Before we place an order, we should subscribe to the orders channel with WebSocket, so that we can monitor the order state changes (e.g. live, filled) and take action if necessary (e.g. place a new order after execution).
In API v5, there are several subscription granularities when subscribing to orders channel.
To subscribe to the above-mentioned BTC-USDT-SWAP order updates, we can send either of the following after connecting to and logging in to the private WebSocket:
Instrument Type | Instrument Type + Underlying (Derivatives only) | Instrument Type + Instrument ID | |
Request | { “op”: “subscribe”, “args”: [ { “channel”: “orders”, “instType”: “SWAP” } ] } | { “op”: “subscribe”, “args”: [ { “channel”: “orders”, “instType”: “SWAP”, “uly”: “BTC-USDT” } ] } | { “op”: “subscribe”, “args”: [ { “channel”: “orders”, “instType”: “SWAP”, “instId”: “BTC-USDT-SWAP” } ] } |
Successful response | { “event”: “subscribe”, “arg”: { “channel”: “orders”, “instType”: “SWAP” } } | { “event”: “subscribe”, “args”: [ { “channel”: “orders”, “instType”: “SWAP”, “uly”: “BTC-USDT” } ] } | { “event”: “subscribe”, “args”: [ { “channel”: “orders”, “instType”: “SWAP”, “instId”: “BTC-USDT-SWAP” } ] } |
We can pass ANY as instType to subscribe to all product types at once as well.
Note that the orders channel does not publish any initial snapshot of your orders before the subscription. It only publishes whenever order state changes (e.g. from live to canceled).
If we want to obtain all the live orders details before subscription, we can invoke the following API:
GET /api/v5/trade/orders-pending |
After subscribing to orders channel, we should be good to place the BTC-USDT-SWAP order.
In API v5, we can use REST or WebSocket to place orders.
We 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-USDT-SWAP”, “tdMode”: “cross”, “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 be live at this point in time and we should check the order state next.
Alternatively, we can place the order via WebSocket, which is, in theory, more efficient than using REST API with less overhead.
Since WebSocket operation is asynchronous, we will also need to provide the message ID (id) to identify the corresponding response.
After logging into the private WebSocket, we can send the following WebSocket message:
{ “id”: “NEWtestBTC0123”, “op”: “order”, “args”: [ { “instId”: “BTC-USDT-SWAP”, “tdMode”: “cross”, “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”: “” } |
Please note that this only indicates that the exchange has received the request successfully with an order ID assigned. The order may not be live at this point in time and we should check the order state.
After placing the order, we should expect a WebSocket message from the orders channel with the state “live.”
Sample message (subscribed to orders channel by instrument type + underlying):
{ “arg”: { “channel”: “orders”, “instType”: “SWAP”, “uly”: “BTC-USDT” }, “data”: [ { “accFillSz”: “0”, “amendResult”: “”, “avgPx”: “”, “cTime”: “1615170596148”, “category”: “normal”, “ccy”: “”, “clOrdId”: “testBTC0123”, “code”: “0”, “fee”: “0”, “feeCcy”: “USDT”, “fillPx”: “”, “fillSz”: “0”, “fillTime”: “”, “instId”: “BTC-USDT-SWAP”, “instType”: “SWAP”, “lever”: “3”, “msg”: “”, “ordId”: “288981657420439575”, “ordType”: “limit”, “pnl”: “0”, “posSide”: “net”, “px”: “50912.4”, “rebate”: “0”, “rebateCcy”: “USDT”, “reqId”: “”, “side”: “buy”, “slOrdPx”: “”, “slTriggerPx”: “”, “state”: “live”, “sz”: “1”, “tag”: “”, “tdMode”: “cross”, “tpOrdPx”: “”, “tpTriggerPx”: “”, “tradeId”: “”, “uTime”: “1615170596148” } ] } |
After the order is filled, the following sample message is pushed with the state changed to “filled,” along with other fill-related fields.
A Trade ID (tradeId) will also be set for this fill and can be used for reconciliation with position, which will be covered below.
{ “arg”: { “channel”: “orders”, “instType”: “SWAP”, “uly”: “BTC-USDT” }, “data”: [ { “accFillSz”: “1”, “amendResult”: “”, “avgPx”: “50912.4”, “cTime”: “1615170596148”, “category”: “normal”, “ccy”: “”, “clOrdId”: “testBTC0123”, “code”: “0”, “fee”: “-0.1018248”, “feeCcy”: “USDT”, “fillPx”: “50912.4”, “fillSz”: “1”, “fillTime”: “1615170598021”, “instId”: “BTC-USDT-SWAP”, “instType”: “SWAP”, “lever”: “3”, “msg”: “”, “ordId”: “288981657420439575”, “ordType”: “limit”, “pnl”: “0”, “posSide”: “net”, “px”: “50912.4”, “rebate”: “0”, “rebateCcy”: “USDT”, “reqId”: “”, “side”: “buy”, “slOrdPx”: “”, “slTriggerPx”: “”, “state”: “filled”, “sz”: “1”, “tag”: “”, “tdMode”: “cross”, “tpOrdPx”: “”, “tpTriggerPx”: “”, “tradeId”: “60477021”, “uTime”: “1615170598022” } ] } |
Order amendment is supported for all instrument types when using API v5, allowing you to amend the price (newPx) and/or amount (newSz) of the order. The cancel on fail (cxlOnFail) parameter is also available for you to cancel the order if the amendment fails.
REST:
POST /api/v5/trade/amend-order |
WebSocket operation (op) argument:
“op”: “amend-order” |
Similar to placing orders, we should expect an acknowledgement after sending the amend request through REST or WebSocket.
Note that the order cannot be amended once it is fully filled or canceled.
Similarly we 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 we receive the order update from the WebSocket orders channel with the state “canceled”.
Note that an order cannot be canceled when it is fully filled or is already canceled.
Batch operations are available for placing, amending, and canceling orders and supports a maximum of 20 orders at a time. The advantage of having a unified API is that the orders 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, we should check the individual sCode and sMsg fields for each of the orders.
Under OKX’s Unified Account trading system, there is only one unified account shared across all instrument types. As a result there is no separate spot, margin, futures, swap or options account when trading with API v5.
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 coin of the account we are interested in for subscription.
Here is a sample request and response after connecting to and logging into the private WebSocket:
Account | Account with Specific Coin | |
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” } } |
Unlike the orders channel, the account channel will publish an initial snapshot for the coins with a non-zero balance, i.e. non-zero equity, available equity or available balance.
Let’s assume we have a non-zero balance on BTC and USDT and the account is set to Multi-currency Margin mode. We should expect the following sample message from the account channel:
Account | Account with Specific Coin |
{ “arg”: { “channel”: “account” }, “data”: [ { “adjEq”: “30979.1086748182657014”, “details”: [ { “availBal”: “”, “availEq”: “18962.59868274799”, “ccy”: “USDT”, “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” } ] } |
Subsequently we 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 coin will be published, including when the coin balance changes to zero. |
Fixed time updates | Updates pushed at a regular interval (10 seconds as of writing). Similar to the initial snapshot, all coins (or specified coins with the ccy parameter) with non-zero balance will be pushed. |
Alternatively we can still invoke the REST API to get the balance for coins with non-zero balance:
GET /api/v5/account/balance |
We 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,USDT,ETH). For example:
Unlike the account channel in WebSocket, however, the coin 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 we have possessed that coin before.
With auto borrow enabled in Multi-currency Margin mode, you can buy/sell the instrument exceeding your available cash balance of that coin by borrowing from OKX.
In this case it is quite useful to retrieve the max available tradable amount of the instrument including the available equity and loanable amount from exchange.
For this we can poll the following REST API in regular interval:
GET /api/v5/account/max-avail-size |
Here is a sample request and response for BTC-USDT with cross margin mode under Multi-currency Margin mode:
Request | GET /api/v5/account/max-avail-size?instId=BTC-USDT&tdMode=cross |
Successful Response | { “code”: “0”, “data”: [ { “availBuy”: “213800.4239369798722052”, “availSell”: “1.3539405224369181”, “instId”: “BTC-USDT” } ], “msg”: “” } |
For spot instruments, availBuy is in quote currency and availSell is in base currency.
The above response means a maximum of 213,800.42 USDT is available to buy BTC-USDT, and a maximum of 1.35394052 BTC is available to sell BTC-USDT. This should be the same as the amount you see when trading on the web UI.
In API v5 positions API is also unified across different products. It is recommended to retrieve the positions data using WebSocket.
Similar to the orders channel, In API v5 there are several subscription granularities when subscribing to the positions channel.
To subscribe to the above BTC-USDT-SWAP position updates, we can send one of the following after connecting to and logging into the private WebSocket:
Instrument Type | Instrument Type + Underlying (Derivatives only) | Instrument Type + Instrument ID | |
Request | { “op”: “subscribe”, “args”: [ { “channel”: “positions”, “instType”: “SWAP” } ] } | { “op”: “subscribe”, “args”: [ { “channel”: “positions”, “instType”: “SWAP”, “uly”: “BTC-USDT” } ] } | { “op”: “subscribe”, “args”: [ { “channel”: “positions”, “instType”: “SWAP”, “instId”: “BTC-USDT-SWAP” } ] } |
Successful response | { “event”: “subscribe”, “arg”: { “channel”: “positions”, “instType”: “SWAP” } } | { “event”: “subscribe”, “args”: [ { “channel”: “positions”, “instType”: “SWAP”, “uly”: “BTC-USDT” } ] } | { “event”: “subscribe”, “args”: [ { “channel”: “positions”, “instType”: “SWAP”, “instId”: “BTC-USDT-SWAP” } ] } |
We can pass ANY as instType to subscribe to positions of all product types at once as well.
The positions channel will publish an initial snapshot for the non-zero positions, i.e. pos > 0 or pos < 0.
Continuing with the BTC-USDT-SWAP cross margin order (with position net mode) example in the earlier section, the following sample message is expected (subscribed by instrument type+ underlying):
{ “arg”: { “channel”: “positions”, “instType”: “SWAP”, “uly”: “BTC-USDT” }, “data”: [ { “adl”: “2”, “availPos”: “”, “avgPx”: “50912.4”, “cTime”: “1615170596148”, “ccy”: “USDT”, “imr”: “165.15734103333082”, “instId”: “BTC-USDT-SWAP”, “instType”: “SWAP”, “interest”: “0”, “last”: “51000”, “lever”: “3”, “liab”: “”, “liabCcy”: “”, “liqPx”: “”, “margin”: “”, “mgnMode”: “cross”, “mgnRatio”: “0”, “mmr”: “1.98188809239997”, “optVal”: “”, “pTime”: “1615196199624”, “pos”: “1”, “posCcy”: “”, “posId”: “287999792370819074”, “posSide”: “net”, “tradeId”: “60477021”, “uTime”: “1615170598022”, “upl”: “0.4520230999924388”, “uplRatio”: “0.0027394232555804” } ] } |
Similar to the account channel, subsequently we will receive positions updates driven by the following:
Event-driven updates | Updates driven by events such as opening and closing positions. Multiple events (e.g., multiple orders being executed at the same time) may be aggregated into one single position update. Only affected position data will be published, including when the position is closed (i.e., changes to zero). |
Fixed time updates | Updates pushed at a regular interval (10 seconds as of writing). All non-zero positions that matched the subscription granularity will be pushed. |
You may notice that there is a Position ID (posId) field included in each of the position data, which can be used as an optional query parameter when invoking the REST API as shown in the following section.
This field is generated by mgnMode + posSide + instId + ccy and is included in the data for you to uniquely identify the position in the same account, and it does not change after you close and reopen the position.
Alternatively we can still invoke the REST API for non-zero positions:
GET /api/v5/account/positions |
The following granularity is available:
Granularity | Sample Request |
Instrument type | GET /api/v5/account/positions?instType=SWAP |
Instrument ID | GET /api/v5/account/positions?instId=BTC-USDT-SWAP |
Position ID (single) | GET /api/v5/account/positions?posId=287999792370819074 |
Position ID (multiple, max 20) | GET /api/v5/account/positions?posId=287999792370819074,289098391880081414 |
Unlike the positions channel in WebSocket, position, whether it is closed or not, will always be returned if it is specified in the parameter posId in the REST API, as long as we have opened that position before.
With the introduction of trade ID (tradeId) in the positions channel, it is now possible to reconcile order fill (from orders channel) and positions. One of the use cases of this is that we may want to derive the position from order fills.
A new order fill always comes with a newer trade ID, thus we can make use of this to match the relevant position/order fill, and to compare which data is newer by comparing the number of trade ID.
Yet there are few pitfalls:
To reconcile between fill and positions properly, we need to consider the above pitfalls and compare the position (or make use of position updated timestamp (uTime)) apart from comparing tradeId.
Let’s have a look into the following example sequence. Assume all are on the same instrument with the same margin mode and the position is in net mode.
Seq. | Channel | Data | Reconciled Position |
1 | order | fillSz=20, side=buy, tradeId=150 | 20 |
2 | positions | pos=20, tradeId=150, uTime=1614859751636 | 20 |
3 | positions | pos=18, tradeId=151, uTime=1614859752637 | 18 |
4 | order | fillSz=2, side=sell, tradeId=151 | 18 |
5 | order | fillSz=3, side=sell, tradeId=156 | 15 |
6 | order | fillSz=1, side=sell, tradeId=158 | 14 |
7 | positions | pos=10, tradeId=163, uTime=1614859755037 | 10 |
8 | order | fillSz=1, side=sell, tradeId=159 | 10 |
9 | order | fillSz=3, side=sell, tradeId=163 | 10 |
10 | positions | pos=10, tradeId=163, uTime=1614859755037 | 10 |
11 | positions | pos=6, tradeId=163, uTime=1614866547430 | 6 |
We can observe that:
In this guide, we’ve gone over the changes in API v5 and how it can be used to configure accounts and sub accounts. We’ve also shown how traders can retrieve account and position data using the WebSocket, and finally how to trade and reconcile positions.
The specifics in this document are likely to change as OKX continues to upgrade its Unified Account trading system. Please refer to the API v5 documentation for the latest specifications.
Telegram API community: https://t.me/OKXAPI
Telegram: https://t.me/OKXOfficial_English
Twitter: https://twitter.com/OKX
Facebook: https://www.facebook.com/okexofficial/
LinkedIn: https://www.linkedin.com/company/okex/
Reddit: https://www.reddit.com/r/OKX/
Instagram: https://www.instagram.com/okex_exchange
Sign up and log into the OKX account to claim a Mystery Box worth up to $500.
Disclaimer: This material should not be taken as the basis for making investment decisions, nor be construed as a recommendation to engage in investment transactions. Trading digital assets involve significant risk and can result in the loss of your invested capital. You should ensure that you fully understand the risk involved and take into consideration your level of experience, investment objectives and seek independent financial advice if necessary.
Start trading cryptocurrency on OKX
OKX Earn: Use your crypto assets to earn passive income on OKX
How to use the OKX trading bot for automated crypto buys and sells
beginners-tutorial-en