trade_preview
Preview trade details before execution to review estimated fill price, fees, margin impact, and risk checks for perpetual futures trading.
Instructions
Preview a trade WITHOUT executing. Returns estimated fill price, fees, margin impact, and risk checks. ALWAYS call this before trade_execute and show the result to the user for confirmation.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| exchange | Yes | Exchange: pacifica, hyperliquid, or lighter | |
| symbol | Yes | Trading symbol (e.g., BTC, ETH, SOL) | |
| side | Yes | Order side | |
| size | Yes | Order size (base currency units, e.g., '0.1' for 0.1 BTC) | |
| orderType | No | Order type | market |
| price | No | Limit price (required for limit orders) |
Implementation Reference
- src/mcp-server.ts:1385-1451 (handler)The `trade_preview` MCP tool implementation in `src/mcp-server.ts`. It fetches market data, validates the trade, and returns a preview object.
server.tool( "trade_preview", "Preview a trade WITHOUT executing. Returns estimated fill price, fees, margin impact, and risk checks. ALWAYS call this before trade_execute and show the result to the user for confirmation.", { exchange: z.string().describe("Exchange: pacifica, hyperliquid, or lighter"), symbol: z.string().describe("Trading symbol (e.g., BTC, ETH, SOL)"), side: z.enum(["buy", "sell"]).describe("Order side"), size: z.string().describe("Order size (base currency units, e.g., '0.1' for 0.1 BTC)"), orderType: z.enum(["market", "limit"]).default("market").describe("Order type"), price: z.string().optional().describe("Limit price (required for limit orders)"), }, async ({ exchange, symbol, side, size, orderType, price }) => { try { const adapter = await getOrCreateAdapter(exchange); const [balance, positions, orderbook] = await Promise.all([ adapter.getBalance(), adapter.getPositions(), adapter.getOrderbook(symbol), ]); // Estimate fill price from orderbook const bestBid = orderbook.bids[0]?.[0]; const bestAsk = orderbook.asks[0]?.[0]; const estPrice = orderType === "limit" && price ? price : side === "buy" ? bestAsk : bestBid; const notional = estPrice ? Number(estPrice) * Number(size) : 0; const existingPos = positions.find(p => p.symbol.toUpperCase().includes(symbol.toUpperCase())); // Run validation let validation; try { validation = await validateTrade(adapter, { symbol, side, size: Number(size), leverage: existingPos?.leverage ?? 1 }); } catch { validation = { valid: true, checks: [], warnings: ["Validation unavailable — proceed with caution"] }; } const preview = { order: { exchange, symbol, side, size, orderType, price: price ?? estPrice }, estimate: { fillPrice: estPrice ?? "unknown", notionalUsd: notional.toFixed(2), spread: bestBid && bestAsk ? ((Number(bestAsk) - Number(bestBid)) / Number(bestBid) * 100).toFixed(4) + "%" : "unknown", }, account: { equity: balance.equity, available: balance.available, marginUsed: balance.marginUsed, marginAfter: (Number(balance.marginUsed) + notional / (existingPos?.leverage ?? 1)).toFixed(2), }, existingPosition: existingPos ? { side: existingPos.side, size: existingPos.size, entryPrice: existingPos.entryPrice, unrealizedPnl: existingPos.unrealizedPnl, } : null, validation, warning: "This is a PREVIEW only. Call trade_execute to place the order after user confirms.", }; return { content: [{ type: "text", text: ok(preview, { exchange, type: "preview" }) }] }; } catch (e) { return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e)) }], isError: true }; } }, );