Python SDK — tria-trade
A fully async Python port of @tria-sdk/api-trading — one TriaClient, two venues (Hyperliquid + Decibel), normalized shapes, and non-overridable Tria builder-fee attribution. Same method names, same shapes, same TRIA_TRADE_* environment contract as the TypeScript SDK.
Install
pip install tria-trade
Requires Python 3.11+. Every method is async.
Private beta. Like the TypeScript SDK, tria-trade is distributed during
beta with access granted on request — ask your Tria contact for the install
index / credentials. The command above is unchanged once the package is public.
Quickstart
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
),
))
print(await client.ticker(venue="hl", market="BTC"))
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())
An SDK environment minted from Settings → API Keys (see Credentials) or the tria-trade provision CLI works unchanged — build the config from os.environ using the TRIA_TRADE_* variables.
place_order takes a single dict (the venue field lives inside it);
place_batch takes venue plus a list of order dicts. Every other method
takes keyword args with the venue first, e.g.
client.positions(venue="hl", market="BTC"). All amounts are decimal
strings for full precision — never floats.
Configuration
- Name
development- Type
- bool = False
- Description
False→ Tria's production builder;True→ staging builder. Both trade on real mainnet venues — only the builder/treasury address differs.
- Name
hl- Type
- HlConfig | None
- Description
agent_private_key+account_address. Omit to disable Hyperliquid.
- Name
decibel- Type
- DecibelConfig | None
- Description
node_api_key(required) enables public market data; addaptos_owner_addressfor private reads; adddelegate_private_keyto trade;gas_stationenables Geomi-sponsored writes (the delegate then needs no APT).
- Name
telemetry- Type
- TelemetryConfig | None
- Description
Optional.
TelemetryConfig(enabled=False)opts out;client_idattributes usage. Defaults to on (see Telemetry).
Method surface
Identical semantics to the TypeScript surface — see Trading & orders and Market data & streams for per-method detail. The Python signatures:
Writes
- Name
place_order(args)- Type
- -> OrderStatus
- Description
Submit one order (dict with
venue,market,side,size,tif, optionalclientOrderId/price/reduceOnly/ DecibeltpTriggerPrice/slTriggerPrice).clientOrderIdis optional — omit it and the SDK mints one; re-using a live one is rejected. On HL thepriceis rounded to the venue rule before signing, and a fill populatesavgFillPrice.
- Name
place_batch(venue, orders)- Type
- -> list[OrderStatus | TriaError]
- Description
Up to 20 orders per venue action; per-row success or
TriaError.
- Name
cancel_order(venue, order_id= | client_order_id=)- Type
- -> CancelResult
- Description
Idempotent — a second cancel returns
{"status": "not_found"}.
- Name
cancel_all_orders(venue, market=None)- Type
- -> CancelAllResult
- Description
Bulk cancel with a per-order success / failure breakdown.
- Name
modify_order(venue, order_id=/client_order_id=, new_price=, new_size=)- Type
- -> OrderStatus
- Description
HL: native modify (new orderId). Decibel: emulated as cancel + replace.
- Name
attach_tp_sl(venue, market, take_profit=, stop_loss=)- Type
- -> dict
- Description
HL: native reduce-only TP/SL. Decibel: raises (TP/SL is at-placement-time).
- Name
close_position(venue, market, slippage_bps=None)- Type
- -> OrderStatus
- Description
Flatten via a reduce-only IOC at the slippage band (default 2%).
Reads
- Name
order_status(venue, order_id)- Type
- -> OrderStatus
- Description
- Name
open_orders(venue, market=None)- Type
- -> list[OrderStatus]
- Description
- Name
fills(venue, market=, from_=, to=, limit=)- Type
- -> list[Fill]
- Description
- Name
positions(venue, market=None)- Type
- -> list[Position]
- Description
- Name
balances(venue)- Type
- -> list[Balance]
- Description
- Name
margin_state(venue)- Type
- -> MarginState
- Description
- Name
account(venue)- Type
- -> AccountSnapshot
- Description
- Combined balances + margin + leverage + builder + network +
mode. On HL, Unified Accounts are detected and collateral is read fromspotClearinghouseState;modeis"unified"/"legacy_cross".
- Name
markets / ticker / orderbook / candles / recent_trades- Type
- public market data
- Description
- See Market data.
Streams & risk
subscribe_fills, subscribe_positions, subscribe_orders, subscribe_account each take venue= + a callback and return an Unsubscribe; on_connection(callback) reports {venue, status} lifecycle events. scheduled_cancel(venue, deadline_ms=) is HL-native (Decibel raises).
def on_fill(fill): # Fill dict
print(fill["side"], fill["size"], fill["market"], "@", fill["price"])
unsubscribe = await client.subscribe_fills(venue="hl", callback=on_fill)
# … later …
unsubscribe()
await client.close() # release WS + HTTP clients + telemetry sink on shutdown
await client.close() releases every long-lived resource (venue WebSocket(s),
REST/HTTP clients, and the telemetry sink) so the event loop can shut down. It's
idempotent and a no-op for REST-only callers. async with TriaClient(...) as client: calls it for you on exit.
Helpers
from tria_trade import generate_client_order_id, round_to_tick_size
cloid = generate_client_order_id() # 0x + 32 lowercase hex
px = round_to_tick_size("100.123", "0.1") # "100.1" — integer-precise, no float drift
Errors
Every failure raises TriaError with a .code of VENUE_REJECTED,
VENUE_RATE_LIMIT, VENUE_UNREACHABLE, INVALID_ARGUMENT, MISSING_BUILDER, or
CONFIG — never a venue-native error. See Errors & attribution.
from tria_trade import TriaError
try:
await client.place_order({...})
except TriaError as e:
if e.code == "VENUE_RATE_LIMIT":
... # back off; e.retry_after_ms may be set
Telemetry
The SDK emits anonymous usage telemetry (trade actions, latencies, error
categories) to a Tria-owned proxy on the same New Relic schema the apps feed
(platform = "python-sdk"). 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. Wallets are truncated and free-text is scrubbed of key/email
runs; private keys and full addresses are never sent. Opt out with
TRIA_TRADE_TELEMETRY=0 or telemetry=TelemetryConfig(enabled=False). Full
details on Telemetry & privacy.
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. Every action is signed on-chain with the operator's
own master key — it never calls a Tria backend.
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
The package ships runnable examples (place_order, cancel_order,
subscribe_fills, multi_venue) that read credentials from the environment.
For shared concepts and the TypeScript equivalents, see the SDK Examples.