Trading Guide¶
This guide covers common trading patterns using the O2 Python SDK.
See also
For complete method signatures, see O2Client — High-level client.
Order types¶
The O2 Exchange supports six order types, specified via the order_type
parameter of create_order(). Simple types use
the OrderType enum, while Limit and
BoundedMarket use dedicated dataclasses.
from o2_sdk import OrderSide, OrderType, LimitOrder, BoundedMarketOrder
- Spot (default)
A standard limit order that rests on the book if not immediately filled.
await client.create_order("fFUEL/fUSDC", OrderSide.BUY, 0.02, 100.0)
- PostOnly
Guaranteed to be a maker order. Rejected immediately if it would cross the spread and match an existing order.
await client.create_order( "fFUEL/fUSDC", OrderSide.BUY, 0.02, 100.0, order_type=OrderType.POST_ONLY, )
- Market
Executes immediately at the best available price. Fails if the order book is empty.
await client.create_order( "fFUEL/fUSDC", OrderSide.BUY, 0.03, 100.0, order_type=OrderType.MARKET, )
- FillOrKill
Must be filled entirely in a single match, or the entire order is rejected.
await client.create_order( "fFUEL/fUSDC", OrderSide.BUY, 0.03, 100.0, order_type=OrderType.FILL_OR_KILL, )
- Limit
Like Spot, but includes a limit price and timestamp for time-in-force semantics. Use the
LimitOrderclass:import time await client.create_order( "fFUEL/fUSDC", OrderSide.BUY, 0.02, 100.0, order_type=LimitOrder(price=0.025, timestamp=int(time.time())), )
- BoundedMarket
A market order with price bounds — executes at market price but only within the specified range. Use the
BoundedMarketOrderclass:await client.create_order( "fFUEL/fUSDC", OrderSide.BUY, 0.025, 100.0, order_type=BoundedMarketOrder(max_price=0.03, min_price=0.01), )
Cancel and replace¶
To cancel an existing order and place a new one:
# Cancel by order ID
await client.cancel_order(order_id="0xabc...", market="fFUEL/fUSDC")
# Cancel all open orders
await client.cancel_all_orders("fFUEL/fUSDC")
To atomically cancel-and-replace in a single transaction, use
actions_for() + batch_actions():
from o2_sdk import OrderSide, OrderType
result = await client.batch_actions(
actions=[
client.actions_for("fFUEL/fUSDC")
.cancel_order(old_order_id)
.settle_balance()
.create_order(OrderSide.BUY, new_price, new_qty, OrderType.SPOT)
.build()
],
collect_orders=True,
)
Important
MarketActionGroup inputs from actions_for()
accept human-readable numerics and are scaled automatically. If you submit
low-level MarketActions with
CreateOrderAction, price and quantity must
already be pre-scaled on-chain integers.
Settling balances¶
When your orders are filled, the proceeds remain locked in the order book contract until they are settled back to your trading account.
create_order() handles this automatically
when settle_first=True (the default). You can also settle manually:
await client.settle_balance("fFUEL/fUSDC")
Market maker pattern¶
A simple two-sided quoting loop using typed actions:
import asyncio
from o2_sdk import (
CancelOrderAction, CreateOrderAction, SettleBalanceAction,
MarketActions, OrderSide, OrderType,
)
market = await client.get_market("fFUEL/fUSDC")
spread = 0.001
qty = 50.0
active_buy = None
active_sell = None
while True:
# Get current mid price
depth = await client.get_depth("fFUEL/fUSDC")
if depth.best_bid and depth.best_ask:
mid = (float(depth.best_bid.price) + float(depth.best_ask.price)) / 2
mid = market.format_price(int(mid))
else:
await asyncio.sleep(5)
continue
buy_price = mid - spread / 2
sell_price = mid + spread / 2
# Build batch: cancel old + settle + place new
actions = []
if active_buy:
actions.append(CancelOrderAction(order_id=active_buy))
if active_sell:
actions.append(CancelOrderAction(order_id=active_sell))
actions.append(SettleBalanceAction(to=session.trade_account_id))
actions.append(CreateOrderAction(
side=OrderSide.BUY,
price=str(market.scale_price(buy_price)),
quantity=str(market.scale_quantity(qty)),
order_type=OrderType.POST_ONLY,
))
actions.append(CreateOrderAction(
side=OrderSide.SELL,
price=str(market.scale_price(sell_price)),
quantity=str(market.scale_quantity(qty)),
order_type=OrderType.POST_ONLY,
))
result = await client.batch_actions(
[MarketActions(market_id=market.market_id, actions=actions)],
collect_orders=True,
)
if result.orders:
active_buy = result.orders[0].order_id if len(result.orders) > 0 else None
active_sell = result.orders[1].order_id if len(result.orders) > 1 else None
await asyncio.sleep(15)
Order monitoring¶
Query order status:
# All orders for an account
orders = await client.get_orders(account, "fFUEL/fUSDC")
# Open orders only
open_orders = await client.get_orders(account, "fFUEL/fUSDC", is_open=True)
# Single order by ID
order = await client.get_order("fFUEL/fUSDC", order_id="0xabc...")
print(f"Status: {'open' if order.is_open else 'closed'}")
print(f"Filled: {order.quantity_fill} / {order.quantity}")
For real-time order updates, use stream_orders():
async for update in client.stream_orders(account):
for order in update.orders:
print(f"Order {order.order_id}: {'open' if order.is_open else 'closed'}")
Withdrawals¶
Withdraw funds from the trading account:
result = await client.withdraw(owner=owner, asset="fUSDC", amount=10.0)
if result.success:
print(f"Withdrawal tx: {result.tx_id}")
Note
Withdrawals require the owner key (not the session key) and use
personalSign.