/** * Binance Market Data Tools * * - binance_get_ticker: Get current price and 24h stats * - binance_get_orderbook: Get order book depth * - binance_get_klines: Get OHLCV candles * * @version 1.0.0 * @author Trading Platform Trading Platform */ import { z } from 'zod'; import { getBinanceClient, BinanceTicker, BinanceOrderBook, BinanceKline } from '../services/binance-client'; // ========================================== // binance_get_ticker // ========================================== /** * Tool: binance_get_ticker * Get current price and 24h statistics for a trading pair */ export const binanceGetTickerSchema = { name: 'binance_get_ticker', description: 'Get the current price and 24-hour statistics for a Binance trading pair', inputSchema: { type: 'object' as const, properties: { symbol: { type: 'string', description: 'Trading pair symbol (e.g., BTCUSDT, ETHUSDT, BNBUSDT)', }, }, required: ['symbol'] as string[], }, }; export const BinanceGetTickerInputSchema = z.object({ symbol: z.string().min(1).max(20).transform((s) => s.toUpperCase()), }); export type BinanceGetTickerInput = z.infer; export interface BinanceGetTickerResult { success: boolean; data?: BinanceTicker; error?: string; } export async function binance_get_ticker( params: BinanceGetTickerInput ): Promise { try { const client = getBinanceClient(); const ticker = await client.getTicker(params.symbol); return { success: true, data: ticker, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred', }; } } export async function handleBinanceGetTicker( params: unknown ): Promise<{ content: Array<{ type: string; text: string }> }> { const validatedParams = BinanceGetTickerInputSchema.parse(params); const result = await binance_get_ticker(validatedParams); if (result.success && result.data) { const d = result.data; const changeSymbol = d.change24h >= 0 ? '+' : ''; const formattedOutput = ` Binance Ticker: ${d.symbol} ${'='.repeat(35)} Current Price: $${d.price.toFixed(getPriceDecimals(d.symbol))} Bid: $${d.bid.toFixed(getPriceDecimals(d.symbol))} Ask: $${d.ask.toFixed(getPriceDecimals(d.symbol))} 24h Statistics -------------- High: $${d.high24h.toFixed(getPriceDecimals(d.symbol))} Low: $${d.low24h.toFixed(getPriceDecimals(d.symbol))} Volume: ${formatVolume(d.volume24h)} Change: ${changeSymbol}${d.change24h.toFixed(2)}% Last Update: ${new Date(d.timestamp).toISOString()} `.trim(); return { content: [{ type: 'text', text: formattedOutput }], }; } return { content: [{ type: 'text', text: `Error: ${result.error}` }], }; } // ========================================== // binance_get_orderbook // ========================================== /** * Tool: binance_get_orderbook * Get order book (bids and asks) with specified depth */ export const binanceGetOrderbookSchema = { name: 'binance_get_orderbook', description: 'Get the order book (bids and asks) with the specified depth for a trading pair', inputSchema: { type: 'object' as const, properties: { symbol: { type: 'string', description: 'Trading pair symbol (e.g., BTCUSDT)', }, limit: { type: 'number', description: 'Order book depth (5, 10, 20, 50, or 100). Default: 20', }, }, required: ['symbol'] as string[], }, }; export const BinanceGetOrderbookInputSchema = z.object({ symbol: z.string().min(1).max(20).transform((s) => s.toUpperCase()), limit: z.number().int().min(5).max(100).default(20), }); export type BinanceGetOrderbookInput = z.infer; export interface BinanceGetOrderbookResult { success: boolean; data?: BinanceOrderBook; error?: string; } export async function binance_get_orderbook( params: BinanceGetOrderbookInput ): Promise { try { const client = getBinanceClient(); const orderbook = await client.getOrderBook(params.symbol, params.limit); return { success: true, data: orderbook, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred', }; } } export async function handleBinanceGetOrderbook( params: unknown ): Promise<{ content: Array<{ type: string; text: string }> }> { const validatedParams = BinanceGetOrderbookInputSchema.parse(params); const result = await binance_get_orderbook(validatedParams); if (result.success && result.data) { const d = result.data; const decimals = getPriceDecimals(d.symbol); // Format top 10 levels const topBids = d.bids.slice(0, 10); const topAsks = d.asks.slice(0, 10); let bidsStr = topBids .map(([price, qty]) => ` $${price.toFixed(decimals)} | ${qty.toFixed(6)}`) .join('\n'); let asksStr = topAsks .map(([price, qty]) => ` $${price.toFixed(decimals)} | ${qty.toFixed(6)}`) .join('\n'); const formattedOutput = ` Order Book: ${d.symbol} ${'='.repeat(35)} Spread: $${d.spread.toFixed(decimals)} (${d.spreadPercentage.toFixed(4)}%) Top ${topAsks.length} Asks (Sell Orders) ${'-'.repeat(25)} ${asksStr} Top ${topBids.length} Bids (Buy Orders) ${'-'.repeat(25)} ${bidsStr} Timestamp: ${new Date(d.timestamp).toISOString()} `.trim(); return { content: [{ type: 'text', text: formattedOutput }], }; } return { content: [{ type: 'text', text: `Error: ${result.error}` }], }; } // ========================================== // binance_get_klines // ========================================== /** * Tool: binance_get_klines * Get historical OHLCV candles for technical analysis */ export const binanceGetKlinesSchema = { name: 'binance_get_klines', description: 'Get historical candlestick (OHLCV) data for technical analysis', inputSchema: { type: 'object' as const, properties: { symbol: { type: 'string', description: 'Trading pair symbol (e.g., BTCUSDT)', }, interval: { type: 'string', description: 'Candle interval: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w. Default: 5m', enum: ['1m', '5m', '15m', '30m', '1h', '4h', '1d', '1w'], }, limit: { type: 'number', description: 'Number of candles to retrieve (max 500). Default: 100', }, }, required: ['symbol'] as string[], }, }; export const BinanceGetKlinesInputSchema = z.object({ symbol: z.string().min(1).max(20).transform((s) => s.toUpperCase()), interval: z.enum(['1m', '5m', '15m', '30m', '1h', '4h', '1d', '1w']).default('5m'), limit: z.number().int().min(1).max(500).default(100), }); export type BinanceGetKlinesInput = z.infer; export interface BinanceGetKlinesResult { success: boolean; data?: { symbol: string; interval: string; candles: BinanceKline[]; count: number; }; error?: string; } export async function binance_get_klines( params: BinanceGetKlinesInput ): Promise { try { const client = getBinanceClient(); const klines = await client.getKlines(params.symbol, params.interval, params.limit); return { success: true, data: { symbol: params.symbol, interval: params.interval, candles: klines, count: klines.length, }, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred', }; } } export async function handleBinanceGetKlines( params: unknown ): Promise<{ content: Array<{ type: string; text: string }> }> { const validatedParams = BinanceGetKlinesInputSchema.parse(params); const result = await binance_get_klines(validatedParams); if (result.success && result.data) { const d = result.data; const decimals = getPriceDecimals(d.symbol); // Get last 5 candles for display const recentCandles = d.candles.slice(-5); let candlesStr = recentCandles .map((c) => { const time = new Date(c.timestamp).toISOString().slice(0, 16).replace('T', ' '); const direction = c.close >= c.open ? 'UP' : 'DOWN'; return ` ${time} | O:${c.open.toFixed(decimals)} H:${c.high.toFixed(decimals)} L:${c.low.toFixed(decimals)} C:${c.close.toFixed(decimals)} | V:${formatVolume(c.volume)} | ${direction}`; }) .join('\n'); // Calculate basic stats const closes = d.candles.map((c) => c.close); const high = Math.max(...d.candles.map((c) => c.high)); const low = Math.min(...d.candles.map((c) => c.low)); const avgVolume = d.candles.reduce((sum, c) => sum + c.volume, 0) / d.candles.length; const formattedOutput = ` Klines: ${d.symbol} (${d.interval}) ${'='.repeat(45)} Retrieved: ${d.count} candles Period Statistics ----------------- Highest High: $${high.toFixed(decimals)} Lowest Low: $${low.toFixed(decimals)} Avg Volume: ${formatVolume(avgVolume)} Recent Candles (last 5) ----------------------- ${candlesStr} First Candle: ${new Date(d.candles[0].timestamp).toISOString()} Last Candle: ${new Date(d.candles[d.candles.length - 1].timestamp).toISOString()} `.trim(); return { content: [{ type: 'text', text: formattedOutput }], }; } return { content: [{ type: 'text', text: `Error: ${result.error}` }], }; } // ========================================== // Helper Functions // ========================================== /** * Get appropriate decimal places for price display */ function getPriceDecimals(symbol: string): number { const upper = symbol.toUpperCase(); // Stablecoins and fiat pairs if (upper.includes('USD') && !upper.startsWith('BTC') && !upper.startsWith('ETH')) { return 4; } // BTC pairs if (upper === 'BTCUSDT' || upper === 'BTCBUSD') { return 2; } // ETH pairs if (upper === 'ETHUSDT' || upper === 'ETHBUSD') { return 2; } // Small value coins if (upper.includes('SHIB') || upper.includes('DOGE') || upper.includes('PEPE')) { return 8; } // Default return 4; } /** * Format large volume numbers */ function formatVolume(volume: number): string { if (volume >= 1_000_000_000) { return `${(volume / 1_000_000_000).toFixed(2)}B`; } if (volume >= 1_000_000) { return `${(volume / 1_000_000).toFixed(2)}M`; } if (volume >= 1_000) { return `${(volume / 1_000).toFixed(2)}K`; } return volume.toFixed(4); }