OKX · Basics
How to Use OKX Demo Trading: Smooth Out Your Strategy with Play Money First
OKX's demo trading places orders with virtual funds, and it shares your OKX login, so you can switch over as soon as you are signed in. Connecting a script to it differs from live in two places: you need a separate API Key created inside the demo environment, and every request has to carry the x-simulated-trading: 1 header. The second is where things go wrong most easily: leave it out and the request goes to live.
The sections below cover where the entry is, how to create the key, the ccxt connection and its self-check, how the REST and WebSocket addresses differ, and what the demo cannot verify.
What OKX demo trading supports and what it doesn't
OKX's Help Center article What's demo trading and how do I use it? says demo mode simulates spot, margin, futures and options trading, and that the balances are virtual assets for simulated trading only. On the API side, the Demo Trading Services section of the API docs states that the API works for demo trading but some functions are not supported, such as withdrawal, deposit and purchase/redemption.
For someone writing a script, the demo is the place to confirm a few things: whether the three credentials are right, whether the exchange accepts your order parameters, whether the returned fields have the structure you assumed, and whether your cancel and query steps run in the right order. A flipped buy/sell direction or a loop missing its exit condition surfaces here at the cost of virtual funds only.
Where demo trading is, and how to create a demo API key
The entry points given by the Help Center:
- Web: hover on the Trade section and select Demo trading.
- App: open the User Center menu and look for Demo trading under the Trade section or in Shortcuts.
- Resetting virtual funds: the article notes you need to close all positions and pending orders, if any, before resetting demo assets.
If you only want to try built-in bots such as grid or copy trading in the interface, entering demo trading is enough. To connect a script you also need a separate API Key created inside the demo environment; the path in the API docs is: log in to OKX → Trade → Demo Trading → Personal Center → Demo Trading API, and create a Demo Trading API Key there. The docs state it as a requirement that demo API trading uses a key created in demo trading, so each side has its own key. Permissions and the Passphrase are filled in the same way as for live; see the API quant starter.
Connecting ccxt to the demo: set_sandbox_mode and a self-check
Connecting ccxt to the demo differs from connecting to live in two ways: one extra line, and credentials that come from the demo Key. The fields are still the same three: apiKey, secret, and password (the Passphrase you set when creating the Key; in ccxt this field is called password).
import ccxt
# Note: these three must be your demo (Demo Trading) Key, not the live one
okx = ccxt.okx({
'apiKey': 'YOUR_DEMO_apiKey',
'secret': 'YOUR_DEMO_secret',
'password': 'YOUR_DEMO_Passphrase', # OKX-specific, called password in ccxt
})
# The key line: make ccxt attach the demo header to every later request
okx.set_sandbox_mode(True)
# Self-check: this line must print 1, otherwise you are still on live
print(okx.headers.get('x-simulated-trading'))
# After this, all requests carry the demo flag and use virtual funds
balance = okx.fetch_balance()
print('Demo USDT free:', balance['USDT']['free'])
What set_sandbox_mode(True) does is make ccxt attach the demo header x-simulated-trading: 1 to every subsequent request. OKX's demo and live sit on the same REST host, and which environment a request lands in depends entirely on that header, so don't expect to tell them apart by URL. With the line in, your balance checks, orders, and cancels all run on play money; remove it (or set it to False) and the same code returns to real money.
You may have seen the variant that writes sandboxMode into options at init. That one does not get you onto the demo: in ccxt the demo header is added by the set_sandbox_mode() method itself, and writing options in the constructor never calls it: no header, no flag, and the request goes out exactly like a live one. So don't let a config value stand in for the line above: call okx.set_sandbox_mode(True), then use that print as your self-check.
What happens if set_sandbox_mode(True) is missing: the request goes out with no x-simulated-trading: 1 header and is treated as a live request no matter which Key was filled in. With the live Key, fetch_balance returns the live balance, which is easiest to misread when that account is empty, and the order that follows is a real one. So don't judge which side you are on by whether something errored; print the self-check line above on every start, and stop if it is not 1. If the toggle is on and you still get auth errors, check first whether the live credentials were pasted into the demo script, then go through the API error guide.
Once the connection and self-check pass, the code for placing, querying and cancelling orders is the same as for live. It lives in the API quant starter and is not repeated here. One thing to watch when you carry it over: the cancel-order response has no order state field (only ordId, clOrdId, sCode, sMsg and so on). An sCode of 0 means the cancel request was accepted; to confirm the order was really cancelled, query that order again.
Demo REST and WebSocket URLs
REST and WebSocket separate the two environments differently. The addresses listed in the API docs:
| Channel | Live | Demo |
|---|---|---|
| REST | https://openapi.okx.com | https://openapi.okx.com, plus the header x-simulated-trading: 1 |
| WebSocket public | wss://ws.okx.com/ws/v5/public | wss://wspap.okx.com/ws/v5/public |
| WebSocket private | wss://ws.okx.com/ws/v5/private | wss://wspap.okx.com/ws/v5/private |
| WebSocket business | wss://ws.okx.com/ws/v5/business | wss://wspap.okx.com/ws/v5/business |
For REST the host is the same on both sides, so the URL tells you nothing about the environment; only the header does. WebSocket is the other way round: the demo has its own wspap.okx.com addresses. If you write your own WebSocket connection, change both things: the address to wspap, and the login to the demo Key. Subscribing to market data and orders over WebSocket is covered in OKX WebSocket real-time data.
What the demo can't verify
What the demo confirms is the flow. Three things it cannot confirm:
- Fill price and slippage. A fill in the demo is not the price the same order would get on the live book. Live slippage depends on the resting depth at that moment and on your order size, and can only be measured live with a small amount.
- Liquidity on thin pairs. An order filling in the demo says nothing about whether that pair can absorb the same size live.
- Behavior in extreme conditions. Delays and rejections during wicks and flash crashes have to be handled by the timeouts, retries and stops in your script; see OKX API risk control.
The order for switching from demo to live
Each time you put up a new strategy, this order is worth following:
- Run on the demo continuously for a stretch. One successful run is not enough; let it run for enough time, covering a few up-and-down cycles, and confirm ordering, cancelling, callbacks, and balance changes all match expectations.
- Going live: one toggle plus one set of credentials. Remove
set_sandbox_mode(True)or change it toFalse, and swap the credentials back to your live Key (the Demo Trading Key doesn't work on live). Having verified on the demo first, these two are the only things you touch when you switch. - Use only a tiny amount on your first live run. An amount you wouldn't mind losing entirely; treat it as tuition. The slippage and depth the demo can't reproduce only surface now.
- Watch it for the first few days. Don't actually walk away; confirm live behavior matches what you saw on the demo, then scale up gradually.
FAQ
Does the demo account need a separate API Key?
Yes, it needs its own. The demo Key has to be created inside the demo environment: log in to OKX → Trade → Demo Trading → Personal Center → Demo Trading API, and create a Demo Trading API Key there; that is the path OKX's own docs give you, not a switch you flip on your live Key. The part that catches people out: the demo and live share the same REST host, and the two are told apart only by the x-simulated-trading: 1 request header, so you cannot work out which environment you are in by looking at the URL. Calling set_sandbox_mode(True) in ccxt adds that header for you; print okx.headers.get('x-simulated-trading') before you run anything, and if it is not 1 you are still on live.
Is the demo money real? Do I have to cover losses?
The demo uses virtual funds, not real money, and a loss takes nothing from your real account. OKX's Help Center article on demo trading describes the balances as virtual assets for simulated trading only that don't represent real money, and notes that before resetting them you need to close all positions and pending orders, if any.
Is the demo WebSocket URL the same as live?
No. According to OKX's API documentation, REST uses https://openapi.okx.com for both, told apart by the x-simulated-trading: 1 request header. WebSocket uses a separate set of addresses: live is wss://ws.okx.com/ws/v5/public, private and business, while demo is wss://wspap.okx.com/ws/v5/public, private and business. If you write your own WebSocket connection, switch to the wspap addresses for demo and log in with the demo key.
What's the difference between set_sandbox_mode and sandboxMode in the config?
They are not equivalent. Writing sandboxMode in the config on its own will not switch you to the demo. ccxt's set_sandbox_mode() is a method: it switches the API URLs to the test set, sets sandboxMode in options, and adds x-simulated-trading: 1 to headers. That header only ever gets added inside this method. Writing sandboxMode in the constructor's options never calls the method, so no header is added and no flag is set, and the request goes out exactly like a live one. Use okx.set_sandbox_mode(True) only, then print okx.headers.get('x-simulated-trading') as a self-check; it has to come back 1 before you are really on the demo.
Once the demo works, how many lines do I change to go live?
The code itself is one line: remove set_sandbox_mode(True) or change it to False; but the credentials have to change with it, because the Demo Trading Key does not work on live, so put your live apiKey, secret and Passphrase back in. That's exactly the upside of running on the demo first: verify logic with play money, then change one line to go to real money. But for your first live run, always use only a tiny amount and watch it for a while, because the demo can't reproduce real slippage and depth.