# 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.orbiquant.com/api/v1/trading
Development: http://localhost:3001/api/v1/trading
```

---

## Autenticación

Todos los endpoints requieren autenticación JWT:

```http
Authorization: Bearer <jwt_token>
```

---

## 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/)
