---
id: "ET-LLM-003"
title: "Motor de Generación de Estrategias"
type: "Technical Specification"
status: "Done"
priority: "Alta"
epic: "OQI-007"
project: "trading-platform"
version: "1.0.0"
created_date: "2025-12-05"
updated_date: "2026-01-04"
---

# ET-LLM-003: Motor de Generación de Estrategias

**Épica:** OQI-007 - LLM Strategy Agent
**Versión:** 1.0
**Fecha:** 2025-12-05
**Estado:** Planificado
**Prioridad:** P0 - Crítico

---

## Resumen

Esta especificación define la arquitectura del motor de generación de estrategias de trading personalizadas, incluyendo análisis de perfil de usuario, cálculo de position sizing y gestión de riesgo.

---

## Arquitectura del Motor

```
┌─────────────────────────────────────────────────────────────────────────┐
│                       STRATEGY GENERATION ENGINE                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐   │
│  │  User Profile   │     │ Market Context  │     │  ML Signals     │   │
│  │  Analyzer       │     │  Builder        │     │  Integrator     │   │
│  └────────┬────────┘     └────────┬────────┘     └────────┬────────┘   │
│           │                       │                       │             │
│           └───────────────────────┼───────────────────────┘             │
│                                   ↓                                      │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │                     Strategy Generator                             │  │
│  │                                                                    │  │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐  │  │
│  │  │  Template   │ │  Position   │ │    Risk     │ │  Backtest   │  │  │
│  │  │  Selector   │ │   Sizer     │ │  Calculator │ │  Validator  │  │  │
│  │  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘  │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                   │                                      │
│                                   ↓                                      │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │                    Strategy Output Formatter                       │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## Componentes del Sistema

### 1. User Profile Analyzer

```typescript
// src/modules/copilot/strategy/profile-analyzer.ts

interface UserProfile {
  riskLevel: 'conservative' | 'moderate' | 'aggressive';
  experience: 'beginner' | 'intermediate' | 'advanced';
  capital: number;
  maxPositions: number;
  restrictions: string[];
  tradingStyle: 'day' | 'swing' | 'position';
  accountAge: number; // días
}

interface ProfileConstraints {
  maxRiskPerTrade: number;      // % del capital
  maxPositionSize: number;      // % del capital
  allowedStrategies: string[];
  forbiddenAssets: string[];
  minExperience: number;        // meses requeridos
}

export class ProfileAnalyzer {
  getConstraints(profile: UserProfile): ProfileConstraints {
    const constraints: ProfileConstraints = {
      maxRiskPerTrade: this.calculateMaxRisk(profile),
      maxPositionSize: this.calculateMaxPosition(profile),
      allowedStrategies: this.getAllowedStrategies(profile),
      forbiddenAssets: profile.restrictions,
      minExperience: 0,
    };

    // Restricciones por nivel de riesgo
    switch (profile.riskLevel) {
      case 'conservative':
        constraints.maxRiskPerTrade = Math.min(constraints.maxRiskPerTrade, 0.02);
        constraints.maxPositionSize = Math.min(constraints.maxPositionSize, 0.10);
        break;
      case 'moderate':
        constraints.maxRiskPerTrade = Math.min(constraints.maxRiskPerTrade, 0.05);
        constraints.maxPositionSize = Math.min(constraints.maxPositionSize, 0.20);
        break;
      case 'aggressive':
        constraints.maxRiskPerTrade = Math.min(constraints.maxRiskPerTrade, 0.10);
        constraints.maxPositionSize = Math.min(constraints.maxPositionSize, 0.30);
        break;
    }

    // Restricciones por experiencia
    if (profile.experience === 'beginner') {
      constraints.allowedStrategies = constraints.allowedStrategies.filter(
        s => !['scalping', 'options', 'leveraged'].includes(s)
      );
    }

    // Restricciones por antigüedad de cuenta
    if (profile.accountAge < 180) { // 6 meses
      constraints.allowedStrategies = constraints.allowedStrategies.filter(
        s => s !== 'leveraged'
      );
    }

    return constraints;
  }

  private calculateMaxRisk(profile: UserProfile): number {
    const baseRisk = {
      conservative: 0.01,
      moderate: 0.03,
      aggressive: 0.05,
    };
    return baseRisk[profile.riskLevel];
  }

