Overview
Most trading bot tutorials show you how to place an order in a loop. That is the easy 10% of the problem, and it produces bots that work in testing and misbehave the first time something interrupts them. The hard part is state. Your bot restarts, the process was killed mid-execution, a request timed out and you do not know if it landed, or a TWAP you started an hour ago is still running somewhere. A bot that assumes it starts flat will happily double its exposure every time it comes back up. This guide builds a bot around that problem. The signal is left to you, because that is your edge and nobody can give it to you. Everything around it, the part that determines whether the bot is safe to leave running, is what we cover.Read time: about 16 minutes. Python and REST API experience assumed. Read Place an order first if you have not placed one yet.
TL;DR
- Reconcile before you trade. Read positions, open orders, and running strategies. Skipping the third is the classic bug.
- Trade the delta, not the target. Compare what you want against what you already have plus what is already working.
- One idempotency key per intended order, reused across retries, so a timeout cannot become a duplicate.
- Managed strategies (
TWAP,ICEBERG,PEGGED,TRAILING_STOP, pre-triggerSTOP_LOSS) do not appear in open orders. cancel-allclears CLOB orders only. Strategies keep running.- Parse every number with
Decimal. They arrive as strings for a reason.
What you will do
- Read complete account state across three endpoints
- Compute a position delta that accounts for orders already working
- Place orders with retry-safe idempotency keys
- Choose an order type based on size rather than habit
- Run a dry cycle that prints intended trades without placing them
- Add a main loop that respects your rate limit and backs off on
429
What you will need
Knowledge- Python 3.9 or later, and comfort with REST APIs
- A trading signal of your own. This guide deliberately does not provide one.
- A Bravado API key with
trade.read,trade.execute, andtrade.cancel pip install requests- USDC collateral, though the dry-run mode needs none
Architecture
Four steps, in this order, every cycle:1
Reconcile
Read what you hold and what is already working. Never assume.
2
Evaluate
Decide the target position. Your signal lives here.
3
Diff
Target minus current equals the trade. Trade only the difference.
4
Execute
Place with an idempotency key, sized by an order type that suits the size.
Set up the client
DRY_RUN defaults to True on purpose. The expensive mistake should require a deliberate act, not an oversight. Every example below is safe to run as written.Reconcile: read complete state
Three calls. The third is the one people miss.PENDING counts as committed. A strategy waiting on its entry conditions has not filled, but it will, and ordering more in the meantime means you get both.
Diff: trade the difference
MIN_SHARES guard matters more than it looks. Without it, a bot whose target is 2 shares away from actual will place a 2-share order, get rejected by the venue, and retry on the next cycle, forever, burning rate limit the entire time.
Choose an order type by size
Most bots hard-codeMARKET and pay for it on thin books. Choose deliberately:
The critical property of
TWAP for a bot specifically: it runs on Bravado’s side. If your process dies mid-execution, the strategy keeps going. A self-hosted scheduler dies with you, halfway through a position.
Execute
Run a dry cycle
DRY_RUN = True:
The main loop
Going live
FlipDRY_RUN to False and start small. Specifically:
1
One symbol first
Run against a single market so any surprise is contained and legible.
2
Watch a restart deliberately
Kill the process mid-cycle and start it again. Confirm the next plan accounts for what is already working rather than re-ordering it.
3
Then widen
Only once a restart is boring should you point it at more markets.
Wrapping up
The bot is four steps, and three of them are bookkeeping. Reconcile, diff, execute, repeat. The signal, the interesting part, plugs into one function. That ratio is deliberate. Most bots that lose money do it through operational failures rather than a bad signal: duplicated positions after a restart, orders retried into existence, a TWAP that nobody was counting. Getting the bookkeeping right is what lets the signal be the thing that matters.Frequently asked questions
Why reconcile every cycle rather than just at startup?
Why reconcile every cycle rather than just at startup?
Your bot is not the only thing changing the account. Strategies fill between cycles, stops trigger, and someone may trade manually. Reading fresh state each pass makes all of that harmless instead of requiring you to anticipate it.
Why does PENDING count as committed size?
Why does PENDING count as committed size?
A
PENDING strategy has been accepted and is waiting on its entry conditions. It has not filled yet, but it will. Treating it as absent means ordering the same exposure twice and getting both.My bot cancelled everything but a TWAP kept running.
My bot cancelled everything but a TWAP kept running.
POST /v2/trade/orders/cancel-all clears CLOB orders only. Managed strategies must be cancelled individually with DELETE /v2/trade/strategies/{id}. Iterate over /v2/trade/strategies and cancel each ACTIVE or PENDING record.Why is my small order rejected repeatedly?
Why is my small order rejected repeatedly?
Resting orders need at least 5 shares and market orders need $1 notional. Without a minimum-size guard, a bot will retry a sub-minimum order every cycle and consume its rate limit doing so.
Should I use floats for sizes if I round them anyway?
Should I use floats for sizes if I round them anyway?
No. Values arrive as strings to preserve precision, and float arithmetic accumulates error that eventually produces a size the venue rejects.
Decimal costs nothing here.How do I know if an order actually filled?
How do I know if an order actually filled?
Check the response body rather than the status code alone. A
200 can carry warnings[] describing a partial fill or a clamped price, and bracket legs report their own failures in brackets.*.error.Resources
- Place an order, all eight order types in detail
- Execute a large position, TWAP and Iceberg tuning
- Track order and strategy state, where each order type lives
- Safe retries, idempotency keys in depth
- Trade API reference, full field documentation
- Rate limits, quotas and backoff