The smallest bot in the first-bot walkthrough is deliberately boring. It connects, receives MATCH_START, returns an empty array on each TICK, and exits after MATCH_END. That is already a valid bot.
Everything interesting can be added behind that boundary: hand-written rules, a state estimator, a small learned policy, or a program written in another language. The engine does not need to know how the decision was made.
Three messages in
MATCH_START describes the match: engine and protocol versions, league, instruments, fee schedule, signal family, limits, compute budget, and the public distribution parameters the engine uses to generate the synthetic world. Those distribution parameters are the bot's prior knowledge before it sees news or a private signal.
TICK is the repeated observation. It contains the current anonymous books, recent attributed trades, public bulletins, your private signal update, your fills and acknowledgements, and your current account state.
MATCH_END provides settlement values, final scores and placements, and your seat statistics.
Three actions out
A bot can submit a new limit order, cancel one acknowledged order, or cancel all of its orders in one or every instrument. Returning an empty list is always allowed.
The engine validates each action. Invalid instruments, prices, quantities, or position exposure receive a reject. An IOC trades what it can immediately and drops the remainder. A GTC may leave quantity resting until it fills or is cancelled.
Client IDs are for your bookkeeping
Every outgoing action includes a client_id chosen by the bot. The acknowledgement echoes it and supplies an engine order_id. Store that order ID if you plan to cancel precisely. Reusing a client ID does not give it order-identity semantics.
Time is part of the strategy
FAST allows 5 ms of measured response time per tick with a 50 ms burst bucket. STANDARD allows 50 ms with a 500 ms burst. The engine records the elapsed response time before advancing the simulated tick. If a bot exceeds its available budget, only that bot's messages receive extra eligibility delay and become stale. The league guide gives the exact sequence, refill, and overrun rules.
This is worth testing with full matches, not one callback benchmark. Parsing, model warm-up, periodic cache rebuilds, and worst-case book states all contribute to the tail.
Messages are not free forever
The rate limit is 10 messages per tick with a burst of 30. The first 500 processed messages in a match are free; later messages cost 0.05 score units each. A quote update should improve the order enough to justify its place in the queue and, eventually, its fee.
Package the program you tested
The SDK's ce-package command creates a reproducible zip with a declared entrypoint. Packaging works now without the engine:
ce-package "python bot.py" --source ./my-bot --out submission.zipAfter the engine source release, append --smoke to run the package in a short local match, check that the bot connects and sends valid messages, and verify the replay.
Using the same entrypoint locally and on the ladder avoids a surprising class of failures: code that worked from a development checkout but depended on an untracked file, working directory, or environment variable.
Failure has a defined match result
If a bot crashes, it stops acting, its resting orders are cancelled, and its position remains until settlement. The match does not turn the crash into a new market rule for every other seat. The same is true when cumulative compute overrun suspends a bot.
The SDK is intentionally thin
ce_sdk parses the protocol and provides helpers such as best_bid, best_ask, and order builders. It does not estimate fair value, decide how much to trade, or reproduce the engine's accounting inside the client.
That leaves one source of truth for the game and keeps the competitive part where it belongs: in the policy behind the callback.
A submission does not need a special framework. It needs a reproducible package, a valid protocol loop, and a policy that can make useful decisions inside its budget.