  private calculateMaxPosition(profile: UserProfile): number {
    const basePosition = {
      conservative: 0.05,
      moderate: 0.15,
      aggressive: 0.25,
    };
    return basePosition[profile.riskLevel];
  }

  private getAllowedStrategies(profile: UserProfile): string[] {
    const strategies = ['trend_following', 'mean_reversion', 'breakout'];

    if (profile.experience !== 'beginner') {
      strategies.push('momentum', 'swing');
    }

    if (profile.experience === 'advanced') {
      strategies.push('scalping', 'options');
    }

    return strategies;
  }
}
```

### 2. Strategy Templates

```typescript
// src/modules/copilot/strategy/templates.ts

interface StrategyTemplate {
  id: string;
  name: string;
  description: string;
  difficulty: 'easy' | 'intermediate' | 'advanced';
  style: 'day' | 'swing' | 'position';
  minExperience: 'beginner' | 'intermediate' | 'advanced';
  requiredIndicators: string[];
  entryConditions: EntryCondition[];
  exitConditions: ExitCondition[];
  riskManagement: RiskManagement;
}

export const STRATEGY_TEMPLATES: StrategyTemplate[] = [
  {
    id: 'sma_crossover',
    name: 'SMA Crossover',
    description: 'Cruce de medias móviles simples (20/50)',
    difficulty: 'easy',
    style: 'swing',
    minExperience: 'beginner',
    requiredIndicators: ['SMA'],
    entryConditions: [
      {
        type: 'crossover',
        indicator1: { name: 'SMA', period: 20 },
        indicator2: { name: 'SMA', period: 50 },
        direction: 'above', // SMA20 cruza por encima de SMA50
        action: 'long',
      },
      {
        type: 'crossover',
        indicator1: { name: 'SMA', period: 20 },
        indicator2: { name: 'SMA', period: 50 },
        direction: 'below',
        action: 'short',
      },
    ],
    exitConditions: [
      {
        type: 'stop_loss',
        method: 'atr_multiple',
        value: 2,
      },
      {
        type: 'take_profit',
        method: 'risk_reward',
        value: 2, // 2:1 R:R
      },
    ],
    riskManagement: {
      maxRiskPerTrade: 0.02,
      positionSizing: 'fixed_risk',
    },
  },
  {
    id: 'rsi_oversold',
    name: 'RSI Oversold Bounce',
    description: 'Comprar cuando RSI está sobrevendido en tendencia alcista',
    difficulty: 'easy',
    style: 'swing',
    minExperience: 'beginner',
    requiredIndicators: ['RSI', 'SMA'],
    entryConditions: [
      {
        type: 'threshold',
        indicator: { name: 'RSI', period: 14 },
        condition: 'below',
        value: 30,
        action: 'watch',
      },
      {
        type: 'threshold',
        indicator: { name: 'RSI', period: 14 },
        condition: 'crosses_above',
        value: 30,
        action: 'long',
        filter: {
          indicator: { name: 'SMA', period: 200 },
          priceAbove: true, // Precio sobre SMA200 (tendencia alcista)
        },
      },
    ],
    exitConditions: [
      {
        type: 'threshold',
        indicator: { name: 'RSI', period: 14 },
        condition: 'above',
        value: 70,
      },
      {
        type: 'stop_loss',
        method: 'percent',
        value: 0.03, // 3%
      },
    ],
    riskManagement: {
      maxRiskPerTrade: 0.02,
      positionSizing: 'fixed_risk',
    },
  },
  {
    id: 'bollinger_squeeze',
    name: 'Bollinger Band Squeeze',
    description: 'Breakout después de contracción de volatilidad',
    difficulty: 'intermediate',
    style: 'swing',
    minExperience: 'intermediate',
    requiredIndicators: ['BB', 'ATR'],
    entryConditions: [
      {
        type: 'squeeze',
        indicator: { name: 'BB', period: 20, stddev: 2 },
        condition: 'width_below',
        value: 0.04, // BB width < 4%
        action: 'prepare',
      },
      {
        type: 'breakout',
        indicator: { name: 'BB', period: 20, stddev: 2 },
        condition: 'close_above_upper',
        volumeConfirm: true,
        action: 'long',
      },
    ],
    exitConditions: [
      {
        type: 'trailing_stop',
        method: 'atr_multiple',
        value: 2,
      },
      {
        type: 'time_exit',
        maxBars: 10, // Máximo 10 velas
      },
    ],
    riskManagement: {
      maxRiskPerTrade: 0.03,
      positionSizing: 'volatility_adjusted',
    },
  },
  {
    id: 'macd_divergence',
    name: 'MACD Divergence',
    description: 'Detectar divergencias entre precio y MACD',
    difficulty: 'advanced',
    style: 'swing',
    minExperience: 'intermediate',
    requiredIndicators: ['MACD', 'RSI'],
    entryConditions: [
      {
        type: 'divergence',
        indicator: { name: 'MACD' },
        priceCondition: 'lower_low',
        indicatorCondition: 'higher_low',
        action: 'long',
        confirmation: {
          indicator: { name: 'MACD' },
          condition: 'histogram_positive',
        },
      },
    ],
    exitConditions: [
      {
        type: 'signal',
        indicator: { name: 'MACD' },
        condition: 'signal_crossover_down',
      },
      {
        type: 'stop_loss',
        method: 'swing_low',
        buffer: 0.005,
      },
    ],
    riskManagement: {
      maxRiskPerTrade: 0.02,
      positionSizing: 'fixed_risk',
    },
  },
  {
    id: 'ml_momentum',
    name: 'ML-Enhanced Momentum',
    description: 'Momentum con confirmación de señales ML',
    difficulty: 'intermediate',
    style: 'swing',
    minExperience: 'intermediate',
    requiredIndicators: ['RSI', 'MACD', 'ML_SIGNAL'],
    entryConditions: [
      {
        type: 'ml_signal',
        direction: 'bullish',
        minConfidence: 0.65,
        action: 'watch',
      },
      {
        type: 'momentum',
        indicator: { name: 'RSI', period: 14 },
        condition: 'between',
        range: [40, 60],
        action: 'prepare',
      },
      {
        type: 'confirmation',
        indicator: { name: 'MACD' },
        condition: 'histogram_rising',
        action: 'long',
      },
    ],
    exitConditions: [
      {
        type: 'ml_signal',
        direction: 'bearish',
        minConfidence: 0.60,
      },
      {
        type: 'trailing_stop',
        method: 'percent',
        value: 0.05,
      },
    ],
    riskManagement: {
      maxRiskPerTrade: 0.025,
      positionSizing: 'kelly_fraction',
      kellyFraction: 0.25,
    },
  },
];
```

### 3. Position Sizer

```typescript
// src/modules/copilot/strategy/position-sizer.ts

