# ET-PFM-004: Motor de Rebalanceo

**Épica:** OQI-008 - Portfolio Manager
**Versión:** 1.0
**Fecha:** 2025-12-05
**Estado:** Planificado

---

## Arquitectura

```
┌─────────────────────────────────────────────────────────────┐
│                    REBALANCING ENGINE                        │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐    │
│  │  Allocation  │   │  Deviation   │   │    Plan      │    │
│  │   Manager    │──▶│  Calculator  │──▶│  Generator   │    │
│  └──────────────┘   └──────────────┘   └──────────────┘    │
│                                              │               │
│                                              ▼               │
│                                    ┌──────────────┐         │
│                                    │    Order     │         │
│                                    │   Executor   │         │
│                                    └──────────────┘         │
└─────────────────────────────────────────────────────────────┘
```

---

## Algoritmo de Rebalanceo

```typescript
@Injectable()
export class RebalanceService {

  async generatePlan(
    portfolioId: string,
    targetAllocation: Allocation[]
  ): Promise<RebalancePlan> {
    const positions = await this.portfolioService.getPositions(portfolioId);
    const totalValue = this.calculateTotalValue(positions);

    const orders: RebalanceOrder[] = [];

    // Calcular diferencias
    for (const target of targetAllocation) {
      const current = positions.find(p => p.symbol === target.symbol);
      const currentValue = current?.marketValue || 0;
      const targetValue = totalValue * (target.percent / 100);
      const difference = targetValue - currentValue;

      if (Math.abs(difference) > 100) { // Min $100 para operar
        orders.push({
          symbol: target.symbol,
          action: difference > 0 ? 'BUY' : 'SELL',
          amount: Math.abs(difference),
          currentAllocation: (currentValue / totalValue) * 100,
          targetAllocation: target.percent,
        });
      }
    }

    // Ordenar: ventas primero
    orders.sort((a, b) => {
      if (a.action === 'SELL' && b.action === 'BUY') return -1;
      if (a.action === 'BUY' && b.action === 'SELL') return 1;
      return b.amount - a.amount;
    });

    return {
      portfolioId,
      orders,
      estimatedCost: this.estimateCosts(orders),
      timestamp: new Date().toISOString(),
    };
  }

  async executePlan(planId: string): Promise<RebalanceResult> {
    const plan = await this.getPlan(planId);
    const results = [];

    for (const order of plan.orders) {
      const result = await this.orderService.createOrder({
        symbol: order.symbol,
        side: order.action.toLowerCase(),
        amount: order.amount,
        type: 'market',
      });
      results.push(result);
    }

    return {
      planId,
      executed: results.filter(r => r.success).length,
      failed: results.filter(r => !r.success).length,
      results,
    };
  }
}
```

---

## Referencias

- [RF-PFM-003: Rebalanceo](../requerimientos/RF-PFM-003-rebalanceo.md)

---

*Especificación técnica - Sistema NEXUS*
