ML Engine Updates: - Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records - Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence) - Backtest results: +176.71R profit with aggressive_filter strategy Documentation Consolidation: - Created docs/99-analisis/_MAP.md index with 13 new analysis documents - Consolidated inventories: removed duplicates from orchestration/inventarios/ - Updated ML_INVENTORY.yml with BTCUSD metrics and training results - Added execution reports: FASE11-BTCUSD, correction issues, alignment validation Architecture & Integration: - Updated all module documentation with NEXUS v3.4 frontmatter - Fixed _MAP.md indexes across all folders - Updated orchestration plans and traces Files: 229 changed, 5064 insertions(+), 1872 deletions(-) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1256 lines
32 KiB
Markdown
1256 lines
32 KiB
Markdown
---
|
|
id: "MCP-BINANCE-CONNECTOR-SPEC"
|
|
title: "Especificacion MCP Binance Connector"
|
|
type: "Technical Specification"
|
|
project: "trading-platform"
|
|
version: "1.0.0"
|
|
created_date: "2026-01-04"
|
|
updated_date: "2026-01-04"
|
|
author: "Orquestador Agent - Trading Platform"
|
|
---
|
|
|
|
# MCP Binance Connector - Especificacion Tecnica
|
|
|
|
**Version:** 1.0.0
|
|
**Fecha:** 2026-01-04
|
|
**Modulo:** OQI-010-llm-trading-integration
|
|
**Puerto:** 3606
|
|
|
|
---
|
|
|
|
## Tabla de Contenidos
|
|
|
|
1. [Vision General](#vision-general)
|
|
2. [Arquitectura](#arquitectura)
|
|
3. [MCP Tools Specification](#mcp-tools-specification)
|
|
4. [Seguridad](#seguridad)
|
|
5. [Configuracion](#configuracion)
|
|
6. [Implementacion](#implementacion)
|
|
7. [Testing](#testing)
|
|
|
|
---
|
|
|
|
## Vision General
|
|
|
|
### Proposito
|
|
|
|
El MCP Binance Connector expone las funcionalidades de Binance como herramientas MCP (Model Context Protocol), permitiendo que los agentes LLM interactuen con el exchange de forma segura y controlada.
|
|
|
|
### Capacidades
|
|
|
|
| Categoria | Funcionalidades |
|
|
|-----------|-----------------|
|
|
| **Market Data** | Precios, order book, velas, 24h stats |
|
|
| **Account** | Balance, posiciones, historial |
|
|
| **Trading** | Ordenes market/limit, cancelacion |
|
|
| **Futures** | Posiciones, leverage, funding |
|
|
|
|
### Integracion con LLM Agent
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ LLM TRADING AGENT │
|
|
│ │
|
|
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
│ │ MCP ORCHESTRATOR │ │
|
|
│ │ │ │
|
|
│ │ ┌──────────────────┐ ┌──────────────────┐ │ │
|
|
│ │ │ MCP MT4 │ │ MCP BINANCE │ │ │
|
|
│ │ │ Connector │ │ Connector │ │ │
|
|
│ │ │ :3605 │ │ :3606 │ │ │
|
|
│ │ └────────┬─────────┘ └────────┬─────────┘ │ │
|
|
│ └────────────┼─────────────────────────┼────────────────────┘ │
|
|
│ │ │ │
|
|
└───────────────┼─────────────────────────┼───────────────────────┘
|
|
│ │
|
|
▼ ▼
|
|
┌───────────────┐ ┌───────────────┐
|
|
│ MetaAPI │ │ Binance │
|
|
│ (MT4/MT5) │ │ Exchange │
|
|
└───────────────┘ └───────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Arquitectura
|
|
|
|
### Estructura del Proyecto
|
|
|
|
```
|
|
apps/mcp-binance-connector/
|
|
├── src/
|
|
│ ├── index.ts # Entry point & MCP server
|
|
│ ├── config.ts # Configuration management
|
|
│ │
|
|
│ ├── services/
|
|
│ │ ├── binance-client.ts # CCXT wrapper for Binance
|
|
│ │ ├── rate-limiter.ts # Rate limiting service
|
|
│ │ └── websocket-manager.ts # WebSocket connections
|
|
│ │
|
|
│ ├── tools/
|
|
│ │ ├── index.ts # Tool registry
|
|
│ │ ├── market.ts # Market data tools
|
|
│ │ ├── account.ts # Account info tools
|
|
│ │ ├── orders.ts # Order management tools
|
|
│ │ ├── positions.ts # Position management (futures)
|
|
│ │ └── utils.ts # Utility tools
|
|
│ │
|
|
│ ├── middleware/
|
|
│ │ ├── auth.ts # API key validation
|
|
│ │ ├── risk-check.ts # Pre-trade risk validation
|
|
│ │ └── logging.ts # Request/response logging
|
|
│ │
|
|
│ └── types/
|
|
│ ├── binance.ts # Binance-specific types
|
|
│ └── mcp.ts # MCP protocol types
|
|
│
|
|
├── tests/
|
|
│ ├── unit/
|
|
│ │ ├── market.test.ts
|
|
│ │ └── orders.test.ts
|
|
│ └── integration/
|
|
│ └── binance-api.test.ts
|
|
│
|
|
├── docs/
|
|
│ ├── ARCHITECTURE.md
|
|
│ └── MCP-TOOLS-SPEC.md
|
|
│
|
|
├── Dockerfile
|
|
├── package.json
|
|
├── tsconfig.json
|
|
├── .env.example
|
|
└── README.md
|
|
```
|
|
|
|
### Dependencias Principales
|
|
|
|
```json
|
|
{
|
|
"dependencies": {
|
|
"@modelcontextprotocol/sdk": "^0.6.0",
|
|
"ccxt": "^4.0.0",
|
|
"express": "^4.18.2",
|
|
"zod": "^3.22.0",
|
|
"redis": "^4.6.0",
|
|
"winston": "^3.11.0",
|
|
"axios": "^1.6.0"
|
|
},
|
|
"devDependencies": {
|
|
"typescript": "^5.3.0",
|
|
"ts-node-dev": "^2.0.0",
|
|
"jest": "^29.7.0",
|
|
"@types/node": "^20.10.0"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## MCP Tools Specification
|
|
|
|
### Tool Categories
|
|
|
|
#### 1. Market Data Tools (Read-Only, Low Risk)
|
|
|
|
```typescript
|
|
// tools/market.ts
|
|
|
|
/**
|
|
* Tool: binance_get_ticker
|
|
* Obtiene precio actual y estadisticas 24h de un simbolo
|
|
*/
|
|
export const getTickerTool: MCPTool = {
|
|
name: "binance_get_ticker",
|
|
description: "Obtiene el precio actual y estadisticas de 24h para un simbolo de trading",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo de trading (ej: BTCUSDT, ETHUSDT)",
|
|
pattern: "^[A-Z]{2,10}$"
|
|
}
|
|
},
|
|
required: ["symbol"]
|
|
},
|
|
handler: async ({ symbol }) => {
|
|
const ticker = await binanceClient.fetchTicker(symbol);
|
|
return {
|
|
symbol: ticker.symbol,
|
|
price: ticker.last,
|
|
bid: ticker.bid,
|
|
ask: ticker.ask,
|
|
high24h: ticker.high,
|
|
low24h: ticker.low,
|
|
volume24h: ticker.baseVolume,
|
|
change24h: ticker.percentage,
|
|
timestamp: ticker.timestamp
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Tool: binance_get_orderbook
|
|
* Obtiene el order book con profundidad especificada
|
|
*/
|
|
export const getOrderbookTool: MCPTool = {
|
|
name: "binance_get_orderbook",
|
|
description: "Obtiene el order book (bids y asks) con la profundidad especificada",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo de trading"
|
|
},
|
|
limit: {
|
|
type: "number",
|
|
description: "Profundidad del orderbook (5, 10, 20, 50, 100)",
|
|
enum: [5, 10, 20, 50, 100],
|
|
default: 20
|
|
}
|
|
},
|
|
required: ["symbol"]
|
|
},
|
|
handler: async ({ symbol, limit = 20 }) => {
|
|
const orderbook = await binanceClient.fetchOrderBook(symbol, limit);
|
|
return {
|
|
symbol,
|
|
bids: orderbook.bids.slice(0, 10),
|
|
asks: orderbook.asks.slice(0, 10),
|
|
spread: orderbook.asks[0][0] - orderbook.bids[0][0],
|
|
spreadPercentage: ((orderbook.asks[0][0] - orderbook.bids[0][0]) / orderbook.bids[0][0]) * 100,
|
|
timestamp: orderbook.timestamp
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Tool: binance_get_klines
|
|
* Obtiene velas historicas (OHLCV)
|
|
*/
|
|
export const getKlinesTool: MCPTool = {
|
|
name: "binance_get_klines",
|
|
description: "Obtiene velas historicas (OHLCV) para analisis tecnico",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo de trading"
|
|
},
|
|
interval: {
|
|
type: "string",
|
|
description: "Intervalo temporal",
|
|
enum: ["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w"],
|
|
default: "5m"
|
|
},
|
|
limit: {
|
|
type: "number",
|
|
description: "Numero de velas (max 500)",
|
|
default: 100,
|
|
maximum: 500
|
|
}
|
|
},
|
|
required: ["symbol"]
|
|
},
|
|
handler: async ({ symbol, interval = "5m", limit = 100 }) => {
|
|
const ohlcv = await binanceClient.fetchOHLCV(symbol, interval, undefined, limit);
|
|
return {
|
|
symbol,
|
|
interval,
|
|
candles: ohlcv.map(c => ({
|
|
timestamp: c[0],
|
|
open: c[1],
|
|
high: c[2],
|
|
low: c[3],
|
|
close: c[4],
|
|
volume: c[5]
|
|
})),
|
|
count: ohlcv.length
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Tool: binance_get_trades
|
|
* Obtiene trades recientes
|
|
*/
|
|
export const getRecentTradesTool: MCPTool = {
|
|
name: "binance_get_trades",
|
|
description: "Obtiene los trades mas recientes del mercado",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo de trading"
|
|
},
|
|
limit: {
|
|
type: "number",
|
|
description: "Numero de trades (max 100)",
|
|
default: 50,
|
|
maximum: 100
|
|
}
|
|
},
|
|
required: ["symbol"]
|
|
},
|
|
handler: async ({ symbol, limit = 50 }) => {
|
|
const trades = await binanceClient.fetchTrades(symbol, undefined, limit);
|
|
return {
|
|
symbol,
|
|
trades: trades.map(t => ({
|
|
id: t.id,
|
|
price: t.price,
|
|
amount: t.amount,
|
|
side: t.side,
|
|
timestamp: t.timestamp
|
|
})),
|
|
count: trades.length
|
|
};
|
|
}
|
|
};
|
|
```
|
|
|
|
#### 2. Account Tools (Read-Only, Medium Risk)
|
|
|
|
```typescript
|
|
// tools/account.ts
|
|
|
|
/**
|
|
* Tool: binance_get_account
|
|
* Obtiene balance y estado de la cuenta
|
|
*/
|
|
export const getAccountTool: MCPTool = {
|
|
name: "binance_get_account",
|
|
description: "Obtiene el balance y estado de la cuenta de trading",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {},
|
|
required: []
|
|
},
|
|
handler: async () => {
|
|
const balance = await binanceClient.fetchBalance();
|
|
|
|
// Filtrar solo activos con balance > 0
|
|
const nonZeroBalances = Object.entries(balance.total)
|
|
.filter(([_, amount]) => amount > 0)
|
|
.map(([asset, total]) => ({
|
|
asset,
|
|
free: balance.free[asset],
|
|
locked: balance.used[asset],
|
|
total
|
|
}));
|
|
|
|
return {
|
|
accountType: "SPOT",
|
|
balances: nonZeroBalances,
|
|
totalUSDT: await calculateTotalUSDT(nonZeroBalances),
|
|
canTrade: true,
|
|
canWithdraw: true,
|
|
updateTime: Date.now()
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Tool: binance_get_open_orders
|
|
* Obtiene ordenes abiertas
|
|
*/
|
|
export const getOpenOrdersTool: MCPTool = {
|
|
name: "binance_get_open_orders",
|
|
description: "Obtiene todas las ordenes abiertas (pendientes de ejecucion)",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo especifico (opcional, si no se especifica retorna todos)"
|
|
}
|
|
},
|
|
required: []
|
|
},
|
|
handler: async ({ symbol }) => {
|
|
const orders = await binanceClient.fetchOpenOrders(symbol);
|
|
return {
|
|
orders: orders.map(o => ({
|
|
id: o.id,
|
|
symbol: o.symbol,
|
|
side: o.side,
|
|
type: o.type,
|
|
price: o.price,
|
|
amount: o.amount,
|
|
filled: o.filled,
|
|
remaining: o.remaining,
|
|
status: o.status,
|
|
createdAt: o.timestamp
|
|
})),
|
|
count: orders.length
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Tool: binance_get_trade_history
|
|
* Obtiene historial de trades ejecutados
|
|
*/
|
|
export const getTradeHistoryTool: MCPTool = {
|
|
name: "binance_get_trade_history",
|
|
description: "Obtiene el historial de trades ejecutados por el usuario",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo de trading"
|
|
},
|
|
limit: {
|
|
type: "number",
|
|
description: "Numero de trades (max 100)",
|
|
default: 50,
|
|
maximum: 100
|
|
}
|
|
},
|
|
required: ["symbol"]
|
|
},
|
|
handler: async ({ symbol, limit = 50 }) => {
|
|
const trades = await binanceClient.fetchMyTrades(symbol, undefined, limit);
|
|
return {
|
|
symbol,
|
|
trades: trades.map(t => ({
|
|
id: t.id,
|
|
orderId: t.order,
|
|
side: t.side,
|
|
price: t.price,
|
|
amount: t.amount,
|
|
cost: t.cost,
|
|
fee: t.fee,
|
|
timestamp: t.timestamp
|
|
})),
|
|
totalPnL: calculatePnL(trades),
|
|
count: trades.length
|
|
};
|
|
}
|
|
};
|
|
```
|
|
|
|
#### 3. Order Management Tools (High Risk, Requires Confirmation)
|
|
|
|
```typescript
|
|
// tools/orders.ts
|
|
|
|
/**
|
|
* Tool: binance_create_order
|
|
* Crea una orden de trading
|
|
* ALTO RIESGO - Requiere confirmacion explicita
|
|
*/
|
|
export const createOrderTool: MCPTool = {
|
|
name: "binance_create_order",
|
|
description: "Crea una orden de compra o venta. ALTO RIESGO - Asegurate de validar con el usuario antes de ejecutar.",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo de trading (ej: BTCUSDT)"
|
|
},
|
|
side: {
|
|
type: "string",
|
|
description: "Direccion de la orden",
|
|
enum: ["buy", "sell"]
|
|
},
|
|
type: {
|
|
type: "string",
|
|
description: "Tipo de orden",
|
|
enum: ["market", "limit", "stop_loss", "take_profit"],
|
|
default: "market"
|
|
},
|
|
amount: {
|
|
type: "number",
|
|
description: "Cantidad a comprar/vender"
|
|
},
|
|
price: {
|
|
type: "number",
|
|
description: "Precio limite (requerido para ordenes limit)"
|
|
},
|
|
stopPrice: {
|
|
type: "number",
|
|
description: "Precio de activacion para stop orders"
|
|
}
|
|
},
|
|
required: ["symbol", "side", "amount"]
|
|
},
|
|
riskLevel: "HIGH",
|
|
requiresConfirmation: true,
|
|
handler: async ({ symbol, side, type = "market", amount, price, stopPrice }) => {
|
|
// Pre-trade risk check
|
|
const riskCheck = await performRiskCheck({ symbol, side, amount, price });
|
|
if (!riskCheck.allowed) {
|
|
return {
|
|
success: false,
|
|
error: "RISK_CHECK_FAILED",
|
|
details: riskCheck.reason
|
|
};
|
|
}
|
|
|
|
try {
|
|
let order;
|
|
if (type === "market") {
|
|
order = await binanceClient.createMarketOrder(symbol, side, amount);
|
|
} else if (type === "limit" && price) {
|
|
order = await binanceClient.createLimitOrder(symbol, side, amount, price);
|
|
} else if (type === "stop_loss" && stopPrice) {
|
|
order = await binanceClient.createOrder(symbol, "stop_loss", side, amount, undefined, {
|
|
stopPrice
|
|
});
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
order: {
|
|
id: order.id,
|
|
symbol: order.symbol,
|
|
side: order.side,
|
|
type: order.type,
|
|
price: order.price || order.average,
|
|
amount: order.amount,
|
|
filled: order.filled,
|
|
status: order.status,
|
|
timestamp: order.timestamp
|
|
}
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Tool: binance_cancel_order
|
|
* Cancela una orden pendiente
|
|
*/
|
|
export const cancelOrderTool: MCPTool = {
|
|
name: "binance_cancel_order",
|
|
description: "Cancela una orden pendiente",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo de trading"
|
|
},
|
|
orderId: {
|
|
type: "string",
|
|
description: "ID de la orden a cancelar"
|
|
}
|
|
},
|
|
required: ["symbol", "orderId"]
|
|
},
|
|
riskLevel: "MEDIUM",
|
|
handler: async ({ symbol, orderId }) => {
|
|
try {
|
|
const result = await binanceClient.cancelOrder(orderId, symbol);
|
|
return {
|
|
success: true,
|
|
cancelledOrder: {
|
|
id: result.id,
|
|
symbol: result.symbol,
|
|
status: "CANCELLED"
|
|
}
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Tool: binance_cancel_all_orders
|
|
* Cancela todas las ordenes de un simbolo
|
|
*/
|
|
export const cancelAllOrdersTool: MCPTool = {
|
|
name: "binance_cancel_all_orders",
|
|
description: "Cancela todas las ordenes pendientes de un simbolo",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo de trading"
|
|
}
|
|
},
|
|
required: ["symbol"]
|
|
},
|
|
riskLevel: "MEDIUM",
|
|
handler: async ({ symbol }) => {
|
|
try {
|
|
const result = await binanceClient.cancelAllOrders(symbol);
|
|
return {
|
|
success: true,
|
|
cancelledCount: result.length
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
#### 4. Futures Tools (High Risk)
|
|
|
|
```typescript
|
|
// tools/positions.ts
|
|
|
|
/**
|
|
* Tool: binance_get_futures_positions
|
|
* Obtiene posiciones abiertas en futuros
|
|
*/
|
|
export const getFuturesPositionsTool: MCPTool = {
|
|
name: "binance_get_futures_positions",
|
|
description: "Obtiene posiciones abiertas en Binance Futures",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo especifico (opcional)"
|
|
}
|
|
},
|
|
required: []
|
|
},
|
|
handler: async ({ symbol }) => {
|
|
// Cambiar a cliente de futuros
|
|
const positions = await binanceFuturesClient.fetchPositions(symbol);
|
|
|
|
const activePositions = positions.filter(p =>
|
|
parseFloat(p.contracts) !== 0
|
|
);
|
|
|
|
return {
|
|
positions: activePositions.map(p => ({
|
|
symbol: p.symbol,
|
|
side: parseFloat(p.contracts) > 0 ? 'LONG' : 'SHORT',
|
|
size: Math.abs(parseFloat(p.contracts)),
|
|
entryPrice: parseFloat(p.entryPrice),
|
|
markPrice: parseFloat(p.markPrice),
|
|
unrealizedPnL: parseFloat(p.unrealizedPnl),
|
|
leverage: p.leverage,
|
|
liquidationPrice: parseFloat(p.liquidationPrice),
|
|
marginType: p.marginType
|
|
})),
|
|
count: activePositions.length
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Tool: binance_set_leverage
|
|
* Configura el leverage para un simbolo
|
|
*/
|
|
export const setLeverageTool: MCPTool = {
|
|
name: "binance_set_leverage",
|
|
description: "Configura el nivel de leverage para un simbolo en Futures",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo de trading"
|
|
},
|
|
leverage: {
|
|
type: "number",
|
|
description: "Nivel de leverage (1-125)",
|
|
minimum: 1,
|
|
maximum: 125
|
|
}
|
|
},
|
|
required: ["symbol", "leverage"]
|
|
},
|
|
riskLevel: "HIGH",
|
|
handler: async ({ symbol, leverage }) => {
|
|
// Validar leverage maximo
|
|
if (leverage > 20) {
|
|
return {
|
|
success: false,
|
|
error: "LEVERAGE_TOO_HIGH",
|
|
message: "Leverage mayor a 20x no recomendado por politica de riesgo"
|
|
};
|
|
}
|
|
|
|
try {
|
|
await binanceFuturesClient.setLeverage(leverage, symbol);
|
|
return {
|
|
success: true,
|
|
symbol,
|
|
leverage,
|
|
message: `Leverage configurado a ${leverage}x`
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Tool: binance_close_position
|
|
* Cierra una posicion de futuros
|
|
*/
|
|
export const closePositionTool: MCPTool = {
|
|
name: "binance_close_position",
|
|
description: "Cierra completamente una posicion de futuros. ALTO RIESGO.",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo de la posicion a cerrar"
|
|
}
|
|
},
|
|
required: ["symbol"]
|
|
},
|
|
riskLevel: "HIGH",
|
|
requiresConfirmation: true,
|
|
handler: async ({ symbol }) => {
|
|
try {
|
|
// Obtener posicion actual
|
|
const positions = await binanceFuturesClient.fetchPositions(symbol);
|
|
const position = positions.find(p =>
|
|
p.symbol === symbol && parseFloat(p.contracts) !== 0
|
|
);
|
|
|
|
if (!position) {
|
|
return {
|
|
success: false,
|
|
error: "NO_POSITION",
|
|
message: `No hay posicion abierta para ${symbol}`
|
|
};
|
|
}
|
|
|
|
const size = Math.abs(parseFloat(position.contracts));
|
|
const side = parseFloat(position.contracts) > 0 ? 'sell' : 'buy';
|
|
|
|
// Crear orden de cierre
|
|
const order = await binanceFuturesClient.createMarketOrder(
|
|
symbol,
|
|
side,
|
|
size,
|
|
undefined,
|
|
{ reduceOnly: true }
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
closedPosition: {
|
|
symbol,
|
|
closedSize: size,
|
|
closedSide: side === 'sell' ? 'LONG' : 'SHORT',
|
|
realizedPnL: parseFloat(position.unrealizedPnl),
|
|
orderId: order.id
|
|
}
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
#### 5. Utility Tools
|
|
|
|
```typescript
|
|
// tools/utils.ts
|
|
|
|
/**
|
|
* Tool: binance_get_exchange_info
|
|
* Obtiene informacion del exchange
|
|
*/
|
|
export const getExchangeInfoTool: MCPTool = {
|
|
name: "binance_get_exchange_info",
|
|
description: "Obtiene informacion del exchange: simbolos disponibles, limites, etc.",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo especifico (opcional)"
|
|
}
|
|
},
|
|
required: []
|
|
},
|
|
handler: async ({ symbol }) => {
|
|
const markets = await binanceClient.loadMarkets();
|
|
|
|
if (symbol) {
|
|
const market = markets[symbol];
|
|
if (!market) {
|
|
return { error: `Symbol ${symbol} not found` };
|
|
}
|
|
return {
|
|
symbol: market.symbol,
|
|
base: market.base,
|
|
quote: market.quote,
|
|
precision: market.precision,
|
|
limits: market.limits,
|
|
active: market.active
|
|
};
|
|
}
|
|
|
|
// Retornar resumen general
|
|
const activeMarkets = Object.values(markets).filter(m => m.active);
|
|
return {
|
|
totalMarkets: activeMarkets.length,
|
|
topMarkets: activeMarkets
|
|
.filter(m => m.quote === 'USDT')
|
|
.slice(0, 20)
|
|
.map(m => m.symbol)
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Tool: binance_get_funding_rate
|
|
* Obtiene funding rate actual (futures)
|
|
*/
|
|
export const getFundingRateTool: MCPTool = {
|
|
name: "binance_get_funding_rate",
|
|
description: "Obtiene el funding rate actual para un simbolo de futuros",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
symbol: {
|
|
type: "string",
|
|
description: "Simbolo de futuros (ej: BTCUSDT)"
|
|
}
|
|
},
|
|
required: ["symbol"]
|
|
},
|
|
handler: async ({ symbol }) => {
|
|
const funding = await binanceFuturesClient.fetchFundingRate(symbol);
|
|
return {
|
|
symbol,
|
|
fundingRate: funding.fundingRate,
|
|
fundingTimestamp: funding.fundingTimestamp,
|
|
nextFundingTime: funding.nextFundingTimestamp,
|
|
annualized: funding.fundingRate * 3 * 365 * 100 // Aproximado anualizado
|
|
};
|
|
}
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Seguridad
|
|
|
|
### Niveles de Riesgo
|
|
|
|
| Nivel | Herramientas | Requiere Confirmacion |
|
|
|-------|--------------|----------------------|
|
|
| **LOW** | get_ticker, get_klines, get_orderbook | No |
|
|
| **MEDIUM** | get_account, cancel_order | No |
|
|
| **HIGH** | create_order, close_position, set_leverage | SI |
|
|
|
|
### Middleware de Autorizacion
|
|
|
|
```typescript
|
|
// middleware/auth.ts
|
|
|
|
import { Request, Response, NextFunction } from 'express';
|
|
|
|
export const authMiddleware = async (
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
) => {
|
|
// 1. Verificar API key del MCP client
|
|
const mcpKey = req.headers['x-mcp-api-key'];
|
|
if (!mcpKey || mcpKey !== process.env.MCP_API_KEY) {
|
|
return res.status(401).json({ error: 'Invalid MCP API key' });
|
|
}
|
|
|
|
// 2. Verificar que Binance API keys estan configuradas
|
|
if (!process.env.BINANCE_API_KEY || !process.env.BINANCE_API_SECRET) {
|
|
return res.status(500).json({ error: 'Binance API not configured' });
|
|
}
|
|
|
|
next();
|
|
};
|
|
```
|
|
|
|
### Pre-Trade Risk Check
|
|
|
|
```typescript
|
|
// middleware/risk-check.ts
|
|
|
|
interface RiskCheckParams {
|
|
symbol: string;
|
|
side: 'buy' | 'sell';
|
|
amount: number;
|
|
price?: number;
|
|
}
|
|
|
|
export const performRiskCheck = async (params: RiskCheckParams) => {
|
|
const { symbol, side, amount, price } = params;
|
|
|
|
// 1. Verificar balance suficiente
|
|
const balance = await binanceClient.fetchBalance();
|
|
const quoteAsset = symbol.replace(/.*?(USDT|BUSD|USDC)$/, '$1');
|
|
const available = balance.free[quoteAsset] || 0;
|
|
|
|
const orderValue = price ? amount * price : amount * (await getCurrentPrice(symbol));
|
|
|
|
if (side === 'buy' && available < orderValue) {
|
|
return {
|
|
allowed: false,
|
|
reason: `Insufficient balance. Required: ${orderValue}, Available: ${available}`
|
|
};
|
|
}
|
|
|
|
// 2. Verificar tamano maximo de orden
|
|
const maxOrderValue = parseFloat(process.env.MAX_ORDER_VALUE_USDT || '1000');
|
|
if (orderValue > maxOrderValue) {
|
|
return {
|
|
allowed: false,
|
|
reason: `Order value ${orderValue} exceeds maximum ${maxOrderValue} USDT`
|
|
};
|
|
}
|
|
|
|
// 3. Verificar limites diarios
|
|
const dailyVolume = await getDailyTradingVolume();
|
|
const maxDailyVolume = parseFloat(process.env.MAX_DAILY_VOLUME_USDT || '10000');
|
|
if (dailyVolume + orderValue > maxDailyVolume) {
|
|
return {
|
|
allowed: false,
|
|
reason: `Daily volume limit reached. Current: ${dailyVolume}, Limit: ${maxDailyVolume}`
|
|
};
|
|
}
|
|
|
|
return { allowed: true };
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Configuracion
|
|
|
|
### Variables de Entorno
|
|
|
|
```bash
|
|
# .env.example
|
|
|
|
# === MCP Server ===
|
|
PORT=3606
|
|
MCP_API_KEY=your_mcp_api_key_here
|
|
|
|
# === Binance API ===
|
|
BINANCE_API_KEY=your_binance_api_key
|
|
BINANCE_API_SECRET=your_binance_api_secret
|
|
|
|
# === Network ===
|
|
BINANCE_TESTNET=true # Usar testnet por defecto
|
|
BINANCE_FUTURES_TESTNET=true
|
|
|
|
# === Risk Limits ===
|
|
MAX_ORDER_VALUE_USDT=1000
|
|
MAX_DAILY_VOLUME_USDT=10000
|
|
MAX_LEVERAGE=20
|
|
MAX_POSITION_SIZE_PCT=5
|
|
|
|
# === Logging ===
|
|
LOG_LEVEL=info
|
|
LOG_FILE=logs/mcp-binance.log
|
|
|
|
# === Redis (para rate limiting) ===
|
|
REDIS_URL=redis://localhost:6379
|
|
```
|
|
|
|
### Configuracion de CCXT
|
|
|
|
```typescript
|
|
// config.ts
|
|
|
|
import ccxt from 'ccxt';
|
|
|
|
export const createBinanceClient = () => {
|
|
const isTestnet = process.env.BINANCE_TESTNET === 'true';
|
|
|
|
return new ccxt.binance({
|
|
apiKey: process.env.BINANCE_API_KEY,
|
|
secret: process.env.BINANCE_API_SECRET,
|
|
sandbox: isTestnet,
|
|
options: {
|
|
defaultType: 'spot',
|
|
adjustForTimeDifference: true,
|
|
},
|
|
enableRateLimit: true,
|
|
rateLimit: 100
|
|
});
|
|
};
|
|
|
|
export const createBinanceFuturesClient = () => {
|
|
const isTestnet = process.env.BINANCE_FUTURES_TESTNET === 'true';
|
|
|
|
return new ccxt.binance({
|
|
apiKey: process.env.BINANCE_API_KEY,
|
|
secret: process.env.BINANCE_API_SECRET,
|
|
sandbox: isTestnet,
|
|
options: {
|
|
defaultType: 'future',
|
|
adjustForTimeDifference: true,
|
|
},
|
|
enableRateLimit: true,
|
|
rateLimit: 100
|
|
});
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Implementacion
|
|
|
|
### Entry Point
|
|
|
|
```typescript
|
|
// src/index.ts
|
|
|
|
import express from 'express';
|
|
import { Server } from '@modelcontextprotocol/sdk/server';
|
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio';
|
|
import { createBinanceClient, createBinanceFuturesClient } from './config';
|
|
import { registerTools } from './tools';
|
|
import { authMiddleware } from './middleware/auth';
|
|
import { loggingMiddleware } from './middleware/logging';
|
|
import logger from './utils/logger';
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3606;
|
|
|
|
// Middleware
|
|
app.use(express.json());
|
|
app.use(loggingMiddleware);
|
|
|
|
// Health check
|
|
app.get('/health', (req, res) => {
|
|
res.json({
|
|
status: 'healthy',
|
|
service: 'mcp-binance-connector',
|
|
version: '1.0.0',
|
|
testnet: process.env.BINANCE_TESTNET === 'true',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
});
|
|
|
|
// MCP Server setup
|
|
const server = new Server({
|
|
name: 'trading-binance-mcp',
|
|
version: '1.0.0'
|
|
}, {
|
|
capabilities: {
|
|
tools: {}
|
|
}
|
|
});
|
|
|
|
// Initialize clients
|
|
const binanceClient = createBinanceClient();
|
|
const binanceFuturesClient = createBinanceFuturesClient();
|
|
|
|
// Register all tools
|
|
registerTools(server, binanceClient, binanceFuturesClient);
|
|
|
|
// Tool execution endpoint (HTTP fallback)
|
|
app.post('/tools/:toolName', authMiddleware, async (req, res) => {
|
|
const { toolName } = req.params;
|
|
const params = req.body;
|
|
|
|
try {
|
|
const result = await executeToolByName(toolName, params);
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error(`Tool execution failed: ${toolName}`, error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// List available tools
|
|
app.get('/tools', (req, res) => {
|
|
res.json({
|
|
tools: getAllToolDefinitions()
|
|
});
|
|
});
|
|
|
|
// Start HTTP server
|
|
app.listen(PORT, () => {
|
|
logger.info(`MCP Binance Connector running on port ${PORT}`);
|
|
logger.info(`Testnet mode: ${process.env.BINANCE_TESTNET === 'true'}`);
|
|
});
|
|
|
|
// Start MCP stdio transport
|
|
const transport = new StdioServerTransport();
|
|
server.connect(transport).catch((error) => {
|
|
logger.error('MCP transport connection failed', error);
|
|
});
|
|
|
|
export default app;
|
|
```
|
|
|
|
### Dockerfile
|
|
|
|
```dockerfile
|
|
# Dockerfile
|
|
|
|
FROM node:20-alpine
|
|
|
|
WORKDIR /app
|
|
|
|
# Install dependencies
|
|
COPY package*.json ./
|
|
RUN npm ci --only=production
|
|
|
|
# Copy source
|
|
COPY . .
|
|
|
|
# Build TypeScript
|
|
RUN npm run build
|
|
|
|
# Environment
|
|
ENV NODE_ENV=production
|
|
ENV PORT=3606
|
|
|
|
EXPOSE 3606
|
|
|
|
# Health check
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
CMD wget --no-verbose --tries=1 --spider http://localhost:3606/health || exit 1
|
|
|
|
CMD ["node", "dist/index.js"]
|
|
```
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Unit Tests
|
|
|
|
```typescript
|
|
// tests/unit/market.test.ts
|
|
|
|
import { getTickerTool, getKlinesTool } from '../../src/tools/market';
|
|
|
|
describe('Market Tools', () => {
|
|
describe('getTickerTool', () => {
|
|
it('should return ticker data for valid symbol', async () => {
|
|
const result = await getTickerTool.handler({ symbol: 'BTCUSDT' });
|
|
|
|
expect(result).toHaveProperty('symbol', 'BTCUSDT');
|
|
expect(result).toHaveProperty('price');
|
|
expect(result.price).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should handle invalid symbol', async () => {
|
|
const result = await getTickerTool.handler({ symbol: 'INVALID' });
|
|
|
|
expect(result).toHaveProperty('error');
|
|
});
|
|
});
|
|
|
|
describe('getKlinesTool', () => {
|
|
it('should return OHLCV data', async () => {
|
|
const result = await getKlinesTool.handler({
|
|
symbol: 'BTCUSDT',
|
|
interval: '5m',
|
|
limit: 10
|
|
});
|
|
|
|
expect(result.candles).toHaveLength(10);
|
|
expect(result.candles[0]).toHaveProperty('open');
|
|
expect(result.candles[0]).toHaveProperty('high');
|
|
expect(result.candles[0]).toHaveProperty('low');
|
|
expect(result.candles[0]).toHaveProperty('close');
|
|
expect(result.candles[0]).toHaveProperty('volume');
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
### Integration Tests
|
|
|
|
```typescript
|
|
// tests/integration/binance-api.test.ts
|
|
|
|
import request from 'supertest';
|
|
import app from '../../src/index';
|
|
|
|
describe('MCP Binance Connector API', () => {
|
|
const API_KEY = process.env.MCP_API_KEY;
|
|
|
|
describe('GET /health', () => {
|
|
it('should return healthy status', async () => {
|
|
const res = await request(app).get('/health');
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.status).toBe('healthy');
|
|
});
|
|
});
|
|
|
|
describe('POST /tools/binance_get_ticker', () => {
|
|
it('should return ticker data', async () => {
|
|
const res = await request(app)
|
|
.post('/tools/binance_get_ticker')
|
|
.set('x-mcp-api-key', API_KEY)
|
|
.send({ symbol: 'BTCUSDT' });
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toHaveProperty('symbol', 'BTCUSDT');
|
|
expect(res.body).toHaveProperty('price');
|
|
});
|
|
});
|
|
|
|
describe('POST /tools/binance_create_order', () => {
|
|
it('should reject without confirmation', async () => {
|
|
const res = await request(app)
|
|
.post('/tools/binance_create_order')
|
|
.set('x-mcp-api-key', API_KEY)
|
|
.send({
|
|
symbol: 'BTCUSDT',
|
|
side: 'buy',
|
|
amount: 0.001
|
|
});
|
|
|
|
// En testnet deberia funcionar, pero verificar risk check
|
|
expect(res.status).toBe(200);
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Metricas y Monitoring
|
|
|
|
| Metrica | Target | Alerta |
|
|
|---------|--------|--------|
|
|
| Latency | <200ms | >500ms |
|
|
| Error Rate | <1% | >5% |
|
|
| Uptime | 99.9% | <99% |
|
|
| Rate Limit Usage | <80% | >90% |
|
|
|
|
---
|
|
|
|
**Documento Generado:** 2026-01-04
|
|
**Autor:** Orquestador Agent - Trading Platform
|
|
**Version:** 1.0.0
|