trading-platform/docs/02-definicion-modulos/OQI-008-portfolio-manager/especificaciones/ET-PFM-004-motor-rebalanceo.md

4.0 KiB

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

@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


Especificación técnica - Sistema NEXUS