Trading API & SDK · Placing Orders · 4/8
The Exchange Class
Authentication Setup
The Exchange class handles all authenticated (write) operations. It requires a wallet for signing.
Testnet only. Every sample in this lesson places, cancels or changes real orders. Run them against
TESTNET_API_URLwith a testnet API wallet.
import os
import eth_account
from hyperliquid.exchange import Exchange
from hyperliquid.info import Info
from hyperliquid.utils import constants
# Load the API (agent) wallet key from an environment variable
wallet = eth_account.Account.from_key(os.environ["HL_API_WALLET_KEY"])
account_address = os.environ["HL_ACCOUNT_ADDRESS"] # your main account
exchange = Exchange(
wallet,
base_url=constants.TESTNET_API_URL,
account_address=account_address,
)
info = Info(constants.TESTNET_API_URL, skip_ws=True)
Two kinds of signed actions
The SDK signs with one of two schemes (signing):
- L1 actions (orders, cancels, modifies, leverage changes) are signed with
sign_l1_action. An approved API wallet can sign these for your account. - User-signed actions (USDC transfers, withdrawals and similar) are signed with
sign_user_signed_actionby the account's own key.
Each request carries a nonce; the SDK uses the current time in milliseconds. Nonces are tracked per signer, so give each bot process its own API wallet (nonces).
Placing a Market Order
The API has no separate market order type. market_open reads the mid price, moves it by slippage (default 5%), and sends an aggressive IOC limit order:
# Buy 0.01 BTC at market price (testnet)
result = exchange.market_open(
name="BTC",
is_buy=True,
sz=0.01, # Size in base asset (BTC)
slippage=0.01 # Limit price set 1% through the mid
)
print(result)
# {'status': 'ok', 'response': {'type': 'order', 'data': {'statuses': [{'filled': {'totalSz': '0.01', 'avgPx': '...', 'oid': ...}}]}}}
Orders must be worth at least $10 notional.
Placing a Limit Order
# Place a limit buy for 0.01 BTC at $90,000 (testnet)
result = exchange.order(
name="BTC",
is_buy=True,
sz=0.01,
limit_px=90000.0,
order_type={"limit": {"tif": "Gtc"}} # Good-til-cancelled
)
# The response includes the order ID (oid)
if result["status"] == "ok":
statuses = result["response"]["data"]["statuses"]
for s in statuses:
if "resting" in s:
print(f"Order placed: oid={s['resting']['oid']}")
Time-in-Force Options
The order_type parameter controls how long an order stays active:
| TiF | Meaning | Use Case |
|---|---|---|
Gtc |
Good-til-cancelled | Default for limit orders |
Ioc |
Immediate-or-cancel | Fill now or cancel |
Alo |
Add-liquidity-only | Post-only: canceled instead of matching |
The other order_type form is {"trigger": {"triggerPx": ..., "isMarket": bool, "tpsl": "tp" | "sl"}} for take-profit and stop-loss orders.
# Post-only order (canceled if it would take liquidity)
exchange.order(
name="ETH",
is_buy=True,
sz=0.1,
limit_px=3000.0,
order_type={"limit": {"tif": "Alo"}}
)
# IOC order (fill immediately or cancel)
exchange.order(
name="ETH",
is_buy=True,
sz=0.1,
limit_px=3100.0,
order_type={"limit": {"tif": "Ioc"}}
)
Cancelling Orders
# Cancel a specific order
exchange.cancel(name="BTC", oid=12345)
# Cancel all open orders for a coin (query the main account, not the API wallet)
open_orders = info.open_orders(account_address)
for order in open_orders:
if order["coin"] == "BTC":
exchange.cancel(name="BTC", oid=order["oid"])
Setting Leverage
# Set leverage for BTC to 10x cross margin
exchange.update_leverage(
leverage=10,
name="BTC",
is_cross=True
)
# Set isolated margin at 5x
exchange.update_leverage(
leverage=5,
name="ETH",
is_cross=False
)