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>
22 KiB
| id | title | type | status | priority | epic | project | version | created_date | updated_date |
|---|---|---|---|---|---|---|---|---|---|
| ET-TRD-004 | Especificación Técnica - REST API Endpoints | Technical Specification | Done | Alta | OQI-003 | trading-platform | 1.0.0 | 2025-12-05 | 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 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:
Authorization: Bearer <jwt_token>
Endpoints - Market Data
GET /market/klines
Obtener datos históricos de velas (klines/candles).
Request:
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:
{
"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:
GET /market/ticker/BTCUSDT
Response:
{
"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:
{
"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:
GET /market/orderbook/BTCUSDT?limit=100
Query Parameters:
limit?: number - Depth (default: 100, options: 5, 10, 20, 50, 100, 500, 1000)
Response:
{
"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:
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:
{
"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:
{
"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:
POST /watchlists
Body:
{
"name": "DeFi Tokens",
"description": "Top DeFi projects",
"color": "#3498DB"
}
Response:
{
"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:
{
"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:
PUT /watchlists/:id
Body:
{
"name": "Updated Name",
"color": "#E74C3C"
}
DELETE /watchlists/:id
Eliminar watchlist.
Response:
{
"success": true,
"message": "Watchlist deleted successfully"
}
POST /watchlists/:id/symbols
Agregar símbolo a watchlist.
Request:
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:
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:
{
"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:
POST /paper/balances/reset
Body:
{
"initialAmount": 10000 // USDT
}
Endpoints - Paper Trading - Orders
GET /paper/orders
Obtener órdenes de paper trading.
Request:
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:
{
"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:
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:
{
"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:
pricemust be null - Limit orders:
priceis required - Stop orders:
stopPriceis 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:
PUT /paper/orders/:id
Body:
{
"price": 49800.00,
"quantity": 0.6
}
DELETE /paper/orders/:id
Cancelar orden abierta.
Response:
{
"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:
GET /paper/positions?status=open&symbol=BTCUSDT
Query Parameters:
status?: 'open' | 'closed' | 'all'
symbol?: string
limit?: number
offset?: number
Response:
{
"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:
{
"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:
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:
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:
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:
{
"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:
GET /paper/statistics?period=30d
Query Parameters:
period?: '7d' | '30d' | '90d' | 'all'
Response:
{
"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
// 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
// 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
// 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
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');
});
});
});