Quant500 Academy

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:

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.

The rest of Quant500

What you are learning, applied to the index every day:

The model portfolio, weekly in your inbox

Every Saturday, its week in one minute: how it went, the best and the worst, and which insiders bought with their own money. Plus, the day the model is rebuilt, which stocks come in and which go out. A simulation, not advice. At most one email a week, no ads, no account; one-click unsubscribe. Made by one person: Pol Gracia.

A model portfolio with simulated results, not a personalized recommendation. Your email is used for this only: privacy.

Behind the Academy and the rest of Quant500 there is one person: Pol Gracia Serrahima.