interface PositionSizingParams {
  capital: number;
  entryPrice: number;
  stopLossPrice: number;
  maxRiskPercent: number;
  method: 'fixed_risk' | 'volatility_adjusted' | 'kelly_fraction';
  volatility?: number; // ATR
  winRate?: number;    // Para Kelly
  avgWinLoss?: number; // Para Kelly
}

interface PositionSize {
  shares: number;
  totalValue: number;
  riskAmount: number;
  percentOfCapital: number;
}

export class PositionSizer {
  calculate(params: PositionSizingParams): PositionSize {
    switch (params.method) {
      case 'fixed_risk':
        return this.fixedRisk(params);
      case 'volatility_adjusted':
        return this.volatilityAdjusted(params);
      case 'kelly_fraction':
        return this.kellyFraction(params);
      default:
        return this.fixedRisk(params);
    }
  }

  private fixedRisk(params: PositionSizingParams): PositionSize {
    const { capital, entryPrice, stopLossPrice, maxRiskPercent } = params;

    const riskPerShare = Math.abs(entryPrice - stopLossPrice);
    const maxRiskAmount = capital * maxRiskPercent;
    const shares = Math.floor(maxRiskAmount / riskPerShare);
    const totalValue = shares * entryPrice;

    return {
      shares,
      totalValue,
      riskAmount: shares * riskPerShare,
      percentOfCapital: (totalValue / capital) * 100,
    };
  }

