# 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.

*https://cryptomarkets.tools/how-to/place-an-order-on-several-exchanges · next to Crypto Trading Bots & Execution SDKs*

**Answer:** 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.

## Approaches

*In the author’s order. Paid placement does not affect it.*

1. [CCXT](https://cryptomarkets.tools/tools/ccxt.md) — One createOrder over 104 exchange classes, MIT, client-side — the default answer, and the layer most of the frameworks below reach venues through.
2. [ccapi](https://cryptomarkets.tools/tools/ccapi.md) — 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.
3. [NautilusTrader](https://cryptomarkets.tools/tools/nautilus-trader.md) — An event-driven engine that owns order state rather than a client that sends orders, with every adapter in its own table marked stable.
4. [Hummingbot](https://cryptomarkets.tools/tools/hummingbot.md) — A finished bot instead of a codebase — 26 spot and 20 perpetual connectors in the repo, plus a separate Gateway process for DEX venues.
5. [CoinAPI EMS Trading API](https://cryptomarkets.tools/tools/coinapi-ems.md) — Somebody else's connectors on a subscription: one order model, nine documented statuses and smart order routing, from $999 a month.

## The short way

[CCXT](https://cryptomarkets.tools/tools/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.

```python
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](https://cryptomarkets.tools/guides/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](https://cryptomarkets.tools/tools/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](https://cryptomarkets.tools/tools/nautilus-trader) 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](https://cryptomarkets.tools/tools/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](https://cryptomarkets.tools/tools/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](https://cryptomarkets.tools/tools/hyperliquid-python-sdk) carries over
fifty methods on `Exchange`, only about a dozen of them order entry;
[dYdX v4 Clients](https://cryptomarkets.tools/tools/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](https://cryptomarkets.tools/glossary/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](https://cryptomarkets.tools/glossary/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](https://cryptomarkets.tools/guides/exchange-api-terms) 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](https://cryptomarkets.tools/tools/barter) declares a Binance execution
module whose source file is a single newline byte, in the published crate as well as the
repository. [GoCryptoTrader](https://cryptomarkets.tools/tools/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](https://cryptomarkets.tools/categories/trading-bots).

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.

## 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

1. [Manual — Precision And Limits, Placing Orders, and the unified order structure](https://raw.githubusercontent.com/ccxt/ccxt/master/wiki/Manual.md) — CCXT, read 2026-09-21
2. [Filters — PRICE_FILTER, LOT_SIZE, MIN_NOTIONAL and NOTIONAL](https://developers.binance.com/docs/binance-spot-api-docs/filters) — Binance, read 2026-09-21
3. [New Order, Spot REST API trading endpoints](https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints) — Binance, read 2026-09-21
4. [Limits — IP limits, weight and unfilled order count](https://developers.binance.com/docs/binance-spot-api-docs/rest-api/limits) — Binance, read 2026-09-21
5. [User Data Streams — the executionReport event](https://developers.binance.com/docs/binance-spot-api-docs/user-data-stream) — Binance, read 2026-09-21
6. [Add Order, Spot REST API](https://docs.kraken.com/api/docs/rest-api/add-order) — Kraken, read 2026-09-21
7. [API key permissions](https://support.kraken.com/articles/360000919966-api-key-permissions) — Kraken, read 2026-09-21
8. [Rate Limit Rules — IP limit and per-UID limits](https://bybit-exchange.github.io/docs/v5/rate-limit) — Bybit, read 2026-09-21

*Last updated 2026-09-21. Corrected in place — an endpoint that moves is a bug here, not a new post.*
