Metadata-Version: 2.5
Name: tria-trade
Version: 8.6.0b0
Summary: Tria API-Trading SDK — programmatic access to Decibel + Hyperliquid through Tria with builder-fee attribution.
Project-URL: Homepage, https://docs.tria.xyz/trade
Project-URL: Source, https://github.com/TriaHQ/tria-monorepo/tree/main/packages/trade-sdk-py
Author-email: Tria Futures Squad <futures@tria.so>
License: UNLICENSED
Keywords: decibel,hyperliquid,perpetuals,sdk,trading
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: aptos-sdk>=0.11
Requires-Dist: bip-utils>=2.9
Requires-Dist: eth-account>=0.13
Requires-Dist: httpx>=0.27
Requires-Dist: hyperliquid-python-sdk>=0.24
Requires-Dist: msgpack>=1.0
Requires-Dist: websockets>=13
Provides-Extra: dev
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# `tria-trade` (Python)

Python port of `@tria-sdk/api-trading` — programmatic access to **Hyperliquid**
and **Decibel** perpetuals through Tria, with builder-fee attribution. Fully
async, mirrors the TypeScript SDK's surface 1:1.

## Install

```bash
pip install tria-trade
```

Requires Python 3.11+.

## Quickstart

```python
import asyncio
from tria_trade import (
    TriaClient,
    TriaClientConfig,
    HlConfig,
    DecibelConfig,
    generate_client_order_id,
)


async def main():
    client = TriaClient(
        TriaClientConfig(
            hl=HlConfig(agent_private_key="0x…", account_address="0x…"),
            decibel=DecibelConfig(
                delegate_private_key="0x…",  # omit for a read-only Decibel client
                aptos_owner_address="0x…",  # SDK derives the primary subaccount
                node_api_key="<geomi-node-key>",  # required (Decibel rejects anonymous reads)
                gas_station="<geomi-gs-key>",  # optional — sponsored (no-APT) writes
            ),
        )
    )

    # Market data
    print(await client.ticker(venue="hl", market="BTC"))
    print(await client.markets(venue="decibel"))

    # Trade
    status = await client.place_order(
        {
            "venue": "hl",
            "market": "BTC",
            "side": "buy",
            "size": "0.001",
            "price": "50000",
            "tif": "gtc",
            "clientOrderId": generate_client_order_id(),
        }
    )
    await client.cancel_order(venue="hl", order_id=status["orderId"])


asyncio.run(main())
```

Build the config from a generated `.env` with `os.environ` (see `examples/`), or
generate one with the provisioning CLI below.

## Surface

Every method is `async` and routed through non-blocking telemetry. `venue` is
`"hl"` or `"decibel"`.

- **Orders** — `place_order`, `place_batch`, `modify_order`, `cancel_order`,
  `cancel_all_orders`, `order_status`, `open_orders`, `attach_tp_sl`
- **Positions / account** — `positions`, `balances`, `margin_state`, `account`,
  `close_position`
- **Market data** — `markets`, `ticker`, `orderbook`, `candles`, `recent_trades`,
  `fills`
- **Risk** — `scheduled_cancel` (HL native; Decibel raises)
- **Streams** — `subscribe_fills`, `subscribe_positions`, `subscribe_orders`,
  `subscribe_account`, and `on_connection` for lifecycle events
- **Lifecycle** — `await client.close()` releases the venue WebSocket(s), REST/HTTP
  clients, and the telemetry sink so the event loop can shut down. Idempotent and a
  no-op for REST-only callers. `async with TriaClient(...) as client:` calls it for you.

All amounts are decimal **strings** (full precision; never `float`). Errors raise
`TriaError` with a `.code` of `VENUE_REJECTED` / `VENUE_RATE_LIMIT` /
`VENUE_UNREACHABLE` / `INVALID_ARGUMENT` / `MISSING_BUILDER` / `CONFIG`.

`clientOrderId` is **optional** — omit it and the SDK mints one; provide your own to
use it as a dedup key (re-using a *live* one is rejected). On HL, `price` is rounded
to the venue rule (≤5 sig figs and ≤ `6 - szDecimals` decimals) before signing, a
fill populates `OrderStatus["avgFillPrice"]`, and `account()` reads **Unified Account**
collateral from `spotClearinghouseState` (`account()["mode"]` is `"unified"` /
`"legacy_cross"`).

### Tiers

`development=False` (default) uses Tria's production builder; `development=True`
uses the staging builder. **Both trade on real mainnet venues** — only the
builder/treasury address differs.

### Decibel: acknowledged at submit

`place_order` on Decibel returns `state="submitted"` as soon as Aptos accepts the
transaction — it does not wait for the commit, and makes no indexer reads unless
you supplied your own `clientOrderId` (one read, to reject a cloid that is already
live). `orderId` is `""` and `filled` / `remaining` are placeholders in that
state; look the order up later with
`order_status(venue="decibel", client_order_id=...)`.