  private volatilityAdjusted(params: PositionSizingParams): PositionSize {
    const { capital, entryPrice, maxRiskPercent, volatility } = params;

    if (!volatility) {
      return this.fixedRisk(params);
    }

    // Ajustar posición inversamente a la volatilidad
    const baseAllocation = capital * 0.10; // 10% base
    const volatilityFactor = 0.02 / volatility; // Normalizar a 2% ATR
    const adjustedAllocation = baseAllocation * Math.min(volatilityFactor, 2);

    const shares = Math.floor(adjustedAllocation / entryPrice);
    const stopLossDistance = volatility * 2;

    return {
      shares,
      totalValue: shares * entryPrice,
      riskAmount: shares * stopLossDistance,
      percentOfCapital: (shares * entryPrice / capital) * 100,
    };
  }

  private kellyFraction(params: PositionSizingParams): PositionSize {
    const { capital, entryPrice, winRate, avgWinLoss, maxRiskPercent } = params;

    if (!winRate || !avgWinLoss) {
      return this.fixedRisk(params);
    }

    // Kelly Criterion: f* = (bp - q) / b
    // b = ratio win/loss, p = win rate, q = 1 - p
    const b = avgWinLoss;
    const p = winRate;
    const q = 1 - p;

    let kellyPercent = (b * p - q) / b;
    kellyPercent = Math.max(0, Math.min(kellyPercent, maxRiskPercent));
    kellyPercent *= 0.25; // Usar solo 25% del Kelly (half-Kelly)

    const allocation = capital * kellyPercent;
    const shares = Math.floor(allocation / entryPrice);

    return {
      shares,
      totalValue: shares * entryPrice,
      riskAmount: allocation * 0.5, // Estimado
      percentOfCapital: kellyPercent * 100,
    };
  }
}
```

### 4. Risk Calculator

```typescript
// src/modules/copilot/strategy/risk-calculator.ts

interface RiskMetrics {
  riskAmount: number;
  riskPercent: number;
  rewardAmount: number;
  rewardPercent: number;
  riskRewardRatio: number;
  breakEvenWinRate: number;
  expectedValue: number;
}

interface StopLossParams {
  method: 'percent' | 'atr_multiple' | 'swing_low' | 'support_level';
  value: number;
  entryPrice: number;
  atr?: number;
  swingLow?: number;
  supportLevel?: number;
}

export class RiskCalculator {
  calculateStopLoss(params: StopLossParams): number {
    const { method, value, entryPrice, atr, swingLow, supportLevel } = params;

    switch (method) {
      case 'percent':
        return entryPrice * (1 - value);

      case 'atr_multiple':
        if (!atr) throw new Error('ATR required for atr_multiple method');
        return entryPrice - (atr * value);

      case 'swing_low':
        if (!swingLow) throw new Error('Swing low required');
        return swingLow * (1 - value); // value es buffer %

      case 'support_level':
        if (!supportLevel) throw new Error('Support level required');
        return supportLevel * (1 - value);

      default:
        return entryPrice * 0.95; // Default 5%
    }
  }

  calculateTakeProfit(
    entryPrice: number,
    stopLoss: number,
    riskRewardRatio: number,
  ): number {
    const riskPerShare = entryPrice - stopLoss;
    const rewardPerShare = riskPerShare * riskRewardRatio;
    return entryPrice + rewardPerShare;
  }

  calculateMetrics(
    entryPrice: number,
    stopLoss: number,
    takeProfit: number,
    positionSize: number,
    estimatedWinRate?: number,
  ): RiskMetrics {
    const riskAmount = (entryPrice - stopLoss) * positionSize;
    const rewardAmount = (takeProfit - entryPrice) * positionSize;
    const riskRewardRatio = rewardAmount / riskAmount;
    const breakEvenWinRate = 1 / (1 + riskRewardRatio);

    let expectedValue = 0;
    if (estimatedWinRate) {
      expectedValue = (estimatedWinRate * rewardAmount) -
                      ((1 - estimatedWinRate) * riskAmount);
    }

    return {
      riskAmount,
      riskPercent: (riskAmount / (entryPrice * positionSize)) * 100,
      rewardAmount,
      rewardPercent: (rewardAmount / (entryPrice * positionSize)) * 100,
      riskRewardRatio,
      breakEvenWinRate,
      expectedValue,
    };
  }
}
```

### 5. Strategy Generator

```typescript
// src/modules/copilot/strategy/strategy-generator.ts

