977 lines
23 KiB
Markdown
977 lines
23 KiB
Markdown
# 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/)
|