469 lines
14 KiB
Markdown
469 lines
14 KiB
Markdown
# RF-TRD-006: Gestión de Posiciones
|
||
|
||
**Versión:** 1.0.0
|
||
**Fecha:** 2025-12-05
|
||
**Épica:** OQI-003 - Trading y Charts
|
||
**Prioridad:** P0
|
||
**Story Points:** 8
|
||
|
||
---
|
||
|
||
## Descripción
|
||
|
||
El sistema debe proporcionar una gestión completa de posiciones abiertas, permitiendo a los usuarios monitorear su exposición al mercado, PnL en tiempo real, y ejecutar acciones sobre sus posiciones de manera eficiente y segura.
|
||
|
||
---
|
||
|
||
## Requisitos Funcionales
|
||
|
||
### RF-TRD-006.1: Apertura de Posiciones
|
||
|
||
El sistema debe:
|
||
- Crear posición automáticamente al ejecutar orden buy/sell
|
||
- Soportar posiciones long (compra) y short (venta, futuro)
|
||
- Permitir una posición por símbolo en paper trading MVP
|
||
- Calcular precio promedio de entrada
|
||
- Registrar timestamp de apertura
|
||
- Aplicar stop loss y take profit si fueron configurados
|
||
|
||
### RF-TRD-006.2: Información de Posición
|
||
|
||
El sistema debe mostrar para cada posición:
|
||
|
||
| Campo | Descripción | Cálculo |
|
||
|-------|-------------|---------|
|
||
| Symbol | Par de trading | - |
|
||
| Side | Long/Short | - |
|
||
| Entry Price | Precio promedio de entrada | Weighted average |
|
||
| Current Price | Precio actual del mercado | Real-time |
|
||
| Quantity | Cantidad del activo | - |
|
||
| Position Value | Valor actual de la posición | quantity × currentPrice |
|
||
| Unrealized PnL | Ganancia/pérdida no realizada | (currentPrice - entryPrice) × quantity |
|
||
| Unrealized PnL % | Porcentaje de ganancia/pérdida | ((currentPrice - entryPrice) / entryPrice) × 100 |
|
||
| Margin Used | Margen ocupado | positionValue / leverage |
|
||
| Liquidation Price | Precio de liquidación | Calculado según leverage |
|
||
| Duration | Tiempo abierta | now - openedAt |
|
||
|
||
### RF-TRD-006.3: Actualización en Tiempo Real
|
||
|
||
El sistema debe:
|
||
- Actualizar precios actuales cada 1 segundo vía WebSocket
|
||
- Recalcular PnL automáticamente con cada update
|
||
- Mostrar animación visual en cambios de precio
|
||
- Destacar posiciones en riesgo (pérdida > 50%)
|
||
- Actualizar métricas agregadas (total PnL, total margin)
|
||
|
||
### RF-TRD-006.4: Panel de Posiciones
|
||
|
||
El sistema debe proporcionar tabla con:
|
||
- Lista de todas las posiciones abiertas
|
||
- Resumen agregado en header
|
||
- Filtros por símbolo, side, PnL
|
||
- Ordenamiento por cualquier columna
|
||
- Indicadores visuales de estado
|
||
- Acciones rápidas por posición
|
||
|
||
**Resumen agregado:**
|
||
```
|
||
Total Positions: 5
|
||
Total Margin Used: $15,234.56
|
||
Total Unrealized PnL: +$1,234.56 (+8.1%)
|
||
```
|
||
|
||
### RF-TRD-006.5: Cerrar Posiciones
|
||
|
||
El sistema debe permitir:
|
||
|
||
**Cierre total:**
|
||
- Cerrar 100% de la posición
|
||
- Ejecutar orden market al precio actual
|
||
- Calcular PnL realizado
|
||
- Actualizar balance
|
||
- Registrar trade en historial
|
||
|
||
**Cierre parcial:**
|
||
- Cerrar porcentaje específico (25%, 50%, 75%)
|
||
- Mantener resto de posición abierta
|
||
- Recalcular precio promedio
|
||
- Registrar trade parcial
|
||
|
||
**Confirmación:**
|
||
- Modal con previsualización del cierre
|
||
- Mostrar PnL estimado
|
||
- Mostrar balance después del cierre
|
||
- Requiere confirmación del usuario
|
||
|
||
### RF-TRD-006.6: Stop Loss y Take Profit
|
||
|
||
El sistema debe:
|
||
|
||
**Configuración:**
|
||
- Añadir SL/TP a posición existente
|
||
- Modificar SL/TP existentes
|
||
- Eliminar SL/TP
|
||
- Validar precios lógicos según side
|
||
|
||
**Monitoreo automático:**
|
||
- Verificar precios cada 1 segundo
|
||
- Ejecutar cierre automático al alcanzar nivel
|
||
- Notificar al usuario
|
||
- Registrar motivo de cierre
|
||
|
||
**Trailing Stop Loss:**
|
||
- Actualizar SL automáticamente según ganancia
|
||
- Configurar trailing distance (% o USD)
|
||
- Activar solo cuando posición es ganadora
|
||
|
||
### RF-TRD-006.7: Alertas y Notificaciones
|
||
|
||
El sistema debe notificar cuando:
|
||
- Posición alcanza +X% de ganancia
|
||
- Posición alcanza -X% de pérdida
|
||
- Stop Loss está cerca de activarse (5%)
|
||
- Take Profit está cerca de activarse (5%)
|
||
- Posición está en riesgo de liquidación
|
||
- Posición ha estado abierta > 24h
|
||
|
||
### RF-TRD-006.8: Gestión de Riesgo
|
||
|
||
El sistema debe:
|
||
|
||
**Validaciones:**
|
||
- Impedir apertura si margin used > 80% del balance
|
||
- Advertir si posición representa > 50% del portfolio
|
||
- Bloquear si existe ya posición del mismo símbolo
|
||
- Validar que stop loss sea lógico
|
||
|
||
**Liquidación:**
|
||
- Calcular precio de liquidación
|
||
- Liquidar automáticamente al alcanzarlo
|
||
- Notificar antes de liquidación (margen < 20%)
|
||
- Registrar liquidación en historial
|
||
|
||
**Métricas de riesgo:**
|
||
- Exposure por símbolo
|
||
- Exposure total
|
||
- Ratio de margen (margin used / balance)
|
||
- Win rate actual
|
||
- Average holding time
|
||
|
||
---
|
||
|
||
## Datos de Entrada
|
||
|
||
### Modificar Posición
|
||
|
||
```typescript
|
||
interface UpdatePositionDto {
|
||
positionId: string;
|
||
stopLoss?: {
|
||
type: 'price' | 'percent';
|
||
value: number;
|
||
trailing?: {
|
||
enabled: boolean;
|
||
distance: number; // % o USD
|
||
};
|
||
};
|
||
takeProfit?: {
|
||
type: 'price' | 'percent';
|
||
value: number;
|
||
};
|
||
}
|
||
```
|
||
|
||
### Cerrar Posición
|
||
|
||
```typescript
|
||
interface ClosePositionDto {
|
||
positionId: string;
|
||
percentage: number; // 1-100, default 100
|
||
type: 'market' | 'limit';
|
||
limitPrice?: number;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Datos de Salida
|
||
|
||
### Posición
|
||
|
||
```typescript
|
||
interface Position {
|
||
id: string;
|
||
userId: string;
|
||
symbol: string;
|
||
side: 'long' | 'short';
|
||
|
||
// Cantidades
|
||
quantity: number;
|
||
entryPrice: number;
|
||
currentPrice: number;
|
||
|
||
// PnL
|
||
unrealizedPnl: number;
|
||
unrealizedPnlPercent: number;
|
||
realizedPnl: number; // De cierres parciales
|
||
|
||
// Valores
|
||
positionValue: number;
|
||
marginUsed: number;
|
||
|
||
// Stop Loss / Take Profit
|
||
stopLoss: PositionStopLoss | null;
|
||
takeProfit: PositionTakeProfit | null;
|
||
|
||
// Liquidación
|
||
liquidationPrice: number | null;
|
||
liquidationRisk: 'low' | 'medium' | 'high';
|
||
|
||
// Timestamps
|
||
openedAt: string;
|
||
lastUpdatedAt: string;
|
||
duration: number; // Segundos
|
||
|
||
// Estado
|
||
status: 'open' | 'closing' | 'closed';
|
||
isInProfit: boolean;
|
||
}
|
||
|
||
interface PositionStopLoss {
|
||
price: number;
|
||
type: 'price' | 'percent';
|
||
isTrailing: boolean;
|
||
trailingDistance?: number;
|
||
originalPrice?: number; // Para trailing
|
||
}
|
||
|
||
interface PositionTakeProfit {
|
||
price: number;
|
||
type: 'price' | 'percent';
|
||
}
|
||
```
|
||
|
||
### Resumen de Posiciones
|
||
|
||
```typescript
|
||
interface PositionsSummary {
|
||
totalPositions: number;
|
||
totalMarginUsed: number;
|
||
totalUnrealizedPnl: number;
|
||
totalUnrealizedPnlPercent: number;
|
||
marginRatio: number; // marginUsed / balance
|
||
|
||
byStatus: {
|
||
profitable: number;
|
||
unprofitable: number;
|
||
};
|
||
|
||
bySide: {
|
||
long: number;
|
||
short: number;
|
||
};
|
||
|
||
topGainer: Position | null;
|
||
topLoser: Position | null;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Reglas de Negocio
|
||
|
||
1. **Posiciones simultáneas:**
|
||
- MVP: 1 posición por símbolo
|
||
- Futuro: Múltiples posiciones (avg down/up)
|
||
|
||
2. **Cierre de posiciones:**
|
||
- Cierre parcial mínimo: 10%
|
||
- Siempre ejecutar a precio market
|
||
- Actualizar entrada promedio en parciales
|
||
|
||
3. **Stop Loss:**
|
||
- Debe estar por debajo del entry (long) o por encima (short)
|
||
- Mínimo 0.5% del entry price
|
||
- Máximo 50% del entry price
|
||
|
||
4. **Take Profit:**
|
||
- Debe estar por encima del entry (long) o por debajo (short)
|
||
- Mínimo 0.5% del entry price
|
||
- Sin límite máximo
|
||
|
||
5. **Liquidación:**
|
||
- Ocurre si pérdida > 80% del margen
|
||
- Notificar cuando margen < 20%
|
||
- Liquidar todo, no parcial
|
||
|
||
6. **Trailing Stop:**
|
||
- Solo se activa si posición en ganancia
|
||
- Se mueve solo hacia ganancia, nunca retrocede
|
||
- Mínima distancia: 0.5%
|
||
|
||
---
|
||
|
||
## Criterios de Aceptación
|
||
|
||
```gherkin
|
||
Escenario: Posición se crea al ejecutar orden
|
||
DADO que el usuario creó orden buy de 0.5 BTC a $50,000
|
||
CUANDO la orden se ejecuta
|
||
ENTONCES se crea posición long
|
||
Y entry price es $50,000
|
||
Y quantity es 0.5 BTC
|
||
Y aparece en panel de posiciones
|
||
|
||
Escenario: PnL se actualiza en tiempo real
|
||
DADO que el usuario tiene posición long con entry $50,000
|
||
Y quantity es 0.5 BTC
|
||
CUANDO el precio sube a $51,000
|
||
ENTONCES unrealized PnL muestra +$500 (+2%)
|
||
Y el valor se actualiza cada segundo
|
||
Y se muestra en color verde
|
||
|
||
Escenario: Usuario cierra posición parcialmente
|
||
DADO que el usuario tiene posición de 1 BTC
|
||
CUANDO cierra 50% de la posición
|
||
ENTONCES se ejecuta orden sell de 0.5 BTC
|
||
Y la posición se reduce a 0.5 BTC
|
||
Y se calcula PnL realizado de la mitad cerrada
|
||
Y se registra trade en historial
|
||
|
||
Escenario: Stop Loss se activa
|
||
DADO que el usuario tiene posición long entry $50,000
|
||
Y stop loss configurado en $48,000
|
||
CUANDO el precio baja a $48,000
|
||
ENTONCES se ejecuta orden market sell automática
|
||
Y la posición se cierra completamente
|
||
Y se registra PnL realizado de -4%
|
||
Y aparece notificación "Stop Loss activado"
|
||
|
||
Escenario: Trailing Stop se ajusta
|
||
DADO que el usuario tiene trailing stop con 5% de distancia
|
||
Y posición long entry $50,000
|
||
Y stop loss inicial $47,500
|
||
CUANDO el precio sube a $55,000
|
||
ENTONCES stop loss se actualiza a $52,250 (5% debajo)
|
||
Y se muestra en panel de posiciones
|
||
Y se preserva ganancia de +4.5%
|
||
|
||
Escenario: Advertencia de riesgo de liquidación
|
||
DADO que el usuario tiene posición con 80% de pérdida
|
||
CUANDO se actualiza el precio
|
||
ENTONCES aparece alerta "Riesgo de liquidación"
|
||
Y se destaca la posición en rojo
|
||
Y se sugiere añadir margen o cerrar
|
||
|
||
Escenario: Usuario modifica Stop Loss
|
||
DADO que el usuario tiene posición con SL en $48,000
|
||
CUANDO modifica SL a $49,000
|
||
ENTONCES el nuevo SL se guarda
|
||
Y se monitorea el nuevo nivel
|
||
Y aparece confirmación
|
||
```
|
||
|
||
---
|
||
|
||
## Interfaz de Usuario
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────────┐
|
||
│ OPEN POSITIONS │
|
||
├────────────────────────────────────────────────────────────────┤
|
||
│ Total: 3 │ Margin: $15,234 │ PnL: +$1,234 (+8.1%) │
|
||
├────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ Symbol │ Side │ Entry │ Current │ Qty │ PnL │ │
|
||
│──────────┼──────┼────────┼─────────┼────────┼──────────┼─────│
|
||
│ BTCUSDT │ LONG │ 50,000 │ 51,500 │ 0.5 │ +$750 │ ... │
|
||
│ │ │ │ │ │ (+3.0%) │ │
|
||
│ │ │ SL: $48,000 TP: $55,000 │ Close│
|
||
│──────────┼──────┼────────┼─────────┼────────┼──────────┼─────│
|
||
│ ETHUSDT │ LONG │ 3,200 │ 3,350 │ 2.0 │ +$300 │ ... │
|
||
│ │ │ │ │ │ (+4.7%) │ │
|
||
│ │ │ SL: None TP: $3,500 │ Close│
|
||
│──────────┼──────┼────────┼─────────┼────────┼──────────┼─────│
|
||
│ BNBUSDT │ LONG │ 450 │ 425 │ 10 │ -$250 │ ... │
|
||
│ │ │ │ │ │ (-5.6%) │ │
|
||
│ │ │ SL: $400 TP: None │ Close│
|
||
└────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Estados de Posición
|
||
|
||
```
|
||
┌──────────┐
|
||
│ OPEN │ ← Estado normal
|
||
└────┬─────┘
|
||
│
|
||
├─→ Profitable (PnL > 0)
|
||
├─→ Unprofitable (PnL < 0)
|
||
├─→ At Risk (loss > 50%)
|
||
│
|
||
├─→ [SL triggered] → CLOSING → CLOSED
|
||
├─→ [TP triggered] → CLOSING → CLOSED
|
||
├─→ [User closes] → CLOSING → CLOSED
|
||
└─→ [Liquidated] → LIQUIDATED
|
||
```
|
||
|
||
---
|
||
|
||
## Dependencias
|
||
|
||
- RF-TRD-005: Sistema de Órdenes (crear órdenes de cierre)
|
||
- RF-TRD-004: Paper Trading (balance y ejecución)
|
||
- RF-TRD-007: Historial (registrar trades cerrados)
|
||
- WebSocket para precios en tiempo real
|
||
|
||
---
|
||
|
||
## Notas Técnicas
|
||
|
||
- Actualizar PnL con debounce de 1 segundo
|
||
- Usar transacciones para cierres de posición
|
||
- Implementar locks para evitar double-close
|
||
- Cachear cálculos pesados (liquidation price)
|
||
- Guardar snapshots de precio cada minuto
|
||
- Implementar circuit breaker para liquidaciones masivas
|
||
- Usar Redis para monitoreo eficiente de SL/TP
|
||
- Indexar por userId y status para queries rápidas
|
||
|
||
---
|
||
|
||
## Cálculos Importantes
|
||
|
||
### PnL para Long Position
|
||
```
|
||
Unrealized PnL = (currentPrice - entryPrice) × quantity
|
||
Unrealized PnL % = ((currentPrice - entryPrice) / entryPrice) × 100
|
||
```
|
||
|
||
### PnL para Short Position (futuro)
|
||
```
|
||
Unrealized PnL = (entryPrice - currentPrice) × quantity
|
||
Unrealized PnL % = ((entryPrice - currentPrice) / entryPrice) × 100
|
||
```
|
||
|
||
### Liquidation Price (sin apalancamiento)
|
||
```
|
||
Liquidation Price = entryPrice × 0.2 (pérdida del 80%)
|
||
```
|
||
|
||
### Trailing Stop Loss Update
|
||
```
|
||
New SL = currentPrice × (1 - trailingDistance) // Para long
|
||
```
|
||
|
||
---
|
||
|
||
## Métricas a Trackear
|
||
|
||
- Posiciones abiertas concurrentes
|
||
- Tiempo promedio de holding
|
||
- Win rate (profitable / total)
|
||
- Profit factor (gross profit / gross loss)
|
||
- Activaciones de SL vs TP
|
||
- Liquidaciones (debería ser 0 idealmente)
|
||
- Máximo drawdown por posición
|