--- id: "RF-TRD-006" title: "Gestion de Posiciones" type: "Requirement" status: "Done" priority: "Alta" module: "trading" epic: "OQI-003" version: "1.0" created_date: "2025-12-05" updated_date: "2026-01-04" --- # 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