The commit is verified in the background. If it aborts, the SDK logs a warning on
the `tria_trade.decibel` logger and `order_status` reports `order_not_found`.
Pass `"resolve": "confirmed"` to await the commit (an abort then raises) and read
the resolved state from the indexer. A reader failure after a successful submit
(e.g. a rate limit) returns the `submitted` status rather than an error — the
order is live either way. `modify_order` and `close_position` take the same
`resolve=` keyword.

**Cancel with `market`** (`cancel_order(venue="decibel", client_order_id=...,
market="BTC/USD")`) whenever you know it: the cancel then goes straight to the
chain with no open-orders lookup against the venue's rate limit. Without it the
SDK looks the order up (at most two reads). Indexer reads count against a per-organization rate limit
shared with other Tria traffic, so prefer the default unless you need the outcome
inline.

## Your fee is your Tria fee

Every order carries Tria's builder address and fee in the venue payload (HL: the
action's `builder`; Decibel: `builder_addr` + fee on `place_order`). The standard
builder fee is **5 bps** on both venues, and each order is charged **the same fee
you pay in the Tria app**: your VIP trading-badge discount, your staking-badge
discount and any fee offer on your account apply exactly as in the app, with the
same math.

How the SDK knows your fee, without any extra credential:

- It signs a short request with the trading key it already has (HL agent /
  Decibel delegate) and asks Tria which discounts apply to that account. The
  request carries your account address, a timestamp and that signature — never a
  private key.
- Tria verifies the signature, checks with the venue that the key trades for the
  account, and answers with your discounts.
- The answer is cached for the whole process (shared by every client you create)
  and refreshed in the background.

It never gets in the way of an order:

- If the fee can't be determined (backend unreachable, key not yet recognised,
  account not linked to Tria), the order goes out at the standard fee — never
  higher.
- The first order in a process waits at most ~1 second for the answer; every
  later order uses the cached one.
- An offer stops applying at its end date even between refreshes.

The fee is paid by the venue out of the fill, not added on top.

## Telemetry

The SDK emits anonymous usage telemetry (trade actions, latencies, error
categories) to a Tria-owned proxy so the team can monitor SDK health on the same
"Trade Health" dashboard the apps feed. It is **non-blocking and fire-and-forget**
— a hung or unreachable endpoint is indistinguishable from telemetry-off and
never delays or fails a trade.

- **What's collected**: trade action, venue, outcome, latency, error category/
  severity, size bucket, a **truncated** wallet (`first6…last4`), an optional
  `client_id`. Free-text is scrubbed of hex/base58/email runs.
- **What's never collected**: private keys, full addresses, or order-level PII —
  the redaction pass makes leaking signing material structurally impossible.
- **Always on**: telemetry is part of the SDK's operating contract and has no
  disable switch. `TRIA_TRADE_TELEMETRY_QUIET=1` silences the one-time
  first-run notice (telemetry itself stays on).

## Provisioning CLI

`tria-trade provision` sets up an operator's HL + Decibel accounts (mint the HL
agent, register the Decibel delegate, approve builders, fund the venues) and
writes a ready-to-use `.env`. All actions are signed on-chain with the operator's
own master key — the wizard never calls a Tria backend.

```bash
export HL_MASTER_PRIVATE_KEY=0x…          # or TRIA_MNEMONIC="word1 word2 …"
export APTOS_MASTER_PRIVATE_KEY=0x…        # for Decibel (or derived from TRIA_MNEMONIC)
export DECIBEL_NODE_API_KEY=<geomi-key>

tria-trade provision --dry-run             # print the plan, no chain writes
tria-trade provision --hl-amount 50 --decibel-amount 50 --output .env
```

Flags: `--hl-amount` / `--decibel-amount` (target USDC), `--output`, `--dry-run`,
`--skip-hl` / `--skip-decibel`, `--development`, `--gas-station-key`,
`--force-new-agent` / `--force-new-decibel-delegate`.

## Examples

See [`examples/`](examples/): `place_order.py`, `cancel_order.py`,
`subscribe_fills.py`, `multi_venue.py`. Each reads credentials from the
environment — `source` a generated `.env` first.

## Development

```bash
cd packages/trade-sdk-py
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

pytest                 # unit tests (telemetry forced off)
ruff check src tests && ruff format --check src tests
mypy
```

**Live e2e** (`tests/test_live_e2e.py`) is skipped unless funded credentials are
present at `~/.config/tria/sdk-test.env`. It runs reads + far-below-market
place/cancel round-trips on both venues (real signed mainnet txns; no fills; it
cancels only the orders it placed). Run with
`TRIA_RUN_LIVE_E2E=1 pytest tests/test_live_e2e.py`.

## Reference

The TypeScript SDK (`@tria-sdk/api-trading` at `packages/trade-sdk/`) is the
canonical implementation; this port mirrors it 1:1. The OpenAPI schema at
`packages/trade-sdk/openapi.yaml` is the cross-language source of truth for shapes.
