# Python

> Install the Python client, place an order in dollars and shares, and choose between the sync, async and streaming clients.

`agara-sdk` is the quickest route from nothing to a working trading script. It
takes dollars and shares, converts them to the wire format for you, and maps
HTTP failures onto a small exception hierarchy. It needs Python 3.10 or newer.

The library concentrates on trading: the orderbook, orders, positions, portfolio
and activity. It does not wrap market or event discovery, so find your
`token_id` through [Markets](/docs/markets) or the public API first.

<Cards>
<Card title="Source" href="https://github.com/Mirai-Tech-Inc/agara-sdks/tree/main/python">
The package, its tests and a complete worked trading script under `examples/`.
</Card>

<Card title="API reference" href="/docs/api-reference">
Per-operation request and response shapes behind each client method.
</Card>
</Cards>

## Quickstart [#quickstart]

<Steps>
<Step>
### Install the SDK [#install-the-sdk]

Not on PyPI yet, so install straight from the public repository. Both extras are
optional: `async` adds an async-native client, `streaming` adds the WebSocket
client.

<Tabs items={["pip","uv"]}>
<Tab value="pip">
```bash
pip install "agara-sdk[async,streaming] @ \
  git+https://github.com/Mirai-Tech-Inc/agara-sdks@main#subdirectory=python"
```
</Tab>

<Tab value="uv">
```bash
uv add "agara-sdk[async,streaming] @ \
  git+https://github.com/Mirai-Tech-Inc/agara-sdks@main#subdirectory=python"
```
</Tab>
</Tabs>

Once the package is published that becomes `pip install
'agara-sdk[async,streaming]'`, and nothing else on this page changes.
</Step>

<Step>
### Create a client [#create-a-client]

Create a token first, following [Authentication](/docs/authentication):

```bash
export AGARA_TOKEN="agt_..."
```

The two clients mirror each other method for method, with the same arguments,
return shapes and exceptions:

<Tabs items={["Sync","Async"]}>
<Tab value="Sync">
```python
import os
from agara_sdk import AgaraClient

client = AgaraClient(
    token=os.environ["AGARA_TOKEN"],
    base_url=os.environ["AGARA_BASE_URL"],
)
```
</Tab>

<Tab value="Async">
```python
import os
from agara_sdk.aio import AsyncAgaraClient

client = AsyncAgaraClient(
    token=os.environ["AGARA_TOKEN"],
    base_url=os.environ["AGARA_BASE_URL"],
)
```
</Tab>
</Tabs>

Both implement the context-manager protocol, so `with` (or `async with`) closes
the underlying connection cleanly.
</Step>

<Step>
### Read a market [#read-a-market]

```python
with AgaraClient(token=os.environ["AGARA_TOKEN"]) as client:
    book = client.get_orderbook(token_id)
    print(f"best bid {book.best_bid} / ask {book.best_ask}, mid {book.mid}")
```

`Orderbook` carries `best_bid`, `best_ask`, `mid` and `spread` so you do not
have to index into the raw levels.
</Step>

<Step>
### Place an order and wait for the outcome [#place-an-order-and-wait-for-the-outcome]

```python
accepted = client.place_order(
    token_id=token_id,
    side="BUY",
    price=0.60,   # dollars per share
    shares=1.0,   # shares
)

final = client.wait_for_terminal(accepted["order_id"], timeout=30.0)
print(final["status"])
```

Acceptance is not settlement. `wait_for_terminal` polls until the order reaches
a terminal status, and that status still needs inspecting: a rejected or
partially filled order is a normal terminal outcome.
</Step>
</Steps>

## Amounts [#amounts]

This is the biggest difference from the other two libraries. You write
`price=0.60` and the client sends `600000`, then converts amounts back on the way
in. `micro_to_float` is available when you hold a raw value from elsewhere:

```python
from agara_sdk import micro_to_float

micro_to_float(600000)  # 0.6
```

Convenience has a cost: a float cannot represent every decimal exactly. For
accounting, or anywhere a rounded cent matters, prefer exact strings or integer
micro units and treat the float conversion as a display step. See
[Conventions](/docs/conventions).

## Pagination [#pagination]

List methods take `limit` and `cursor`, and hand back the cursor for the next
page. Loop until it comes back empty:

```python
cursor = None
while True:
    page = client.list_trades(limit=500, cursor=cursor)
    for trade in page["trades"]:
        print(trade["fill_id"], trade["price_micro"])

    cursor = page["pagination"]["next_cursor"]
    if not cursor:
        break
```

Keep that cursor to resume a scan later rather than replaying from the start.
`list_orders`, `list_open_orders`, `list_positions` and `list_activities` page
the same way.

## Error handling [#error-handling]

Every failure inherits from `AgaraError`, with a subclass per HTTP status, so you
can catch broadly or narrowly:

```python
from agara_sdk import AgaraError, RateLimitedError, RejectedError

try:
    client.place_order(token_id=token_id, side="BUY", price=0.60, shares=1.0)
except RejectedError as error:      # 422, the order was refused on its merits
    print("rejected:", error)
except RateLimitedError as error:   # 429, back off before retrying
    print("slow down:", error)
except AgaraError:                  # anything else the server reported
    raise
```

`BadRequestError` (400), `AuthError` (401), `ForbiddenError` (403),
`NotFoundError` (404), `ConflictError` (409), `RejectedError` (422),
`RateLimitedError` (429) and `ServerError` (5xx) are the full set.

REST calls are never retried for you. A `ServerError` surfaces so you can choose
your own backoff, and a request that fails in transit may still have taken
effect, so read the state back before sending it again. [Errors](/docs/errors)
lists the problem codes and which are worth another attempt.

## Realtime subscriptions [#realtime-subscriptions]

The `streaming` extra adds an async WebSocket client with typed frames, callback
or iterator dispatch, and automatic reconnection. One client covers both the
public market channels and your private account events:

```python
import asyncio, os
from agara_sdk import streaming

async def main():
    client = streaming.AgaraStreamClient(token=os.environ["AGARA_TOKEN"])

    @client.on_trade
    async def _(trade: streaming.Trade):
        print(f"trade {trade.side} {trade.size} @ {trade.price}")

    @client.on_fill
    async def _(fill: streaming.Fill):
        print(f"my fill {fill.fill_id}")

    await client.subscribe([
        streaming.trades(condition_id),
        streaming.account_events(),
    ])
    await client.run()

asyncio.run(main())
```

Reconnection restores your subscriptions but does not replay what you missed
while disconnected. Treat a reconnect as a prompt to take a fresh snapshot.
[Streaming](/docs/streaming) covers the channels and
[Account stream](/docs/account-stream) the private one.

## Next steps [#next-steps]

<Cards>
<Card title="Pre-signed orders" href="/docs/api-traders/pre-signed-orders">
The one-time setup for signing locally, and why it beats server-side signing.
</Card>

<Card title="Account batches" href="/docs/api-traders/account-batches">
Sign your own split, merge or withdrawal, one per batch.
</Card>

<Card title="Streaming" href="/docs/streaming">
Channels, sequence numbers and what a gap obliges you to do.
</Card>

<Card title="Errors" href="/docs/errors">
Problem codes and the failures you must reconcile rather than retry.
</Card>
</Cards>
