How to place an order on several exchanges from one codebase
One order method over many venues — which library or service to use, and what the unified call hides about precision, order flags, rate limits and fills.
Use CCXT unless you have a reason not to: one order method over 104 exchanges, MIT licensed, with your keys in your own process. Reach for a framework or a paid execution service when order state matters more than the venue count, and for a first-party SDK when one venue's own surface is the strategy. What the unified call hides is per-symbol precision, per-venue order flags, and the gap between an acknowledgement and a fill.
The short way
CCXT and create_order. It is an MIT-licensed client library, version 4.5.81 ships
104 REST exchange classes and 76 websocket ones, and it is the layer most of the frameworks below
reach a venue through.
import os
import ccxt
exchange = ccxt.binance({
'apiKey': os.environ['YOUR_API_KEY'],
'secret': os.environ['YOUR_API_SECRET'],
})
exchange.load_markets()
symbol = 'BTC/USDT'
amount = exchange.amount_to_precision(symbol, 0.01)
price = exchange.price_to_precision(symbol, 60000)
order = exchange.create_order(symbol, 'limit', 'buy', amount, price)
print(order['id'], order['status'], order['filled'])
Swap ccxt.binance() for ccxt.okx() and the rest of the file is unchanged. That is the whole
proposition: the venue becomes a configuration value instead of a rewrite. Nothing of the
vendor's sits in the path either — it is a client-side library, so your process talks to the
exchange and your keys stay where you put them.
Two lines in that snippet are the ones people leave out. load_markets() has to run before the
other two, because amount_to_precision and price_to_precision are reading that venue's rules
for that symbol; without them you are sending a number you invented to a matching engine that
publishes the increments it accepts.
One thing to switch off before it matters: CCXT participates in builder programmes on seven DEX
venues and adds one basis point on top of the exchange's own fees there by default, disabled with
exchange.options['builderFee'] = False. That is disclosed in the project's README rather than
hidden, and it is the mildest example of a pattern worth knowing about before you pick any free
bot — who pays for your free trading bot is the
long version.
What the options are
One library, many venues. CCXT, as above. Use it when the venue is a parameter rather than a decision, and when what you need from a venue is the four operations every venue has.
The same idea, in C++. ccapi is header-only C++17 with SWIG bindings for five
other languages, covering 30 venue identifiers for market data of which 28 also take orders, and
FIX on three of them. It imitates Bloomberg's API — a Session, a Subscription and a
Request/Event model — which is what keeps the surface identical across venues and languages,
because the venue is a string and the operation is an enum. The build is the install: there is no
published wheel or npm package, and pip install ccapi fetches an unrelated project.
An engine that owns the order, not a client that sends one. NautilusTrader is event-driven with a Rust core, and every integration in its own table is marked stable — Binance, BitMEX, Bybit, Coinbase, Deribit, Kraken and OKX on the centralised side, dYdX, Derive, Hyperliquid, Lighter and Polymarket on the decentralised one. The reason to start here rather than with a connector is that the same strategy object runs against a simulated venue and a real one, so order state, fills and positions are modelled once.
A bot rather than a codebase. Hummingbot already contains the scheduler, the sizing and the order lifecycle: 26 spot connectors plus a paper-trade one and 20 perpetual connectors in the repository, with DEX venues reached through a separate Gateway service. Note the reporting default — each instance sends aggregated volume, connector id, version and system specs home every fifteen minutes unless you turn it off.
Somebody else's connectors, on a subscription. CoinAPI EMS is the managed version of this whole page: one order model, nine order statuses with every legal transition documented, smart order routing with TWAP, VWAP and iceberg, and FIX. It starts at $999 a month, it holds your exchange credentials in its cloud, and its destination table is nineteen distinct venues that do not include OKX, Bybit, KuCoin, Bitget, Gate.io or Hyperliquid.
And the case against all of them. When one venue's own surface is the strategy — vaults,
sub-accounts, builder fees, a chain-level transaction — a first-party SDK reaches things no
unified layer exposes. The Hyperliquid Python SDK carries over
fifty methods on Exchange, only about a dozen of them order entry;
dYdX v4 Clients hides the split between a hosted indexer for reads and a
signed chain transaction for orders. Neither reaches a second venue, which is the trade.
Where this breaks
The unified vocabulary is the intersection, not the union. CCXT's unified order structure
lists two values for type, market and limit. Kraken's Add Order endpoint accepts eleven
ordertype values, including iceberg, trailing-stop, trailing-stop-limit and
settle-position. Everything in the gap goes through the exchange-specific params dictionary,
and the manual is explicit that its contents are exchange-specific and you must consult the
venue's own documentation for the field names. So the portable part of your code is the part
that is not the strategy, and the abstraction stops paying exactly where your strategy gets
particular.
The flags that change what an order is do not map cleanly. Post-only is the clearest case,
because three venues express one intention three ways. Kraken makes it an order flag — post,
documented as available only when ordertype = limit. Binance makes it an order type,
LIMIT_MAKER, "a LIMIT order that will be rejected if the order immediately matches and trades
as a taker". CCXT makes it a timeInForce value, PO, and says in the same breath that the
unification of timeInForce is a work in progress and that PO is unified only on exchanges
where exchange.has['createPostOnlyOrder'] is true. Reduce-only has the same problem with a
different shape: Kraken exposes it as a boolean that makes an order "only reduce a currently open
position, not increase it or open a new position", and a venue with no such concept has nothing
for a unified field to become. A flag that silently does not apply is worse than one that errors,
because it is the difference between closing a position and doubling it — and on a maker strategy
it is also the difference between the two sides of the taker fee.
Precision and minimum notional are per symbol, and they are two different rules. CCXT's manual
says so in bold: "Do not confuse limits with precision!" — a precision of 0.01 does not imply
a minimum of 0.01, and the reverse does not hold either. Binance publishes both through
exchangeInfo as symbol filters: PRICE_FILTER carries a tickSize that a price must be a whole
multiple of, LOT_SIZE carries minQty, maxQty and a stepSize for the quantity, and
NOTIONAL carries a minimum and maximum order value with separate switches for whether each
applies to market orders. There is a nastier one underneath: amount does not always mean the
same thing. Some venues want a market buy expressed in quote currency — how much you want to
spend — which CCXT handles with a per-exchange createMarketBuyOrderRequiresPrice option rather
than with a unified field. Get that wrong and the order is not rejected; it is filled, at the
wrong size.
Rate limits are counted in different units on every venue, and a wrapper cannot average them. Binance states it plainly: "The limits on the API are based on the IPs, not the API keys." Weight is per route, a 429 is a warning, and repeatedly ignoring one earns an automated IP ban that "scale[s] in duration for repeat offenders, from 2 minutes to 3 days" — while, separately, "the number of unfilled orders is tracked for each account". Bybit puts the same two counters the other way up: its API rate limit is "per second per UID", with a further allowance of 600 requests in a five-second window per IP, and an over-frequent IP gets a 403 and an instruction to terminate all HTTP sessions and wait at least ten minutes. One venue's per-key budget is another's per-IP budget, a second key buys nothing on the first, and a shared egress address means you are counted alongside strangers. CCXT's limiter is client-side, paced against constants compiled into each exchange class rather than against anything the venue reports back, so a venue that quietly tightens a limit is discovered when you are banned. Rate limit sets out the families of limit in use across this catalogue.
A key is a set of permissions and a place it sits, and both travel worse than code does. Kraken splits the grants finely enough to show the shape of the problem: "Modify Orders is required for the trading endpoints that place new orders, such as AddOrder, EditOrder, and AddOrderBatch", while "Cancel/Close Orders is required for the trading endpoints that cancel open or pending orders". A key can therefore open a position and be unable to close it, and nothing in a unified library will tell you before the cancel fails. The same page describes IP whitelisting as "a security feature that restricts API key use to specific client side IP addresses" and key expiration as a key valid for a fixed period — both excellent, and both incompatible with a deployment whose egress address changes or that you forget to renew. Then there is where the key lives. A client library keeps it in your process; a managed execution service holds your exchange credentials in its cloud, which is the actual thing CoinAPI EMS sells and the actual thing you are accepting. The pattern to copy is Hyperliquid's agent wallet — a generated key its own example documents as having no permission to transfer or withdraw funds — so that the credential on the trading box can lose and cannot leave. And note that the key is governed by the venue's own agreement rather than by the library's licence: the market data you pull back down the same connection has separate terms, which what exchange API terms actually let you do covers.
A REST acknowledgement is not a fill. Binance's new-order endpoint takes a newOrderRespType
of ACK, RESULT or FULL, and the default is not uniform: market and limit orders default to
FULL, "all other orders default to ACK". So for a stop-limit the default response tells you
the order exists and nothing about what it did. The documentation also notes that the API system
is asynchronous and some delay in the response is normal and expected. What the order actually
did arrives on the private websocket: executionReport, whose execution types are NEW,
CANCELED, REPLACED, REJECTED, TRADE, EXPIRED and TRADE_PREVENTION, carrying cumulative
filled quantity and cumulative quote quantity — the documentation notes the average price is the
second divided by the first — and a rejection reason from a documented list that includes
WOULD_MATCH_IMMEDIATELY, which is the post-only case above arriving as an event. Polling
fetch_order in a loop instead is how one order becomes a rate-limit problem. If what you want is
to check the request without checking your risk limits, Kraken has a validate flag: "If set to
true the order will be validated only, it will not trade in the matching engine."
"Supports venue X" is a claim about a codebase. It is worth resolving before you build on it,
and every product here gives you a way to. CCXT's count is exchange classes, not venues traded
last month, and what works is per method — exchange.has is the answer, and 28 of the 104 classes
have no websocket class at all. ccapi's thirty are compile-time identifiers, so Binance is three
of them and Huobi another three. Seven of CoinAPI EMS's twenty-six destinations are UAT test
environments of venues already counted. Barter declares a Binance execution
module whose source file is a single newline byte, in the published crate as well as the
repository. GoCryptoTrader implements its own connectors for 22
exchanges, which is genuinely unusual, and its own README says the bot is not ready for
production. Count identifiers and read the capability table; do not count names in a README.
If you outgrow this
If the problem is order state — reconnects, partial fills, what your position is after a restart, whether a cancel landed — that is not a connector problem and a bigger connector will not fix it. NautilusTrader is the shape that answers it, with the caveat from its own card that it is mid-transition between a v1 line on security backports and a v2 release candidate. Hummingbot is the same answer for somebody who would rather configure than build.
If the problem is who maintains the adapters at three in the morning, that is what CoinAPI EMS is sold as, and the check to do first is its destination list rather than its price — a managed connector set that does not include the venue you trade is a subscription to somebody else's 2019. The rest of the field is in crypto trading bots and execution SDKs.
If the problem is latency in your own process, ccapi is the C++ answer and Barter is the Rust
one, with the difference that Barter ships no live execution client at all — implementing its
ExecutionClient trait against your venue is your work.
If the problem is that one venue's features are the strategy, stop unifying. The Hyperliquid Python SDK and dYdX v4 Clients each give you the whole of one venue and none of anybody else's, and both are free.
And whichever of these you land on, rehearse against a testnet before a live key exists. The
support is uneven and each product says where: CCXT's setSandboxMode(true) swaps in the venue's
test endpoints and raises NotSupported where there is no testnet; the Hyperliquid SDK ships
testnet and local-node URLs as constants; dYdX's Python client ships testnet network constants and
a faucet, so a rehearsal costs a faucet request rather than an account application. ccapi has no
sandbox switch at all, and GoCryptoTrader wires sandbox routing into exactly two of its
twenty-two exchanges.
The tools named above
In the order this page puts them in, which is an editorial judgement and not a ranking anyone paid for.
CCXT
One createOrder over 104 exchange classes, MIT, client-side — the default answer, and the layer most of the frameworks below reach venues through.
One MIT client for 104 crypto exchanges, 76 of them over websocket.
FreeFree tierOpen source
ccapi
The same idea in header-only C++17 across 30 venue identifiers, 28 of which take orders, on a Bloomberg-shaped Session and Request model.
Header-only C++ market data and execution for 30 venues, on a Bloomberg-shaped API.
FreeFree tierOpen source
NautilusTrader
An event-driven engine that owns order state rather than a client that sends orders, with every adapter in its own table marked stable.
Event-driven Rust engine that settles perpetual funding at the venue boundary.
FreeFree tierOpen source
Hummingbot
A finished bot instead of a codebase — 26 spot and 20 perpetual connectors in the repo, plus a separate Gateway process for DEX venues.
Apache-2.0 Python framework for running market-making bots on CEXs and DEXs.
FreeFree tierOpen source
CoinAPI EMS Trading API
Somebody else's connectors on a subscription: one order model, nine documented statuses and smart order routing, from $999 a month.
One order model over 26 exchange destinations, TWAP and VWAP, priced per account-hour.
$999/mo
FAQ
Can one library really place an order on any exchange?
It can place the order every exchange has, which is a market or a limit order with a side, an amount and a price. The CCXT unified order structure lists exactly two values for type — market and limit — and everything past that reaches the venue through the exchange-specific params dictionary, which the manual describes as contents you have to look up in the venue's own documentation. So the portable part of your code is the part that is not strategy.
Why was my order rejected when the price looked fine?
Most often a symbol rule you did not read. Binance publishes per-symbol filters through exchangeInfo — PRICE_FILTER sets a tickSize the price must be a multiple of, LOT_SIZE sets minQty and a stepSize for the quantity, and NOTIONAL sets a minimum and maximum order value. CCXT's manual warns in bold not to confuse limits with precision: a minimum size and a rounding increment are two different rules and neither implies the other.
Does a 200 response mean the order filled?
No. On Binance the response shape is chosen by newOrderRespType, and while market and limit orders default to FULL, every other order type defaults to ACK — an acknowledgement with no fill information in it. The documentation also says the API system is asynchronous and some delay in the response is normal and expected. Order state lives on the private user data stream, where executionReport carries the execution type, the filled quantity and the rejection reason.
Will a wrapper keep me inside every venue's rate limit?
Not reliably, because the venues are not counting the same thing. Binance states that its limits are based on IPs and not API keys, and separately tracks unfilled order count per account; Bybit's API limit is per second per UID, with a further 600 requests per five seconds per IP on top. A client-side limiter paces against constants compiled into the library rather than against anything the venue reports back, so a tightened limit is discovered by being banned.
Where should the API key live if the software is not mine?
Ask that before the venue list. A client library runs in your process and the key never leaves it; a managed execution service holds your exchange credentials in its own cloud by design. Between those, prefer a credential that cannot move money: Hyperliquid's SDK mints an agent key its own example describes as unable to transfer or withdraw funds, and Kraken splits key permissions so that placing orders, cancelling them and touching funds are separate grants.
Sources
- Manual — Precision And Limits, Placing Orders, and the unified order structure — CCXT, read
- Filters — PRICE_FILTER, LOT_SIZE, MIN_NOTIONAL and NOTIONAL — Binance, read
- New Order, Spot REST API trading endpoints — Binance, read
- Limits — IP limits, weight and unfilled order count — Binance, read
- User Data Streams — the executionReport event — Binance, read
- Add Order, Spot REST API — Kraken, read
- API key permissions — Kraken, read
- Rate Limit Rules — IP limit and per-UID limits — Bybit, read
The catalogue next door
This page names a handful of products. The rest of them are in Crypto Trading Bots & Execution SDKs, each filled in against the same schema, with the fields to narrow it yourself.
Last updated . Corrected in place — an endpoint that moves is a bug on this page, not a new post.