Trading Bot Example
A complete example of building a trading bot using ZeroQuant SDK with order management, position tracking, and exchange routing.
TypeScript Implementation
import {
// Core
ZeroQuantClient,
// Orders
OrderManager,
MVOManager,
MinimumViableOrder,
OrderStatus,
// Positions
PositionTracker,
// Exchange Routing
ExchangeRouter,
createAgentRouter,
SupportedExchange,
ExchangeCredentials,
// TEE
AMDSEVClient,
} from '@zeroquant/sdk';
// Configuration
const CONFIG = {
// Agent limits (standard)
DAILY_LIMIT_USD: 10000,
PER_TX_LIMIT_USD: 1000,
// TEE elevated limits (3x)
TEE_DAILY_LIMIT_USD: 30000,
TEE_PER_TX_LIMIT_USD: 3000,
// Trading pairs
ALLOWED_PAIRS: ['ETH/USDC', 'BTC/USDC', 'SOL/USDC'],
// Risk management
MAX_POSITION_SIZE_USD: 5000,
STOP_LOSS_PERCENT: 0.02, // 2%
TAKE_PROFIT_PERCENT: 0.05, // 5%
};
class TradingBot {
private orderManager: OrderManager;
private positionTracker: PositionTracker;
private exchangeRouter: ExchangeRouter;
private credentials: ExchangeCredentials;
private dailySpent: number = 0;
constructor(
credentials: ExchangeCredentials,
useTEELimits: boolean = false,
) {
this.credentials = credentials;
// Initialize order manager
this.orderManager = new OrderManager();
// Initialize position tracker
this.positionTracker = new PositionTracker();
// Initialize exchange router with appropriate limits
this.exchangeRouter = createAgentRouter({
dailyLimitUsd: useTEELimits
? CONFIG.TEE_DAILY_LIMIT_USD
: CONFIG.DAILY_LIMIT_USD,
perTxLimitUsd: useTEELimits
? CONFIG.TEE_PER_TX_LIMIT_USD
: CONFIG.PER_TX_LIMIT_USD,
allowedPairs: CONFIG.ALLOWED_PAIRS,
});
// Subscribe to events
this.setupEventHandlers();
}
private setupEventHandlers(): void {
// Order events
this.orderManager.subscribe((event) => {
console.log(`[Order] ${event.type}: ${event.order?.id}`);
if (event.type === 'fill') {
// Update position tracker with fill
this.positionTracker.updateFromFill(event.fill!);
}
});
// Position events
this.positionTracker.subscribe((event) => {
console.log(`[Position] ${event.type}: ${event.symbol}`);
console.log(` Size: ${event.position.size}`);
console.log(` P&L: ${event.position.unrealizedPnl}`);
// Check risk limits
this.checkRiskLimits(event.symbol);
});
// Router events
this.exchangeRouter.subscribe((event) => {
if (event.type === 'order_rejected') {
console.warn(`[Router] Order rejected:`, event.errors);
}
});
}
async placeOrder(
symbol: string,
side: 'buy' | 'sell',
quantity: number,
price?: number,
): Promise<boolean> {
const order: MinimumViableOrder = {
symbol,
side,
originalQuantityBase: quantity,
originalPrice: price ?? null,
timeInForce: price ? 'gtc' : null,
reduceOnly: false,
exchange: 'binance',
baseAsset: symbol.split('/')[0],
quoteAsset: symbol.split('/')[1],
contractTitle: null,
};
// Validate first (dry run)
const validation = await this.exchangeRouter.validateOrder(
order,
'binance' as SupportedExchange,
this.credentials,
);
if (!validation.valid) {
console.error('Order validation failed:', validation.errors);
return false;
}
// Route to exchange
const result = await this.exchangeRouter.routeOrder({
order,
exchange: 'binance' as SupportedExchange,
credentials: this.credentials,
validationRules: {
dailySpentUsd: this.dailySpent,
},
});
if (result.success) {
// Track order
this.orderManager.trackOrder({
...order,
id: result.exchangeResponse!.exchangeOrderId!,
status: OrderStatus.OPEN,
createdAt: Date.now(),
});
// Update daily spent
if (price && quantity) {
this.dailySpent += price * quantity;
}
console.log(`Order placed: ${result.exchangeResponse!.exchangeOrderId}`);
return true;
} else {
console.error('Order failed:', result.error);
return false;
}
}
async cancelOrder(orderId: string, symbol: string): Promise<boolean> {
const result = await this.exchangeRouter.cancelOrder({
orderId,
symbol,
exchange: 'binance' as SupportedExchange,
credentials: this.credentials,
});
if (result.success) {
this.orderManager.updateOrderStatus(orderId, OrderStatus.CANCELLED);
console.log(`Order cancelled: ${orderId}`);
return true;
}
return false;
}
updatePrice(symbol: string, price: number): void {
this.positionTracker.updatePrice(symbol, price);
}
// Track symbols currently being checked to prevent re-entrancy
private _checkingRisk = new Set<string>();
private checkRiskLimits(symbol: string): void {
// Re-entrancy guard - prevent recursive calls from order fills
if (this._checkingRisk.has(symbol)) {
return;
}
const position = this.positionTracker.getPosition(symbol);
if (!position || position.size === 0) return;
// Guard against division by zero
const denominator = position.entryPrice * position.size;
if (denominator === 0) {
console.warn(`[Risk] Cannot calculate PnL - zero entry price or size for ${symbol}`);
return;
}
const pnlPercent = position.unrealizedPnl / denominator;
try {
this._checkingRisk.add(symbol);
// Stop loss check
if (pnlPercent <= -CONFIG.STOP_LOSS_PERCENT) {
console.warn(`[Risk] Stop loss triggered for ${symbol}`);
this.placeOrder(
symbol,
position.side === 'long' ? 'sell' : 'buy',
position.size,
);
}
// Take profit check
if (pnlPercent >= CONFIG.TAKE_PROFIT_PERCENT) {
console.log(`[Risk] Take profit triggered for ${symbol}`);
this.placeOrder(
symbol,
position.side === 'long' ? 'sell' : 'buy',
position.size,
);
}
} finally {
this._checkingRisk.delete(symbol);
}
}
getStats(): {
positions: ReturnType<PositionTracker['getSnapshot']>;
orders: ReturnType<OrderManager['getStats']>;
router: ReturnType<ExchangeRouter['getStats']>;
} {
return {
positions: this.positionTracker.getSnapshot(),
orders: this.orderManager.getStats(),
router: this.exchangeRouter.getStats(),
};
}
}
// Usage example
async function main() {
const bot = new TradingBot({
apiKey: process.env.BINANCE_API_KEY!,
apiSecret: process.env.BINANCE_API_SECRET!,
});
// Place a limit buy order
await bot.placeOrder('ETH/USDC', 'buy', 0.5, 1950);
// Place a market sell order
await bot.placeOrder('ETH/USDC', 'sell', 0.25);
// Simulate price update
bot.updatePrice('ETH/USDC', 2000);
// Get stats
const stats = bot.getStats();
console.log('Bot stats:', stats);
}
main().catch(console.error);
Python Implementation
import asyncio
import os
from dataclasses import dataclass
from typing import Optional
from zeroquant import (
# Orders
OrderManager,
MVOManager,
MinimumViableOrder,
OrderStatus,
OrderFill,
# Positions
PositionTracker,
PositionSide,
# Exchange Routing
ExchangeRouter,
create_agent_router,
SupportedExchange,
ExchangeCredentials,
RouteOrderRequest,
ValidationRules,
)
@dataclass
class Config:
"""Trading bot configuration."""
# Agent limits (standard)
DAILY_LIMIT_USD: float = 10000
PER_TX_LIMIT_USD: float = 1000
# TEE elevated limits (3x)
TEE_DAILY_LIMIT_USD: float = 30000
TEE_PER_TX_LIMIT_USD: float = 3000
# Trading pairs
ALLOWED_PAIRS: list = None
# Risk management
MAX_POSITION_SIZE_USD: float = 5000
STOP_LOSS_PERCENT: float = 0.02 # 2%
TAKE_PROFIT_PERCENT: float = 0.05 # 5%
def __post_init__(self):
if self.ALLOWED_PAIRS is None:
self.ALLOWED_PAIRS = ['ETH/USDC', 'BTC/USDC', 'SOL/USDC']
class TradingBot:
"""
Trading bot with order management, position tracking, and exchange routing.
"""
def __init__(
self,
credentials: ExchangeCredentials,
use_tee_limits: bool = False,
config: Optional[Config] = None,
):
self.config = config or Config()
self.credentials = credentials
self.daily_spent = 0.0
# Initialize components
self.order_manager = OrderManager()
self.position_tracker = PositionTracker()
# Initialize exchange router with limits
daily_limit = (
self.config.TEE_DAILY_LIMIT_USD if use_tee_limits
else self.config.DAILY_LIMIT_USD
)
per_tx_limit = (
self.config.TEE_PER_TX_LIMIT_USD if use_tee_limits
else self.config.PER_TX_LIMIT_USD
)
self.exchange_router = create_agent_router(
daily_limit_usd=daily_limit,
per_tx_limit_usd=per_tx_limit,
allowed_pairs=self.config.ALLOWED_PAIRS,
)
# Setup event handlers
self._setup_event_handlers()
def _setup_event_handlers(self):
"""Setup event subscriptions."""
# Order events
def on_order_event(event):
print(f"[Order] {event['type']}: {event.get('order', {}).get('id')}")
if event['type'] == 'fill' and 'fill' in event:
fill_data = event['fill']
fill = OrderFill(
fill_id=fill_data.get('fill_id', ''),
order_id=fill_data.get('order_id', ''),
symbol=fill_data['symbol'],
side=fill_data['side'],
price=fill_data['price'],
quantity=fill_data['quantity'],
fee=fill_data.get('fee', 0),
timestamp=fill_data.get('timestamp', 0),
)
self.position_tracker.update_from_fill(fill)
self.order_manager.subscribe(on_order_event)
# Position events
def on_position_event(update):
print(f"[Position] {update.type}: {update.symbol}")
print(f" Size: {update.position.size}")
print(f" P&L: {update.position.unrealized_pnl}")
# Check risk limits
asyncio.create_task(self._check_risk_limits(update.symbol))
self.position_tracker.subscribe(on_position_event)
# Router events
def on_router_event(event):
if event.get('type') == 'order_rejected':
print(f"[Router] Order rejected: {event.get('errors')}")
self.exchange_router.subscribe(on_router_event)
async def place_order(
self,
symbol: str,
side: str,
quantity: float,
price: Optional[float] = None,
) -> bool:
"""
Place an order through the exchange router.
Args:
symbol: Trading pair (e.g., 'ETH/USDC')
side: 'buy' or 'sell'
quantity: Order quantity in base asset
price: Limit price (None for market order)
Returns:
True if order was placed successfully
"""
base_asset, quote_asset = symbol.split('/')
order = MinimumViableOrder(
symbol=symbol,
side=side,
original_quantity_base=quantity,
original_price=price,
time_in_force='gtc' if price else None,
reduce_only=False,
exchange='binance',
base_asset=base_asset,
quote_asset=quote_asset,
)
# Validate first (dry run)
validation = await self.exchange_router.validate_order(
order,
SupportedExchange.BINANCE,
self.credentials,
)
if not validation.valid:
print(f"Order validation failed: {validation.errors}")
return False
# Route to exchange
request = RouteOrderRequest(
order=order,
exchange=SupportedExchange.BINANCE,
credentials=self.credentials,
validation_rules=ValidationRules(daily_spent_usd=self.daily_spent),
)
result = await self.exchange_router.route_order(request)
if result.success:
# Track order
self.order_manager.track_order({
'id': result.exchange_response.exchange_order_id,
'symbol': symbol,
'side': side,
'quantity': quantity,
'price': price,
'status': 'open',
'created_at': result.timestamp,
})
# Update daily spent
if price and quantity:
self.daily_spent += price * quantity
print(f"Order placed: {result.exchange_response.exchange_order_id}")
return True
else:
print(f"Order failed: {result.error}")
return False
async def cancel_order(self, order_id: str, symbol: str) -> bool:
"""Cancel an order."""
from zeroquant import CancelOrderRequest
request = CancelOrderRequest(
order_id=order_id,
symbol=symbol,
exchange=SupportedExchange.BINANCE,
credentials=self.credentials,
)
result = await self.exchange_router.cancel_order(request)
if result.success:
self.order_manager.update_order_status(order_id, 'cancelled')
print(f"Order cancelled: {order_id}")
return True
return False
def update_price(self, symbol: str, price: float):
"""Update mark price for a symbol."""
self.position_tracker.update_price(symbol, price)
# Re-entrancy guard to prevent recursive risk checks
_checking_risk: set = set()
async def _check_risk_limits(self, symbol: str):
"""Check and enforce risk limits."""
# Prevent re-entrancy from order fills triggering more risk checks
if symbol in self._checking_risk:
return
position = self.position_tracker.get_position(symbol)
if position is None or position.size == 0:
return
# Guard against division by zero
denominator = position.entry_price * position.size
if denominator == 0:
print(f"[Risk] Cannot calculate PnL - zero entry price or size for {symbol}")
return
pnl_percent = position.unrealized_pnl / denominator
try:
self._checking_risk.add(symbol)
# Stop loss check
if pnl_percent <= -self.config.STOP_LOSS_PERCENT:
print(f"[Risk] Stop loss triggered for {symbol}")
close_side = 'sell' if position.side == PositionSide.LONG else 'buy'
await self.place_order(symbol, close_side, position.size)
# Take profit check
if pnl_percent >= self.config.TAKE_PROFIT_PERCENT:
print(f"[Risk] Take profit triggered for {symbol}")
close_side = 'sell' if position.side == PositionSide.LONG else 'buy'
await self.place_order(symbol, close_side, position.size)
finally:
self._checking_risk.discard(symbol)
def get_stats(self) -> dict:
"""Get bot statistics."""
return {
'positions': self.position_tracker.get_snapshot().model_dump(),
'orders': self.order_manager.get_stats(),
'router': self.exchange_router.get_stats().model_dump(),
}
async def main():
"""Run the trading bot."""
# Initialize bot
credentials = ExchangeCredentials(
api_key=os.environ.get('BINANCE_API_KEY', ''),
api_secret=os.environ.get('BINANCE_API_SECRET', ''),
)
bot = TradingBot(credentials, use_tee_limits=False)
# Place a limit buy order
await bot.place_order('ETH/USDC', 'buy', 0.5, 1950.0)
# Place a market sell order
await bot.place_order('ETH/USDC', 'sell', 0.25)
# Simulate price update
bot.update_price('ETH/USDC', 2000.0)
# Get stats
stats = bot.get_stats()
print('Bot stats:', stats)
# Cleanup
await bot.exchange_router.close()
if __name__ == '__main__':
asyncio.run(main())
Key Concepts
Order Flow
┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐
│ Strategy │────▶│ Validation │────▶│ Exchange │────▶│ Position │
│ Decision │ │ (Router) │ │ Adapter │ │ Tracker │
└─────────────┘ └──────────────┘ └──────────────┘ └─────────────┘
│ │
┌──────┴──────┐ ┌──────┴──────┐
│ Reject │ │ Order │
│ (if invalid)│ │ Manager │
└─────────────┘ └─────────────┘
Component Responsibilities
| Component | Responsibility |
|---|---|
OrderManager | Track orders, manage lifecycle, emit events |
PositionTracker | Calculate P&L, track positions, emit updates |
ExchangeRouter | Validate orders, route to exchanges |
ExchangeAdapter | Convert formats, communicate with exchange API |
Risk Management
The bot implements:
- Daily limits - Prevents exceeding daily trading volume
- Per-transaction limits - Caps individual order sizes
- Stop loss - Automatic position closing at loss threshold
- Take profit - Automatic position closing at profit threshold
- Allowed pairs - Restricts trading to approved assets
TEE Integration
For elevated limits with TEE attestation:
// Check TEE attestation
const teeClient = new AMDSEVClient(w3, verifierAddr, permissionAddr);
const hasValidTEE = await teeClient.hasValidAttestation(agentAddress);
// Create bot with appropriate limits
const bot = new TradingBot(credentials, hasValidTEE);