interface GenerateStrategyParams {
  symbol: string;
  userProfile: UserProfile;
  userPlan: 'free' | 'pro' | 'premium';
  marketData: MarketData;
  mlSignal?: MLSignal;
  preferredStyle?: 'day' | 'swing' | 'position';
}

interface GeneratedStrategy {
  template: StrategyTemplate;
  symbol: string;
  entry: {
    price: number;
    condition: string;
    confidence: number;
  };
  stopLoss: {
    price: number;
    percent: number;
    method: string;
  };
  takeProfit: {
    price: number;
    percent: number;
    riskRewardRatio: number;
  };
  positionSize: PositionSize;
  riskMetrics: RiskMetrics;
  explanation: string;
  invalidationConditions: string[];
  disclaimer: string;
}

@Injectable()
export class StrategyGenerator {
  constructor(
    private readonly profileAnalyzer: ProfileAnalyzer,
    private readonly positionSizer: PositionSizer,
    private readonly riskCalculator: RiskCalculator,
    private readonly marketDataService: MarketDataService,
    private readonly mlService: MLSignalService,
  ) {}

  async generate(params: GenerateStrategyParams): Promise<GeneratedStrategy> {
    const { symbol, userProfile, userPlan, marketData, mlSignal } = params;

    // 1. Get profile constraints
    const constraints = this.profileAnalyzer.getConstraints(userProfile);

    // 2. Filter applicable templates
    const applicableTemplates = this.filterTemplates(
      STRATEGY_TEMPLATES,
      constraints,
      userPlan,
      params.preferredStyle,
    );

    // 3. Score and select best template
    const scoredTemplates = await this.scoreTemplates(
      applicableTemplates,
      marketData,
      mlSignal,
    );

    const selectedTemplate = scoredTemplates[0].template;

    // 4. Calculate entry/exit levels
    const { entry, stopLoss, takeProfit } = await this.calculateLevels(
      selectedTemplate,
      symbol,
      marketData,
    );

    // 5. Calculate position size
    const positionSize = this.positionSizer.calculate({
      capital: userProfile.capital,
      entryPrice: entry.price,
      stopLossPrice: stopLoss.price,
      maxRiskPercent: constraints.maxRiskPerTrade,
      method: selectedTemplate.riskManagement.positionSizing,
      volatility: marketData.atr,
    });

    // 6. Calculate risk metrics
    const riskMetrics = this.riskCalculator.calculateMetrics(
      entry.price,
      stopLoss.price,
      takeProfit.price,
      positionSize.shares,
      mlSignal?.confidence,
    );

    // 7. Generate explanation
    const explanation = this.generateExplanation(
      selectedTemplate,
      entry,
      stopLoss,
      takeProfit,
      marketData,
      mlSignal,
    );

    return {
      template: selectedTemplate,
      symbol,
      entry,
      stopLoss,
      takeProfit,
      positionSize,
      riskMetrics,
      explanation,
      invalidationConditions: this.getInvalidationConditions(selectedTemplate),
      disclaimer: this.getDisclaimer(),
    };
  }

  private filterTemplates(
    templates: StrategyTemplate[],
    constraints: ProfileConstraints,
    userPlan: string,
    preferredStyle?: string,
  ): StrategyTemplate[] {
    return templates.filter(t => {
      // Check if strategy is allowed
      if (!constraints.allowedStrategies.includes(t.id)) return false;

      // Check ML requirement
      if (t.requiredIndicators.includes('ML_SIGNAL') && userPlan === 'free') {
        return false;
      }

      // Check style preference
      if (preferredStyle && t.style !== preferredStyle) return false;

      return true;
    });
  }

