trading-platform-mcp-binanc.../src/tools/market.ts
2026-01-16 08:33:09 -06:00

393 lines
10 KiB
TypeScript

/**
* 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<typeof BinanceGetTickerInputSchema>;
export interface BinanceGetTickerResult {
success: boolean;
data?: BinanceTicker;
error?: string;
}
export async function binance_get_ticker(
params: BinanceGetTickerInput
): Promise<BinanceGetTickerResult> {
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<typeof BinanceGetOrderbookInputSchema>;
export interface BinanceGetOrderbookResult {
success: boolean;
data?: BinanceOrderBook;
error?: string;
}
export async function binance_get_orderbook(
params: BinanceGetOrderbookInput
): Promise<BinanceGetOrderbookResult> {
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<typeof BinanceGetKlinesInputSchema>;
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<BinanceGetKlinesResult> {
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);
}