--- id: "ET-TRD-004" title: "Especificación Técnica - REST API Endpoints" type: "Technical Specification" status: "Done" priority: "Alta" epic: "OQI-003" project: "trading-platform" version: "1.0.0" created_date: "2025-12-05" updated_date: "2026-01-04" --- # ET-TRD-004: Especificación Técnica - REST API Endpoints **Version:** 1.0.0 **Fecha:** 2025-12-05 **Estado:** Pendiente **Épica:** [OQI-003](../_MAP.md) **Requerimiento:** RF-TRD-004 --- ## Resumen Esta especificación detalla todos los endpoints REST API del módulo de trading, incluyendo market data, watchlists, paper trading (órdenes, posiciones, balances), validación de requests y manejo de errores. --- ## Arquitectura ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ FRONTEND │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ API Client │───▶│ Axios │───▶│ Auth │ │ │ │ (services) │ │ Interceptors │ │ Interceptor │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ └────────────────────────────────┬────────────────────────────────────────┘ │ │ HTTPS + JWT ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ API GATEWAY / ROUTER │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ /api/v1/trading/* │ │ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ │ │ Auth │─▶│Validation │─▶│ Rate │─▶│ Router │ │ │ │ │ │Middleware │ │Middleware │ │ Limiting │ │ │ │ │ │ │ └───────────┘ └───────────┘ └───────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ └────────────────────────────────┬────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ CONTROLLERS │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Market │ │ Watchlist │ │ Paper │ │ │ │ Controller │ │ Controller │ │ Trading │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Binance │ │ Watchlist │ │ Order │ │ │ │ Service │ │ Service │ │ Execution │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ ``` --- ## Base URL ``` Production: https://api.trading.com/api/v1/trading Development: http://localhost:3001/api/v1/trading ``` --- ## Autenticación Todos los endpoints requieren autenticación JWT: ```http Authorization: Bearer ``` --- ## Endpoints - Market Data ### GET /market/klines Obtener datos históricos de velas (klines/candles). **Request:** ```typescript GET /market/klines?symbol=BTCUSDT&interval=1h&limit=100 Query Parameters: symbol: string (required) - Trading pair (e.g., BTCUSDT) interval: string (required) - Kline interval (1m, 5m, 15m, 1h, 4h, 1d, etc.) startTime?: number - Start time in milliseconds endTime?: number - End time in milliseconds limit?: number - Number of results (default: 500, max: 1000) ``` **Response:** ```typescript { "success": true, "data": [ { "openTime": 1638316800000, "open": "50000.00", "high": "51000.00", "low": "49500.00", "close": "50500.00", "volume": "1250.50", "closeTime": 1638320399999, "quoteVolume": "63125000.00", "trades": 15420, "takerBuyBaseVolume": "625.25", "takerBuyQuoteVolume": "31562500.00" } ], "cached": false, "timestamp": 1638320400000 } ``` ### GET /market/ticker/:symbol Obtener ticker 24hr de un símbolo. **Request:** ```typescript GET /market/ticker/BTCUSDT ``` **Response:** ```typescript { "success": true, "data": { "symbol": "BTCUSDT", "priceChange": "1500.00", "priceChangePercent": "3.05", "weightedAvgPrice": "49750.25", "lastPrice": "50500.00", "lastQty": "0.5", "bidPrice": "50499.00", "bidQty": "2.5", "askPrice": "50501.00", "askQty": "3.2", "openPrice": "49000.00", "highPrice": "51200.00", "lowPrice": "48800.00", "volume": "15420.50", "quoteVolume": "767250000.00", "openTime": 1638234000000, "closeTime": 1638320400000, "firstId": 1000000, "lastId": 1015420, "count": 15420 }, "cached": false, "timestamp": 1638320400000 } ``` ### GET /market/tickers Obtener todos los tickers 24hr. **Response:** ```typescript { "success": true, "data": [ { "symbol": "BTCUSDT", "lastPrice": "50500.00", ... }, { "symbol": "ETHUSDT", "lastPrice": "4200.00", ... } ], "count": 350, "timestamp": 1638320400000 } ``` ### GET /market/orderbook/:symbol Obtener order book (libro de órdenes). **Request:** ```typescript GET /market/orderbook/BTCUSDT?limit=100 Query Parameters: limit?: number - Depth (default: 100, options: 5, 10, 20, 50, 100, 500, 1000) ``` **Response:** ```typescript { "success": true, "data": { "lastUpdateId": 1027024, "bids": [ ["50499.00", "2.5"], ["50498.00", "1.8"] ], "asks": [ ["50501.00", "3.2"], ["50502.00", "2.1"] ] }, "timestamp": 1638320400000 } ``` ### GET /market/symbols Obtener lista de símbolos disponibles. **Request:** ```typescript GET /market/symbols?quoteAsset=USDT Query Parameters: quoteAsset?: string - Filter by quote asset (USDT, BTC, etc.) status?: string - Filter by status (TRADING, BREAK, etc.) ``` **Response:** ```typescript { "success": true, "data": [ { "symbol": "BTCUSDT", "status": "TRADING", "baseAsset": "BTC", "baseAssetPrecision": 8, "quoteAsset": "USDT", "quotePrecision": 8, "orderTypes": ["LIMIT", "MARKET", "STOP_LOSS_LIMIT"], "filters": [ { "filterType": "PRICE_FILTER", "minPrice": "0.01000000", "maxPrice": "1000000.00000000", "tickSize": "0.01000000" } ] } ], "count": 350, "timestamp": 1638320400000 } ``` --- ## Endpoints - Watchlists ### GET /watchlists Obtener todas las watchlists del usuario. **Response:** ```typescript { "success": true, "data": [ { "id": "uuid-1", "userId": "uuid-user", "name": "My Crypto", "description": "Main cryptocurrencies", "color": "#FF5733", "isDefault": true, "orderIndex": 0, "symbolCount": 5, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ], "count": 3 } ``` ### POST /watchlists Crear nueva watchlist. **Request:** ```typescript POST /watchlists Body: { "name": "DeFi Tokens", "description": "Top DeFi projects", "color": "#3498DB" } ``` **Response:** ```typescript { "success": true, "data": { "id": "uuid-new", "userId": "uuid-user", "name": "DeFi Tokens", "description": "Top DeFi projects", "color": "#3498DB", "isDefault": false, "orderIndex": 3, "createdAt": "2024-01-20T14:20:00Z", "updatedAt": "2024-01-20T14:20:00Z" } } ``` ### GET /watchlists/:id Obtener watchlist con sus símbolos. **Response:** ```typescript { "success": true, "data": { "id": "uuid-1", "name": "My Crypto", "symbols": [ { "id": "uuid-s1", "watchlistId": "uuid-1", "symbol": "BTCUSDT", "baseAsset": "BTC", "quoteAsset": "USDT", "notes": "King of crypto", "alertPriceHigh": 55000, "alertPriceLow": 45000, "orderIndex": 0, "currentPrice": 50500.00, "priceChange24h": 3.05, "addedAt": "2024-01-15T10:30:00Z" } ] } } ``` ### PUT /watchlists/:id Actualizar watchlist. **Request:** ```typescript PUT /watchlists/:id Body: { "name": "Updated Name", "color": "#E74C3C" } ``` ### DELETE /watchlists/:id Eliminar watchlist. **Response:** ```typescript { "success": true, "message": "Watchlist deleted successfully" } ``` ### POST /watchlists/:id/symbols Agregar símbolo a watchlist. **Request:** ```typescript POST /watchlists/:id/symbols Body: { "symbol": "ETHUSDT", "baseAsset": "ETH", "quoteAsset": "USDT", "notes": "Ethereum network", "alertPriceHigh": 4500, "alertPriceLow": 3800 } ``` ### DELETE /watchlists/:id/symbols/:symbolId Remover símbolo de watchlist. ### PUT /watchlists/:id/symbols/reorder Reordenar símbolos en watchlist. **Request:** ```typescript PUT /watchlists/:id/symbols/reorder Body: { "symbolIds": ["uuid-s3", "uuid-s1", "uuid-s2"] } ``` --- ## Endpoints - Paper Trading - Balances ### GET /paper/balances Obtener balances de paper trading. **Response:** ```typescript { "success": true, "data": [ { "id": "uuid-b1", "userId": "uuid-user", "asset": "USDT", "total": 12500.50, "available": 11000.00, "locked": 1500.50, "updatedAt": "2024-01-20T15:30:00Z" }, { "asset": "BTC", "total": 0.5, "available": 0.5, "locked": 0, "updatedAt": "2024-01-20T15:30:00Z" } ], "totalValueUSDT": 37500.50 } ``` ### POST /paper/balances/reset Resetear balances a valores iniciales. **Request:** ```typescript POST /paper/balances/reset Body: { "initialAmount": 10000 // USDT } ``` --- ## Endpoints - Paper Trading - Orders ### GET /paper/orders Obtener órdenes de paper trading. **Request:** ```typescript GET /paper/orders?status=open&symbol=BTCUSDT&limit=50 Query Parameters: status?: OrderStatus - Filter by status (open, filled, cancelled, all) symbol?: string - Filter by symbol limit?: number - Results limit (default: 50) offset?: number - Pagination offset ``` **Response:** ```typescript { "success": true, "data": [ { "id": "uuid-o1", "userId": "uuid-user", "symbol": "BTCUSDT", "side": "buy", "type": "limit", "status": "open", "quantity": 0.5, "filledQuantity": 0.2, "remainingQuantity": 0.3, "price": 49500.00, "averageFillPrice": 49520.00, "quoteQuantity": 24750.00, "filledQuoteQuantity": 9904.00, "commission": 9.904, "commissionAsset": "USDT", "timeInForce": "GTC", "placedAt": "2024-01-20T14:00:00Z", "updatedAt": "2024-01-20T14:05:00Z" } ], "pagination": { "total": 125, "limit": 50, "offset": 0, "hasMore": true } } ``` ### POST /paper/orders Crear nueva orden de paper trading. **Request:** ```typescript POST /paper/orders Body: { "symbol": "BTCUSDT", "side": "buy", "type": "limit", "quantity": 0.5, "price": 49500.00, "timeInForce": "GTC", "stopPrice": null, // For stop orders "notes": "Buy the dip" } ``` **Response:** ```typescript { "success": true, "data": { "id": "uuid-new-order", "status": "open", "symbol": "BTCUSDT", "side": "buy", "type": "limit", "quantity": 0.5, "price": 49500.00, "placedAt": "2024-01-20T15:00:00Z" }, "message": "Order placed successfully" } ``` **Validation Rules:** - Market orders: `price` must be null - Limit orders: `price` is required - Stop orders: `stopPrice` is required - Quantity must be > 0 - Must have sufficient balance ### GET /paper/orders/:id Obtener detalle de orden específica. ### PUT /paper/orders/:id Modificar orden abierta (solo limit orders). **Request:** ```typescript PUT /paper/orders/:id Body: { "price": 49800.00, "quantity": 0.6 } ``` ### DELETE /paper/orders/:id Cancelar orden abierta. **Response:** ```typescript { "success": true, "data": { "id": "uuid-o1", "status": "cancelled", "cancelledAt": "2024-01-20T15:30:00Z" }, "message": "Order cancelled successfully" } ``` --- ## Endpoints - Paper Trading - Positions ### GET /paper/positions Obtener posiciones de paper trading. **Request:** ```typescript GET /paper/positions?status=open&symbol=BTCUSDT Query Parameters: status?: 'open' | 'closed' | 'all' symbol?: string limit?: number offset?: number ``` **Response:** ```typescript { "success": true, "data": [ { "id": "uuid-p1", "userId": "uuid-user", "symbol": "BTCUSDT", "side": "long", "status": "open", "entryPrice": 49520.00, "currentPrice": 50500.00, "currentQuantity": 0.5, "entryValue": 24760.00, "currentValue": 25250.00, "unrealizedPnl": 490.00, "realizedPnl": 0, "totalPnl": 490.00, "pnlPercentage": 1.98, "totalCommission": 24.76, "stopLossPrice": 48000.00, "takeProfitPrice": 52000.00, "openedAt": "2024-01-20T14:05:00Z", "updatedAt": "2024-01-20T15:30:00Z" } ], "summary": { "totalPositions": 3, "totalPnl": 1250.50, "totalPnlPercentage": 5.02 } } ``` ### GET /paper/positions/:id Obtener detalle de posición con historial de trades. **Response:** ```typescript { "success": true, "data": { "id": "uuid-p1", "symbol": "BTCUSDT", "side": "long", "status": "open", // ... otros campos "trades": [ { "id": "uuid-t1", "orderId": "uuid-o1", "type": "entry", "price": 49520.00, "quantity": 0.5, "commission": 24.76, "executedAt": "2024-01-20T14:05:00Z" } ] } } ``` ### PUT /paper/positions/:id/stop-loss Actualizar stop loss de posición. **Request:** ```typescript PUT /paper/positions/:id/stop-loss Body: { "stopLossPrice": 48500.00 } ``` ### PUT /paper/positions/:id/take-profit Actualizar take profit de posición. ### POST /paper/positions/:id/close Cerrar posición manualmente. **Request:** ```typescript POST /paper/positions/:id/close Body: { "quantity": 0.5, // Optional, defaults to full position "type": "market" // or "limit" "price": 50500.00 // Required if type is limit } ``` --- ## Endpoints - Paper Trading - Trades ### GET /paper/trades Obtener historial de trades ejecutados. **Request:** ```typescript GET /paper/trades?symbol=BTCUSDT&startDate=2024-01-01&limit=100 Query Parameters: symbol?: string startDate?: string (ISO 8601) endDate?: string limit?: number offset?: number ``` **Response:** ```typescript { "success": true, "data": [ { "id": "uuid-t1", "orderId": "uuid-o1", "positionId": "uuid-p1", "symbol": "BTCUSDT", "side": "buy", "type": "entry", "price": 49520.00, "quantity": 0.5, "quoteQuantity": 24760.00, "commission": 24.76, "commissionAsset": "USDT", "marketPrice": 49500.00, "slippage": 20.00, "isMaker": false, "executedAt": "2024-01-20T14:05:00Z" } ], "pagination": { "total": 250, "limit": 100, "offset": 0 } } ``` --- ## Endpoints - Statistics ### GET /paper/statistics Obtener estadísticas de paper trading. **Request:** ```typescript GET /paper/statistics?period=30d Query Parameters: period?: '7d' | '30d' | '90d' | 'all' ``` **Response:** ```typescript { "success": true, "data": { "period": "30d", "totalTrades": 45, "winningTrades": 28, "losingTrades": 17, "winRate": 62.22, "totalPnl": 2450.50, "totalPnlPercentage": 24.51, "averagePnl": 54.46, "largestWin": 850.00, "largestLoss": -320.00, "averageWin": 125.50, "averageLoss": -65.30, "profitFactor": 1.92, "sharpeRatio": 1.45, "maxDrawdown": -580.00, "maxDrawdownPercentage": -5.80, "totalCommission": 245.50, "bySymbol": [ { "symbol": "BTCUSDT", "trades": 20, "pnl": 1500.00, "winRate": 65.00 } ] } } ``` --- ## Validation Schemas ```typescript // validation/market.schemas.ts import { z } from 'zod'; export const getKlinesSchema = z.object({ query: z.object({ symbol: z.string().min(1), interval: z.enum(['1s', '1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '6h', '8h', '12h', '1d', '3d', '1w', '1M']), startTime: z.string().optional().transform(Number), endTime: z.string().optional().transform(Number), limit: z.string().optional().transform(Number).refine(n => n <= 1000), }), }); export const createOrderSchema = z.object({ body: z.object({ symbol: z.string().min(1), side: z.enum(['buy', 'sell']), type: z.enum(['market', 'limit', 'stop_loss', 'stop_limit', 'take_profit']), quantity: z.number().positive(), price: z.number().positive().optional(), stopPrice: z.number().positive().optional(), timeInForce: z.enum(['GTC', 'IOC', 'FOK']).default('GTC'), notes: z.string().max(500).optional(), }).refine( data => { if (data.type === 'market') return !data.price; if (data.type === 'limit') return !!data.price; if (['stop_loss', 'stop_limit'].includes(data.type)) return !!data.stopPrice; return true; }, { message: 'Invalid price configuration for order type' } ), }); ``` --- ## Error Handling ```typescript // Respuesta de error estándar { "success": false, "error": { "code": "INSUFFICIENT_BALANCE", "message": "Insufficient balance for this order", "details": { "required": 10000.00, "available": 8500.50 } }, "timestamp": 1638320400000 } // Códigos de error comunes ERROR_CODES = { // Market data INVALID_SYMBOL: 'Invalid trading symbol', INVALID_INTERVAL: 'Invalid kline interval', // Watchlists WATCHLIST_NOT_FOUND: 'Watchlist not found', WATCHLIST_NAME_EXISTS: 'Watchlist name already exists', SYMBOL_ALREADY_IN_WATCHLIST: 'Symbol already in watchlist', // Orders INSUFFICIENT_BALANCE: 'Insufficient balance', INVALID_QUANTITY: 'Invalid order quantity', INVALID_PRICE: 'Invalid order price', ORDER_NOT_FOUND: 'Order not found', ORDER_NOT_CANCELLABLE: 'Order cannot be cancelled', // Positions POSITION_NOT_FOUND: 'Position not found', NO_OPEN_POSITION: 'No open position for this symbol', // General VALIDATION_ERROR: 'Validation error', RATE_LIMIT_EXCEEDED: 'Rate limit exceeded', UNAUTHORIZED: 'Unauthorized access', } ``` --- ## Rate Limiting ```typescript // Por usuario autenticado { endpoint: '/paper/orders', limit: 100, // requests per minute window: 60000 // ms } // Headers de respuesta X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1638320460000 ``` --- ## Testing ```typescript describe('Trading API', () => { describe('GET /market/klines', () => { it('should return klines data', async () => { const response = await request(app) .get('/api/v1/trading/market/klines') .query({ symbol: 'BTCUSDT', interval: '1h' }) .set('Authorization', `Bearer ${token}`); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data).toBeInstanceOf(Array); }); }); describe('POST /paper/orders', () => { it('should create limit order', async () => { const response = await request(app) .post('/api/v1/trading/paper/orders') .set('Authorization', `Bearer ${token}`) .send({ symbol: 'BTCUSDT', side: 'buy', type: 'limit', quantity: 0.5, price: 49500.00, }); expect(response.status).toBe(201); expect(response.body.data.status).toBe('open'); }); it('should reject order with insufficient balance', async () => { const response = await request(app) .post('/api/v1/trading/paper/orders') .set('Authorization', `Bearer ${token}`) .send({ symbol: 'BTCUSDT', side: 'buy', type: 'market', quantity: 100, // Too large }); expect(response.status).toBe(400); expect(response.body.error.code).toBe('INSUFFICIENT_BALANCE'); }); }); }); ``` --- ## Referencias - [REST API Best Practices](https://restfulapi.net/) - [Express.js Documentation](https://expressjs.com/) - [Zod Validation](https://zod.dev/)