  private async scoreTemplates(
    templates: StrategyTemplate[],
    marketData: MarketData,
    mlSignal?: MLSignal,
  ): Promise<Array<{ template: StrategyTemplate; score: number }>> {
    const scored = templates.map(template => {
      let score = 50; // Base score

      // Score based on market conditions
      if (marketData.trend === 'bullish' && template.id.includes('momentum')) {
        score += 20;
      }

      if (marketData.volatility > 0.03 && template.id.includes('breakout')) {
        score += 15;
      }

      // Score based on ML alignment
      if (mlSignal && template.requiredIndicators.includes('ML_SIGNAL')) {
        if (mlSignal.confidence > 0.7) {
          score += 25;
        } else if (mlSignal.confidence > 0.5) {
          score += 10;
        }
      }

      return { template, score };
    });

    return scored.sort((a, b) => b.score - a.score);
  }

  private generateExplanation(
    template: StrategyTemplate,
    entry: any,
    stopLoss: any,
    takeProfit: any,
    marketData: MarketData,
    mlSignal?: MLSignal,
  ): string {
    let explanation = `La estrategia "${template.name}" es adecuada para las condiciones actuales.\n\n`;

    explanation += `**Por qué esta estrategia:** ${template.description}\n\n`;

    explanation += `**Entrada en $${entry.price.toFixed(2)}:** ${entry.condition}\n\n`;

    explanation += `**Stop Loss en $${stopLoss.price.toFixed(2)} (${stopLoss.percent.toFixed(1)}%):** `;
    explanation += `Usando método ${stopLoss.method} para proteger el capital.\n\n`;

    explanation += `**Take Profit en $${takeProfit.price.toFixed(2)} (R:R ${takeProfit.riskRewardRatio.toFixed(1)}:1):** `;
    explanation += `Objetivo basado en resistencia/proyección técnica.\n\n`;

    if (mlSignal) {
      explanation += `**Confirmación ML:** El modelo predice movimiento ${mlSignal.prediction} `;
      explanation += `con ${(mlSignal.confidence * 100).toFixed(0)}% de confianza.\n\n`;
    }

    return explanation;
  }

  private getInvalidationConditions(template: StrategyTemplate): string[] {
    return [
      `El precio rompe el nivel de stop loss`,
      `Cambio de tendencia en timeframe mayor`,
      `Volumen anormalmente bajo en breakout`,
      `Noticia de alto impacto contradictoria`,
    ];
  }

  private getDisclaimer(): string {
    return `⚠️ Esta sugerencia es informativa y no constituye asesoría financiera. ` +
           `El trading implica riesgos significativos. Opera bajo tu propio criterio y riesgo. ` +
           `Nunca inviertas más de lo que puedes permitirte perder.`;
  }
}
```

---

## Integración con LLM Agent

```typescript
// Tool definition para el agente
const generateStrategyTool = {
  type: 'function',
  function: {
    name: 'generate_strategy',
    description: 'Genera una estrategia de trading personalizada para un símbolo',
    parameters: {
      type: 'object',
      properties: {
        symbol: {
          type: 'string',
          description: 'Símbolo del activo',
        },
        style: {
          type: 'string',
          enum: ['day', 'swing', 'position'],
          description: 'Estilo de trading preferido (opcional)',
        },
      },
      required: ['symbol'],
    },
  },
};

// Implementación del tool
async function generateStrategy(
  symbol: string,
  userId: string,
  userPlan: string,
  style?: string,
): Promise<GeneratedStrategy> {
  const userProfile = await userService.getProfile(userId);
  const marketData = await marketDataService.getFullContext(symbol);
  const mlSignal = userPlan !== 'free'
    ? await mlService.getPrediction(symbol)
    : undefined;

  const strategy = await strategyGenerator.generate({
    symbol,
    userProfile,
    userPlan,
    marketData,
    mlSignal,
    preferredStyle: style,
  });

  return strategy;
}
```

---

## Dependencias

### Módulos Internos
- MarketDataService (OQI-003)
- MLSignalService (OQI-006)
- UserService (OQI-001)

### Bibliotecas
- technicalindicators (cálculo de indicadores)
- decimal.js (precisión en cálculos financieros)

---

## Referencias

- [RF-LLM-003: Sugerencias de Estrategias](../requerimientos/RF-LLM-003-strategy-suggestions.md)
- [ET-LLM-002: Agente de Análisis](./ET-LLM-002-agente-analisis.md)

---

*Especificación técnica - Sistema NEXUS*
*Trading Platform*
