4. Placing a limit order on Coinbase Pro
Level 13: Connecting to Real Brokers
We now have authentication. Let us place a limit order. A limit order lets you specify the price at which you want to buy or sell an asset, giving you more control over the execution price.
That sentence hides the central trade-off of execution: a market order guarantees execution but not price; a limit order guarantees price but not execution. If you place a buy limit below the market and the price never falls that far, your order sits in the book forever without filling. For a systematic strategy that is no small detail: it means your real position may not be the one the model assumes.
The endpoint for creating orders is POST https://api.pro.coinbase.com/orders and the request body describes the order:
API_URL = 'https://api.pro.coinbase.com/orders'
body = {
'size': '0.0005', # amount of BTC to buy
'price': '10000.00', # limit price at which you want to buy
'side': 'buy', # buy or sell
'product_id': 'BTC-USD', # the pair
'type': 'limit' # limit order
}
body_json = json.dumps(body)
response = requests.post(API_URL, headers=headers, data=body_json, timeout=10)
if response.ok:
print('Order sent:', response.json())
else:
print('Failed to send the order:', response.status_code, response.text)Notice two details in the code above. First: size and price are sent as strings, not as floating point numbers. That is deliberate. A float does not represent many decimal values exactly, and a silent rounding error in a monetary amount is a mistake that can cost money; that is why serious financial APIs work with strings or exact decimals. Second: the timeout in the call. Without it, a request can hang indefinitely, and in module 6 you will see why that is dangerous.
Sent is not filled
A successful response from the server means the order has been *accepted* and placed in the book, not that it has been executed. The response carries an order identifier and a status, normally pending or open. To find out whether it filled you have to query it afterwards, with GET /orders/:order-id, or receive it over the WebSocket fills channel. Confusing acceptance with execution is one of the most frequent reasons a live system believes it holds a position it does not have.
The rest of the API follows the same REST logic. These are the endpoints you will use most:
GET /productsandGET /products/:product-id/ticker: available instruments and last price.GET /products/:product-id/book: the order book.GET /products/:product-id/candles: historical candles.GET /accountsandGET /accounts/:account-id/ledger: balances and movements.POST /orders: create an order.GET /ordersandGET /orders/:order-id: open orders and the status of a specific one.DELETE /orders/:order-id: cancel an order.GET /fills: your real fills, with the price and commission of each one.GET /time: the server's time, useful for detecting clock drift.
Wherever you see :product-id, :order-id or :account-id, substitute the real value (for example BTC-USD).
Two more options are worth knowing from the start. post_only rejects the order if it would execute immediately against the book, which guarantees that you are always a *maker* and pay the lower fee. And time in force: GTC keeps the order alive until you cancel it, IOC executes what it can instantly and cancels the rest, and FOK demands an immediate complete fill or nothing. Choosing this option badly completely changes the behavior of the strategy.