300 lines
10 KiB
Markdown
300 lines
10 KiB
Markdown
# US-TRD-008: Cerrar Posición
|
|
|
|
## Metadata
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **ID** | US-TRD-008 |
|
|
| **Épica** | OQI-003 - Trading y Charts |
|
|
| **Módulo** | trading |
|
|
| **Prioridad** | P0 |
|
|
| **Story Points** | 3 |
|
|
| **Sprint** | Sprint 4 |
|
|
| **Estado** | Pendiente |
|
|
| **Asignado a** | Por asignar |
|
|
|
|
---
|
|
|
|
## Historia de Usuario
|
|
|
|
**Como** trader practicante,
|
|
**quiero** cerrar mis posiciones abiertas total o parcialmente,
|
|
**para** realizar mis ganancias o limitar mis pérdidas en el momento que decida.
|
|
|
|
## Descripción Detallada
|
|
|
|
El usuario debe poder cerrar posiciones long o short en cualquier momento, ya sea completamente o una porción específica. Al cerrar, se ejecuta una orden market en dirección opuesta y se calcula el P&L (profit and loss) realizado.
|
|
|
|
## Mockups/Wireframes
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ OPEN POSITIONS │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ Symbol Side Size Entry Current PnL Actions │
|
|
│ ────────────────────────────────────────────────────────────── │
|
|
│ BTCUSDT LONG 0.1 BTC $95,000 $97,234.50 +$223.45 [Close]│
|
|
│ (+2.35%) [ ▼ ] │
|
|
│ ────────────────────────────────────────────────────────────── │
|
|
│ │
|
|
│ ┌────────────────────────────────────┐ │
|
|
│ │ CLOSE POSITION │ │
|
|
│ ├────────────────────────────────────┤ │
|
|
│ │ Symbol: BTCUSDT │ │
|
|
│ │ Position: 0.1 BTC LONG │ │
|
|
│ │ │ │
|
|
│ │ Amount to close: │ │
|
|
│ │ ┌──────────────────────────────┐ │ │
|
|
│ │ │ 0.1 BTC │ │ │
|
|
│ │ └──────────────────────────────┘ │ │
|
|
│ │ [25%] [50%] [75%] [100%] │ │
|
|
│ │ │ │
|
|
│ │ Entry Price: $95,000.00 │ │
|
|
│ │ Current Price: $97,234.50 │ │
|
|
│ │ Est. P&L: +$223.45 (+2.35%) │ │
|
|
│ │ │ │
|
|
│ │ [Cancel] [Close Position] │ │
|
|
│ └────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Criterios de Aceptación
|
|
|
|
**Escenario 1: Cerrar posición long completa**
|
|
```gherkin
|
|
DADO que el usuario tiene posición long de 0.1 BTC en BTCUSDT
|
|
Y el precio de entrada fue $95,000
|
|
Y el precio actual es $97,234.50
|
|
CUANDO hace click en "Close" en la posición
|
|
Y selecciona cerrar 100% (0.1 BTC)
|
|
Y confirma
|
|
ENTONCES se ejecuta orden market SELL de 0.1 BTC
|
|
Y la posición se cierra completamente
|
|
Y se calcula P&L: +$223.45 (+2.35%)
|
|
Y el balance se incrementa en $9,723.45 ($9,500 + $223.45)
|
|
Y la posición desaparece de "Open Positions"
|
|
Y se muestra notificación "Position closed. P&L: +$223.45"
|
|
```
|
|
|
|
**Escenario 2: Cerrar posición short con ganancia**
|
|
```gherkin
|
|
DADO que el usuario tiene posición short de 0.05 BTC
|
|
Y el precio de entrada fue $97,000
|
|
Y el precio actual es $96,000
|
|
CUANDO cierra la posición completa
|
|
ENTONCES se ejecuta orden market BUY de 0.05 BTC
|
|
Y se calcula P&L: +$50.00 (+1.03%)
|
|
Y la posición se cierra
|
|
```
|
|
|
|
**Escenario 3: Cerrar posición parcial (50%)**
|
|
```gherkin
|
|
DADO que el usuario tiene posición long de 0.1 BTC
|
|
CUANDO hace click en "Close"
|
|
Y selecciona 50% (0.05 BTC)
|
|
Y confirma
|
|
ENTONCES se cierra solo 0.05 BTC
|
|
Y la posición restante es 0.05 BTC
|
|
Y el P&L parcial se registra
|
|
Y la posición sigue apareciendo con nuevo tamaño
|
|
```
|
|
|
|
**Escenario 4: Cerrar posición con pérdida**
|
|
```gherkin
|
|
DADO que el usuario tiene posición long a $97,000
|
|
Y el precio actual es $95,000
|
|
CUANDO cierra la posición
|
|
ENTONCES se calcula P&L: -$200.00 (-2.06%)
|
|
Y el balance se reduce en $200
|
|
Y se registra la pérdida en el historial
|
|
```
|
|
|
|
**Escenario 5: Confirmación antes de cerrar**
|
|
```gherkin
|
|
DADO que el usuario hace click en "Close"
|
|
CUANDO se abre el diálogo de confirmación
|
|
ENTONCES muestra:
|
|
- Cantidad a cerrar
|
|
- Precio de entrada vs actual
|
|
- P&L estimado
|
|
- Botón de confirmar
|
|
Y debe confirmar antes de ejecutar
|
|
```
|
|
|
|
## Criterios Adicionales
|
|
|
|
- [ ] Slippage aplicado al cerrar (±0.1%)
|
|
- [ ] Keyboard shortcut: Esc para cancelar
|
|
- [ ] Color verde para P&L positivo, rojo para negativo
|
|
- [ ] Mostrar ROI (Return on Investment) en %
|
|
- [ ] Registro en trade history
|
|
|
|
---
|
|
|
|
## Tareas Técnicas
|
|
|
|
**Database:**
|
|
- [ ] DB-TRD-012: Crear tabla paper_trade_history
|
|
- [ ] DB-TRD-013: Añadir campo closed_at a paper_positions
|
|
|
|
**Backend:**
|
|
- [ ] BE-TRD-038: Crear endpoint POST /trading/paper/positions/:id/close
|
|
- [ ] BE-TRD-039: Implementar PositionService.closePosition()
|
|
- [ ] BE-TRD-040: Implementar cálculo de P&L
|
|
- [ ] BE-TRD-041: Implementar cierre parcial
|
|
- [ ] BE-TRD-042: Actualizar BalanceService con P&L
|
|
- [ ] BE-TRD-043: Crear registro en trade_history
|
|
|
|
**Frontend:**
|
|
- [ ] FE-TRD-038: Crear componente OpenPositionsPanel.tsx
|
|
- [ ] FE-TRD-039: Crear componente ClosePositionDialog.tsx
|
|
- [ ] FE-TRD-040: Crear componente PositionRow.tsx con P&L
|
|
- [ ] FE-TRD-041: Implementar hook useClosePosition
|
|
- [ ] FE-TRD-042: Actualizar positionStore con cierres
|
|
- [ ] FE-TRD-043: Implementar animación al cerrar
|
|
|
|
**Tests:**
|
|
- [ ] TEST-TRD-019: Test unitario cálculo P&L
|
|
- [ ] TEST-TRD-020: Test integración cerrar posición
|
|
- [ ] TEST-TRD-021: Test E2E flujo completo cierre
|
|
|
|
---
|
|
|
|
## Dependencias
|
|
|
|
**Depende de:**
|
|
- [ ] US-TRD-006: Crear orden market - Estado: Pendiente (necesita posiciones abiertas)
|
|
|
|
**Bloquea:**
|
|
- [ ] US-TRD-010: Ver historial de trades
|
|
|
|
---
|
|
|
|
## Notas Técnicas
|
|
|
|
**Endpoints involucrados:**
|
|
| Método | Endpoint | Descripción |
|
|
|--------|----------|-------------|
|
|
| POST | /trading/paper/positions/:id/close | Cerrar posición |
|
|
| GET | /trading/paper/positions | Listar posiciones abiertas |
|
|
| GET | /trading/paper/positions/:id | Obtener detalles de posición |
|
|
|
|
**Entidades/Tablas:**
|
|
```sql
|
|
CREATE TABLE trading.paper_trade_history (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id),
|
|
symbol VARCHAR(20) NOT NULL,
|
|
side VARCHAR(10) NOT NULL,
|
|
quantity DECIMAL(20, 8) NOT NULL,
|
|
entry_price DECIMAL(20, 8) NOT NULL,
|
|
exit_price DECIMAL(20, 8) NOT NULL,
|
|
pnl DECIMAL(20, 8) NOT NULL,
|
|
pnl_percentage DECIMAL(10, 4) NOT NULL,
|
|
opened_at TIMESTAMP NOT NULL,
|
|
closed_at TIMESTAMP DEFAULT NOW(),
|
|
duration_seconds INTEGER
|
|
);
|
|
|
|
ALTER TABLE trading.paper_positions ADD COLUMN closed_at TIMESTAMP;
|
|
ALTER TABLE trading.paper_positions ADD COLUMN status VARCHAR(20) DEFAULT 'open';
|
|
```
|
|
|
|
**Componentes UI:**
|
|
- `OpenPositionsPanel`: Panel de posiciones abiertas
|
|
- `ClosePositionDialog`: Modal de confirmación
|
|
- `PositionRow`: Fila con datos de posición
|
|
- `PnLBadge`: Badge con P&L coloreado
|
|
|
|
**Request Body:**
|
|
```typescript
|
|
{
|
|
quantity: 0.05, // Cantidad a cerrar (o null para cerrar todo)
|
|
percentage: 50 // O porcentaje (25, 50, 75, 100)
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```typescript
|
|
{
|
|
trade: {
|
|
id: "uuid",
|
|
symbol: "BTCUSDT",
|
|
side: "long",
|
|
quantity: 0.05,
|
|
entryPrice: 95000.00,
|
|
exitPrice: 97234.50,
|
|
pnl: 111.73,
|
|
pnlPercentage: 2.35,
|
|
openedAt: "2025-12-05T09:00:00Z",
|
|
closedAt: "2025-12-05T10:30:00Z",
|
|
durationSeconds: 5400
|
|
},
|
|
position: {
|
|
id: "uuid",
|
|
remainingQuantity: 0.05, // Si cierre parcial
|
|
status: "open" // o "closed"
|
|
},
|
|
balance: {
|
|
balance: 9611.73,
|
|
equity: 9723.46,
|
|
marginUsed: 4762.50
|
|
}
|
|
}
|
|
```
|
|
|
|
**Cálculo de P&L:**
|
|
```typescript
|
|
// Para posición LONG
|
|
const pnl = (exitPrice - entryPrice) * quantity;
|
|
const pnlPercentage = ((exitPrice - entryPrice) / entryPrice) * 100;
|
|
|
|
// Para posición SHORT
|
|
const pnl = (entryPrice - exitPrice) * quantity;
|
|
const pnlPercentage = ((entryPrice - exitPrice) / entryPrice) * 100;
|
|
|
|
// Aplicar slippage (simulación)
|
|
const slippage = 0.001; // 0.1%
|
|
const actualExitPrice = side === 'long'
|
|
? currentPrice * (1 - slippage)
|
|
: currentPrice * (1 + slippage);
|
|
```
|
|
|
|
---
|
|
|
|
## Definition of Ready (DoR)
|
|
|
|
- [x] Historia claramente escrita
|
|
- [x] Criterios de aceptación definidos
|
|
- [x] Story points estimados
|
|
- [x] Dependencias identificadas
|
|
- [x] Sin bloqueadores
|
|
- [ ] Diseño/mockup disponible
|
|
- [ ] API spec disponible
|
|
|
|
## Definition of Done (DoD)
|
|
|
|
- [ ] Código implementado según criterios
|
|
- [ ] Tests unitarios escritos y pasando
|
|
- [ ] Tests de integración pasando
|
|
- [ ] Code review aprobado
|
|
- [ ] Documentación actualizada
|
|
- [ ] QA aprobado
|
|
- [ ] Desplegado en ambiente de pruebas
|
|
|
|
---
|
|
|
|
## Historial de Cambios
|
|
|
|
| Fecha | Cambio | Autor |
|
|
|-------|--------|-------|
|
|
| 2025-12-05 | Creación | Requirements-Analyst |
|
|
|
|
---
|
|
|
|
**Creada por:** Requirements-Analyst
|
|
**Fecha:** 2025-12-05
|
|
**Última actualización:** 2025-12-05
|