# Trading

> Place single and batch orders, cancel one, many or all, and read order state.

Source: https://docs.stxapp.io/sdks/python/trading/

Placing and cancelling needs a `read_write` key. The examples run on the US demo exchange with 1-cent buy orders that will not fill. How orders behave on the exchange is covered in [Order types](/concepts/order-types/) and [Risk controls](/risk-controls/); this page shows the SDK calls.

The examples use a market that is open, accepting orders (`trading` is `True`), and whose event has not started:

```python
from stx import STX

with STX() as client:
    market = next(
        m
        for m in client.iter_markets(
            status="open", trading=True, sort_by="event_start", sort_direction="desc"
        )
        if m.event_status == "scheduled"
    )
    print(market.market_id, market.symbol, market.max_price)
```

## Place an order

```python
from stx import STX

with STX() as client:
    market = next(
        m
        for m in client.iter_markets(
            status="open", trading=True, sort_by="event_start", sort_direction="desc"
        )
        if m.event_status == "scheduled"
    )
    order = client.place_order(
        market.market_id, "buy", "limit", price="0.01", quantity="1", client_order_id="quickstart-1"
    )
    print(order.id, order.status, order.price, order.quantity, order.client_order_id)
    client.cancel_order(order.id)
```

| Argument | Meaning |
|---|---|
| `market_id` | The market |
| `action` | `"buy"` or `"sell"` |
| `order_type` | `"limit"` or `"market"` |
| `price` | Dollars as a string, e.g. `"0.56"`. Required for a limit order, omitted for a market order |
| `quantity` | Contracts as a string, e.g. `"2"` |
| `client_order_id` | Your own id, echoed back and filterable |
| `expiration`, `expiration_time` | See [Expiration](#expiration) |
| `cancel_on_disconnect` | See [Cancel-on-disconnect](#cancel-on-disconnect) |
| `device_id` | Optional device label |

`price` and `quantity` must be strings (or `decimal.Decimal`). A float raises `TypeError` before anything is sent, because a float cannot carry an exact decimal. The exchange validates everything else: a price off the tick, a price at or above `max_price`, or a fractional quantity comes back as `STXRejectedException` (422) or `STXValidationException` (400) carrying the exchange's message:

```python
from stx import STX, STXAPIException

with STX() as client:
    market = next(
        m
        for m in client.iter_markets(
            status="open", trading=True, sort_by="event_start", sort_direction="desc"
        )
        if m.event_status == "scheduled"
    )
    try:
        client.place_order(market.market_id, "buy", "limit", price="0.01", quantity="1.5")
    except STXAPIException as exc:
        print(exc.status_code, exc)
```

The returned `Order` shows the state at acceptance (`accepted`, `open`, `filled`...). Follow it on the [`orders` channel](/sdks/python/websockets#orders) or with `client.order(order.id)`.

## Place several orders

`place_orders()` sends one `POST /api/v1/orders/batched`. Each order is a dict with the `place_order` fields; the result has one entry per order, in order, holding either `order` or `errors`:

```python
from stx import STX

with STX() as client:
    market = next(
        m
        for m in client.iter_markets(
            status="open", trading=True, sort_by="event_start", sort_direction="desc"
        )
        if m.event_status == "scheduled"
    )
    results = client.place_orders(
        [
            {"market_id": market.market_id, "action": "buy", "order_type": "limit",
             "price": "0.01", "quantity": "1"},
            {"market_id": market.market_id, "action": "buy", "order_type": "limit",
             "price": "0.02", "quantity": "1"},
        ]
    )
    for r in results:
        print("placed" if r.ok else "rejected", r.order.id if r.ok else r.errors)

    cancelled = client.cancel_orders([r.order.id for r in results if r.ok])
    print("cancelled", len(cancelled))
```

One rejected order does not stop the others.

## Cancel

```python
from stx import STX

with STX() as client:
    market = next(
        m
        for m in client.iter_markets(
            status="open", trading=True, sort_by="event_start", sort_direction="desc"
        )
        if m.event_status == "scheduled"
    )
    a = client.place_order(market.market_id, "buy", "limit", price="0.01", quantity="1")
    b = client.place_order(market.market_id, "buy", "limit", price="0.01", quantity="1")
    c = client.place_order(market.market_id, "buy", "limit", price="0.01", quantity="1")

    print(client.cancel_order(a.id))                  # one
    print(client.cancel_orders([b.id, c.id]))         # several by id
    print(client.cancel_all_orders())                 # everything left on the account
```

Each returns `Cancellation` objects with `order_id` and `status`. A cancel is a request: an order can fill between sending the cancel and the exchange processing it, so reconcile against fills rather than assuming you are flat.

## Read orders

```python
from stx import STX

with STX() as client:
    for order in client.orders(status=["open", "delayed"], limit=20):
        print(order.id, order.market_id, order.action, order.price, order.filled, "/", order.quantity)

    recent = client.orders(limit=1)
    if recent.items:
        print(client.order(recent[0].id).status)
```

`orders()` filters on `order_ids`, `client_order_ids`, `market_ids` and `status` (one or a list of `created`, `requested`, `accepted`, `delayed`, `open`, `filled`, `rejected`, `cancelled`, `partially_cancelled`).

## Avoid duplicate orders

A `POST` is never retried after a server error or a dropped connection: the order may already be on the book. If that happens, look it up by `client_order_id` before placing again:

```python
import uuid

from stx import STX, STXServerException, STXTransportException

with STX() as client:
    market = next(
        m
        for m in client.iter_markets(
            status="open", trading=True, sort_by="event_start", sort_direction="desc"
        )
        if m.event_status == "scheduled"
    )
    coid = str(uuid.uuid4())
    try:
        order = client.place_order(
            market.market_id, "buy", "limit", price="0.01", quantity="1", client_order_id=coid
        )
    except (STXServerException, STXTransportException):
        found = client.orders(client_order_ids=[coid])
        order = found[0] if found.items else None
    print(order.id if order else "not placed")
    if order:
        client.cancel_order(order.id)
```

## Expiration

`expiration="good_till_start"` pulls the order when the event starts. `expiration="good_till_time"` pulls it at `expiration_time`, in Unix **microseconds**:

```python
import time

from stx import STX

with STX() as client:
    market = next(
        m
        for m in client.iter_markets(
            status="open", trading=True, sort_by="event_start", sort_direction="desc"
        )
        if m.event_status == "scheduled"
    )
    in_one_hour = int((time.time() + 3600) * 1_000_000)
    order = client.place_order(
        market.market_id, "buy", "limit", price="0.01", quantity="1",
        expiration="good_till_time", expiration_time=in_one_hour,
    )
    print(order.id, order.status)
    client.cancel_order(order.id)
```

## Cancel-on-disconnect

Cancel-on-disconnect takes two halves: join the `orders` channel with it armed, and place orders with `cancel_on_disconnect=True`. While the socket is up, the SDK pings the channel at 60% of the timeout the server granted, so your orders stay on the book. If your process dies, the exchange cancels them. The timeout limits and the grace period are on [Risk controls](/risk-controls/).

```python
import asyncio

from stx import AsyncSTX

async def main():
    async with AsyncSTX() as client:
        market = None
        async for m in client.iter_markets(
            status="open", trading=True, sort_by="event_start", sort_direction="desc"
        ):
            if m.event_status == "scheduled":
                market = m
                break
        async with client.websocket() as ws:
            orders = await ws.orders(cancel_on_disconnect=True, ping_timeout=5000)
            print("armed, server timeout (ms):", orders.reply["ping_timeout"])
            order = await client.place_order(
                market.market_id, "buy", "limit", price="0.01", quantity="1",
                cancel_on_disconnect=True,
            )
            await asyncio.sleep(8)  # longer than the timeout: the pings keep it alive
            print((await client.order(order.id)).status)
            await client.cancel_order(order.id)

asyncio.run(main())
```
