OKX · Risk Control
Quant Risk Control: How API Auto-Trading Won't Blow Up Your Account
A lot of people assume quant risk comes from the market: misread the direction and you lose. But anyone who's run scripts for a while will tell you what actually zeros out an account in an instant is usually not the market—it's your own code: a flipped direction, a loop that won't stop, a duplicate order after a dropped connection. Trading by hand, you get one last glance before hitting confirm; a script doesn't get that glance—it faithfully and continuously executes the error.
In this piece we (the MeowQuant desk) focus on quant risk control, and we come at it from "how to guard against it at the code level," not vaguely shouting "control your position." We break down the few classes of risk specific to API trading, give you copy-ready stop-loss, position-splitting, and retry code, then lay out the account-security bottom line and the order of operations for a "small-size cold start." By the end you should be able to fit your script with several brakes, so that even when it errors it doesn't drag you into a blow-up.
Risks specific to API trading
Manual trading and API auto-trading have different risk profiles. Manual trading's main enemies are emotion and judgment; API trading adds, on top of that, a whole class of "machine-specific" risks. Recognize them and you'll know where to fit the brakes.
Runaway program
This is the classic one. A piece of logic is wrong—buy/sell direction flipped, a stop-loss condition written backwards, a loop missing its exit condition—and the program won't stop to wonder "is something off here?" the way a person would; it executes the wrong instruction over and over. A human might catch one wrong order and notice; a script can place dozens of wrong ones in seconds. The lethality of a runaway is exactly in its faithfulness.
Duplicate orders
These crop up most when the network stutters or a request times out. You send an order request, get no response, the script thinks it failed and resends—but the first order actually filled, so you end up with two. On futures, that means a doubled position and doubled risk. Duplicate orders usually aren't a logic bug but a failure to handle "the uncertain in-between state."
Dropped connection / going offline
Your script runs on your computer or server, and the moment the network drops it loses contact with the exchange. The bad case: your position sits exposed in the market while the stop-loss logic that should protect it has gone offline along with the script. By the time you reconnect, things may be unrecognizable. The essence of connection risk is: don't stake all your protection on the assumption that "the script stays online."
Position management and splitting
The first wall of risk control is making sure no single error can reach all your funds. This is done with position rules—and those rules need to be hard-coded, not left to your in-the-moment restraint.
A few principles we've always stuck to ourselves:
- Set a per-order cap. No single order exceeds a fixed fraction of total funds (say 1%–2%). That way even if this order is entirely wrong on direction, the loss has a ceiling.
- Set a total-position cap. All open positions together don't exceed a total, so the program never gets a chance to bet all your funds at once.
- Split, don't go all-in. Break funds into a few tranches and enter in batches, reducing the impact of a judgment error at any single entry point.
- Leave the script some cash. Always keep a portion of funds out of trading in the account—both a buffer and room for you to act manually when something goes wrong.
Write these as hard constraints in code, check them before placing an order, and refuse the order if they're not met:
MAX_PER_ORDER_RATIO = 0.02 # single order no more than 2% of total funds
MAX_TOTAL_RATIO = 0.30 # total position no more than 30% of total funds
def check_position_limit(okx, symbol, order_value_usdt):
bal = okx.fetch_balance()
equity = bal['USDT']['total'] # total equity in USDT
# per-order cap
if order_value_usdt > equity * MAX_PER_ORDER_RATIO:
raise ValueError('Exceeds per-order position cap, order refused')
# here you can also layer a check that "current total position + this order"
# must not exceed MAX_TOTAL_RATIO
return True
It's not complicated, but its value is this: it turns "I should control my position" from an intention into a gate the script must pass before it can place an order. Intentions waver; code doesn't.
Stop-loss must be programmatic (in code)
If you remember only one line from this article, remember this: the stop-loss must be programmatic. A script runs automatically, and you can't watch the screen 24/7; when the market reverses fast, by the time you see it and close out by hand you're often several price levels too late.
Programmatic stop-losses come in two approaches, best used together:
Approach one: exchange-side conditional / stop orders. Place a stop order on OKX at the same time as the main order, so the exchange closes your position automatically at the trigger price. Its biggest upside: even if your script drops offline, this stop-loss still takes effect on the exchange side. It's the most reliable move against "an exposed position when the connection drops."
Approach two: local monitoring + trigger close in the script. The script continuously reads the floating loss on the position in a loop, and once it exceeds your threshold it actively fires a close order. It's more flexible (can do trailing stops, time-based stops, etc.), but requires the script to be online.
Below is a minimal skeleton of a local-monitoring stop-loss to demonstrate the logic (always verify on the demo account before going live):
import ccxt, time
okx = ccxt.okx({
'apiKey': 'YOUR_apiKey',
'secret': 'YOUR_secret',
'password': 'YOUR_Passphrase',
'enableRateLimit': True,
})
symbol = 'BTC/USDT'
entry_price = 60000 # your average entry price (example)
amount = 0.001 # position size
stop_ratio = 0.05 # stop out on a 5% drop
while True:
ticker = okx.fetch_ticker(symbol)
last = ticker['last']
drawdown = (entry_price - last) / entry_price
if drawdown >= stop_ratio:
okx.create_order(symbol, 'market', 'sell', amount)
print(f'Stop-loss triggered, closed at market @ {last}')
break
time.sleep(10) # leave a gap, don't hammer the endpoint
Two notes: one, the loop must have a sleep or it'll hit the rate limit; two, a local stop-loss is only a supplement—the exchange-side stop order is the last line of defense when the connection drops, so do both to be solid.
Guarding against duplicate orders and dropped connections
The root of duplicate orders is "I'm not sure whether the last order actually went through." The core of guarding against it: don't blindly resend—confirm state first.
Two methods used together:
Use a client-side custom order ID (clientOrderId). Attach a unique ID you generate yourself when placing an order; the exchange rejects a duplicate ID, which is a natural dedup mechanism. Even if you resend after a timeout, the exchange only recognizes the first order.
import uuid
cid = 'meow-' + uuid.uuid4().hex[:16] # a unique order ID you generate yourself
order = okx.create_order(
symbol, 'limit', 'buy', 0.001, 50000,
params={'clientOrderId': cid}, # attach it; a resend is still recognized only once
)
After a timeout / dropped connection, check before topping up. If an order request times out, don't just resend; first use an open-order query or position query to confirm whether the last order actually went in, then decide whether to top up. Same after reconnecting—run fetch_open_orders and fetch_positions to see the real current state clearly, then let the strategy continue. Making "check state first, then act" the fixed first step after reconnecting blocks the vast majority of duplicate operations caused by losing contact.
OK30001 for a fee discount that applies to API orders too. Sign up for OKX and open the API here →
Rate limits, timeouts, and retries
Risk control isn't only about avoiding losses—it also includes keeping the script from "killing itself." Handle rate limits and timeouts poorly and your strategy can fail at the critical moment.
Rate limits: turn on 'enableRateLimit': True at init, and ccxt automatically inserts gaps between requests. Add exponential backoff in high-frequency loops (wait 1 second on the first hit, then 2, then 4). We cover this in more detail in our common API errors piece; here we just stress: any loop hammering an endpoint, rate-limiting is standard issue.
Timeouts: set an explicit timeout on requests, so the script doesn't seize up entirely because one request hangs. ccxt can be configured with 'timeout' (in milliseconds):
okx = ccxt.okx({
'apiKey': 'YOUR_apiKey',
'secret': 'YOUR_secret',
'password': 'YOUR_Passphrase',
'enableRateLimit': True,
'timeout': 10000, # 10-second timeout per request, avoids hanging
})
Retries have a cap. You can retry on a network stutter, but set a maximum count; once you hit it, alert or stop, rather than retrying forever. Infinite retry is itself a kind of "runaway"—it keeps consuming requests, and possibly repeating operations, while you're not watching.
Permissions and IP whitelist: the security bottom line
The bottom layer of risk control is account security. However steady your script, leak the credentials and it all goes to zero. There are two near-exceptionless bottom lines here:
Tick only "Read" and "Trade"—never "Withdraw." Running quant never needs the withdrawal action at all. As long as the Key has no withdrawal permission, even if your apiKey, secret, and Passphrase trio leaks someday, whoever gets them can at most place wild orders in your account (bad, but the position and stop-loss constraints above backstop it), and can't run off with your coins. The moment you tick withdrawal, a leak is like handing over the keys to your safe.
Set an IP whitelist. If your script runs on a fixed-IP machine (cloud server, home broadband with a static public IP), add that IP to the Key's whitelist so only requests from it are accepted—security up another notch. If your IP changes often, a whitelist will reject you daily by mistake; in that case you can skip it for now, but you must keep your credentials well guarded—don't hard-code them in code that gets uploaded; use environment variables or a config file that isn't synced.
These two, plus the positions and stop-losses above, make up the protection where "even if the worst happens, the loss has a boundary." The exact steps for creating a Key are in our API quant intro, and you can also use the API permission self-check list to run through it before opening a Key.
Small-size cold start: the pre-real-money checklist
With all the code-level risk control fitted, going live still has one last step: don't pile in heavy from the start. When a new strategy goes to real money, we always run a "small-size cold start," in this order:
- Run on the demo continuously first. Cover a few up-and-down cycles, and confirm ordering, cancelling, stop-loss, and callbacks all match expectations. This step costs nothing; skipping it is using real money as your test environment.
- Then cold-start with a tiny bit of real money. An amount small enough that losing it all wouldn't sting; 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 the demo, the stop-loss really triggers, and there are no duplicate orders, then scale up gradually.
- Consider futures and leverage last. Before spot runs steadily, don't touch leverage. Leverage multiplies every class of risk above, so at the beginner stage we strongly suggest spot only.
Combine this checklist with the code constraints above and you've fitted your script not just one brake but several: positions are capped, stop-loss triggers automatically, duplicate orders are blocked, a leaked credential still can't move money, and you test with small money before going in. Any one alone isn't enough—together they put enough of a buffer between "error" and "blow-up."
Tested: a gotcha we hit
print spam the screen, hit Ctrl+C to stop it, fixed the direction, added the exit condition and a sleep(10), and tacked on that per-order position-cap check. Had this happened on live with leverage on, that string of bad orders would have been real money pouring out continuously. This gotcha set the rules we follow now: stop-loss logic must be verified on the demo first, the loop must have a gap and an exit condition, and every order must pass the position-cap check before placement—none of the three optional.
FAQ
What's the risk in API auto-trading most likely to blow up an account?
Not the market itself, but the program's faithfulness—a script never hesitates like a human. A flipped buy/sell direction, a loop missing its exit condition, a duplicate order fired off after a dropped connection: things you might catch before confirming by hand, a script will execute dozens of times in a row. Add futures leverage and that kind of error can zero out your capital in a very short time. The core of guarding against it is writing risk control into the code, not relying on yourself to watch.
Does the stop-loss have to be in code? Can't I just watch it manually?
We strongly recommend making it programmatic. A script runs automatically, and you can't watch 24/7; once the market reverses fast, by the time you see it and stop out by hand it's often too late. Write the stop-loss logic into the strategy, or use OKX's conditional/stop orders so the program closes the position at the trigger line automatically—that keeps pace with auto-trading. Watching by hand can only be a supplement, never the sole line of defense.
How do I stop a script from placing duplicate orders?
Use two methods together. One: give each order a client-side custom order ID (clientOrderId); the exchange rejects a duplicate ID, so it deduplicates naturally. Two: after placing an order, check the current open orders and positions before deciding whether to continue, rather than blindly firing round after round. After a dropped connection or timeout especially, check state first and only then top up—don't assume the last order failed and just resend.
How do I set API Key permissions to be safe?
For a quant Key, tick only Read and Trade, never Withdraw. That way even if the three credentials leak, whoever gets them can at most place wild orders in your account, not run off with your coins. Then bind an IP whitelist to the Key (if you run on a fixed-IP machine) so only requests from the specified IPs are accepted, taking security up another notch. Automated trading never needs to withdraw anything.
How much should I put in the first time a new strategy goes live?
Use an amount you wouldn't mind losing entirely, treat it as tuition rather than capital. Smooth the flow on the demo first, then do a cold start with a tiny bit of real money, watch it for the first few days, confirm live behavior matches the demo and that slippage and fills are within expectations, then scale up gradually. Going in heavy from the start is betting all your funds on unverified code.
What happens to a running script if the connection drops?
It depends on how you wrote it. The bad version: on a dropped connection the request times out and the script crashes, or it blindly retries and causes duplicate orders, while your position sits exposed in the market with no stop-loss. The sound approach: set timeouts and retry caps on requests, and after reconnecting, first check current positions and open-order state before deciding what to do, and rely on an exchange-side stop order (not just the script's local judgment) so that even if the script drops offline, the close-out protection still takes effect on the exchange side.
Risk control isn't done-and-dusted once you finish the code—it has to grow alongside your strategy. We suggest saving this article's checklist and running through it every time you put up a new strategy. Next you can read the common API errors to round out your script's stability, and always verify new code on the demo account first. Remember the old line: survive first, talk returns later.
Fit the risk control, then let the strategy run for you
Get your account and API ready, verify positions, stop-losses, and dedup on the demo, then cold-start with a small amount of real money. A new account registered with the invite code gets a fee discount, and orders placed via the API get it too.
Crypto asset prices swing violently, and futures and leverage can lead to a total loss of capital. Quant and automated trading don't guarantee profit—use only funds you can afford to lose.