---
title: "Análisis de Modelos ML - Vuelta 2"
version: "1.0.0"
date: "2026-01-06"
status: "In Progress"
author: "ML-Specialist + Orquestador"
epic: "OQI-006"
tags: ["ml", "analysis", "architecture", "integration", "refactoring"]
---

# ANÁLISIS DE MODELOS ML - VUELTA 2

## 1. RESUMEN DE HALLAZGOS VUELTA 1

### 1.1 Brechas Críticas Identificadas
| Brecha | Severidad | Estado Real |
|--------|-----------|-------------|
| Factores hardcodeados | CRÍTICA | **PARCIALMENTE RESUELTO** - `SYMBOL_CONFIGS` existe |
| Sin separación activo/timeframe | ALTA | **RESUELTO** - `SymbolTimeframeTrainer` existe |
| Atención no integrada | ALTA | **RESUELTO** - `use_dynamic_factor_weighting=True` |
| Win rate bajo | CRÍTICA | Pendiente optimización |
| R:R insuficiente | ALTA | Pendiente ajuste filtros |

### 1.2 Cambio de Diagnóstico
**Vuelta 1**: Creíamos que faltaba infraestructura
**Vuelta 2**: El problema es **integración y uso** de infraestructura existente

---

## 2. INFRAESTRUCTURA EXISTENTE DESCUBIERTA

### 2.1 Sistema de Configuración

**Archivo**: `config/trading.yaml`
```yaml
symbols:
  primary: ["XAUUSD", "EURUSD", "GBPUSD", "BTCUSD"]
  secondary: ["USDJPY", "GBPJPY", "AUDUSD", "NZDUSD"]

timeframes:
  primary: 5
  aggregations: [15, 30, 60, 240]
```

**Archivo**: `config/models.yaml`
```yaml
xgboost:
  base:
    n_estimators: 200
    max_depth: 5
    learning_rate: 0.05
    # ... configuración completa

meta_model:
  type: "xgboost"
  # Soporte para metamodelo ya existe
```

### 2.2 Trainer por Símbolo/Timeframe

**Archivo**: `training/symbol_timeframe_trainer.py`

```python
SYMBOL_CONFIGS = {
    'XAUUSD': SymbolConfig(symbol='XAUUSD', base_factor=5.0, pip_value=0.01),
    'BTCUSD': SymbolConfig(symbol='BTCUSD', base_factor=100.0, pip_value=0.01),
    'EURUSD': SymbolConfig(symbol='EURUSD', base_factor=0.0005, pip_value=0.0001),
    'GBPUSD': SymbolConfig(symbol='GBPUSD', base_factor=0.0006, pip_value=0.0001),
    'USDJPY': SymbolConfig(symbol='USDJPY', base_factor=0.05, pip_value=0.01),
}

@dataclass
class TrainerConfig:
    timeframes: List[str] = ['5m', '15m']
    symbols: List[str] = ['XAUUSD', 'BTCUSD', 'EURUSD']
    use_dynamic_factor_weighting: bool = True  # ✅ Ya habilitado
    use_atr_weighting: bool = True             # ✅ Ya habilitado
    factor_window: int = 200
    softplus_beta: float = 4.0
    softplus_w_max: float = 3.0
```

### 2.3 Modelos Ya Entrenados Separados

**Ubicación**: `models/ml_first/`
```
ml_first/
└── XAUUSD/
    └── movement_predictor/
        ├── 5m_15min/
        │   └── metadata.yaml
        └── 15m_60min/
            └── metadata.yaml
```

---

## 3. ANÁLISIS DE BRECHAS REAL

### 3.1 El Problema: Fragmentación de Código

**Código Nuevo (Correcto)** - `symbol_timeframe_trainer.py`:
```python
SYMBOL_CONFIGS = {
    'XAUUSD': SymbolConfig(symbol='XAUUSD', base_factor=5.0, ...),
    # Factores dinámicos por símbolo
}
```

**Código Legacy (Incorrecto)** - `range_predictor_factor.py:598-601`:
```python
SYMBOLS = {
    'XAUUSD': {'base': 2650.0, 'volatility': 0.0012, 'factor': 2.5},
    'EURUSD': {'base': 1.0420, 'volatility': 0.0004, 'factor': 0.0003},
}
# Hardcoded, solo 2 símbolos, valores estáticos
```

### 3.2 Dependencias No Conectadas

```
symbol_timeframe_trainer.py ──✅─→ DynamicFactorWeighter
       │
       └── ❌ NO es usado por →  range_predictor_factor.py
                                  enhanced_range_predictor.py
                                  prediction_service.py
```

### 3.3 APIs No Usan Trainer Correcto

**prediction_service.py** usa modelos legacy:
- `RangePredictorFactor` (hardcoded)
- `EnhancedRangePredictor` (parcialmente dinámico)

**Debería usar**:
- `SymbolTimeframeTrainer.predict()`
- O modelos cargados desde `models/ml_first/{symbol}/{timeframe}/`

---

## 4. ARQUITECTURA PROPUESTA DE INTEGRACIÓN

### 4.1 Nuevo Flujo de Predicción

```
┌─────────────────────────────────────────────────────────────────┐
│                    PREDICTION SERVICE (Unificado)               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Request: { symbol: "XAUUSD", timeframe: "15m" }               │
│                                                                 │
│  1. Load Config: SYMBOL_CONFIGS['XAUUSD']                      │
│     → base_factor: 5.0, pip_value: 0.01                        │
│                                                                 │
│  2. Load Model: models/XAUUSD/15m/range_xgb.joblib             │
│                                                                 │
│  3. Apply Attention: DynamicFactorWeighter                     │
│     → compute_factor_median_range(df, window=200)              │
│     → weight_smooth(m, w_max=3.0, beta=4.0)                    │
│                                                                 │
│  4. Predict: model.predict(features, attention_weight)         │
│                                                                 │
│  5. Post-process: scale by base_factor                         │
│                                                                 │
│  Response: { delta_high, delta_low, confidence, attention }    │
└─────────────────────────────────────────────────────────────────┘
```

### 4.2 Refactoring Requerido

**Paso 1: Eliminar Factores Hardcodeados**
```python
# ANTES (range_predictor_factor.py)
class PriceDataGenerator:
    SYMBOLS = {...}  # Hardcoded

# DESPUÉS
from training.symbol_timeframe_trainer import SYMBOL_CONFIGS

class PriceDataGenerator:
    def __init__(self, symbol: str):
        self.config = SYMBOL_CONFIGS.get(symbol, self._default_config(symbol))
```

**Paso 2: Unificar Carga de Modelos**
```python
# prediction_service.py
class UnifiedPredictionService:
    def __init__(self):
        self.trainer = SymbolTimeframeTrainer()
        self.trainer.load('models/ml_first/')  # Cargar modelos entrenados

    def predict(self, symbol: str, timeframe: str, df: pd.DataFrame):
        return self.trainer.predict(df, symbol, timeframe)
```

**Paso 3: Integrar en API**
```python
# api/main.py
@app.post("/predict/range/{symbol}")
async def predict_range(symbol: str, timeframe: str = "15m"):
    predictions = prediction_service.predict(symbol, timeframe, data)
    return predictions
```

---

## 5. ANÁLISIS DE HYPERPARÁMETROS

### 5.1 XGBoost - Comparación de Configs

| Parámetro | config/models.yaml | symbol_timeframe_trainer.py | Recomendación |
|-----------|-------------------|----------------------------|---------------|
| n_estimators | 200 | 300 | 300 (más estable) |
| max_depth | 5 | 6 | 5 (evitar overfitting) |
| learning_rate | 0.05 | 0.03 | 0.03 (más lento, mejor) |
| subsample | 0.8 | 0.8 | 0.8 ✅ |
| min_child_weight | 3 | 10 | 10 (más conservador) |

### 5.2 Attention Weighting - Config Óptima

| Parámetro | Valor Actual | Propuesto | Razón |
|-----------|--------------|-----------|-------|
| factor_window | 200 | 200 ✅ | Suficiente contexto |
| softplus_beta | 4.0 | 4.0 ✅ | Transición suave |
| softplus_w_max | 3.0 | 3.0 ✅ | Cap razonable |

### 5.3 Configuración Óptima Propuesta

```yaml
# config/unified_model.yaml
training:
  use_dynamic_factor_weighting: true
  use_atr_weighting: true
  use_session_weighting: false  # Deshabilitar, no aporta en backtests

  factor:
    window: 200
    min_periods: 100

  softplus:
    beta: 4.0
    w_max: 3.0

  xgboost:
    n_estimators: 300
    max_depth: 5
    learning_rate: 0.03
    subsample: 0.8
    colsample_bytree: 0.8
    min_child_weight: 10
    gamma: 0.1
    reg_alpha: 0.1
    reg_lambda: 1.0
```

---

## 6. ANÁLISIS DE MODELOS POR TIPO

### 6.1 XGBoost vs GRU vs Transformer

| Criterio | XGBoost | GRU | Transformer |
|----------|---------|-----|-------------|
| **Velocidad inferencia** | ⚡ Rápido | 🔸 Medio | 🔸 Medio |
| **Memoria** | ⚡ Bajo | 🔸 Medio | 🔴 Alto |
| **Interpretabilidad** | ⚡ Alta | 🔴 Baja | 🔴 Baja |
| **Dependencias temporales** | 🔴 Limitado | ⚡ Excelente | ⚡ Excelente |
| **Volatility Attention** | 🔸 Via sample_weight | ⚡ Nativo | ⚡ Nativo |
| **Backtests actuales** | 80% WR (scalping) | No probado | Implementado |

**Recomendación**:
1. **Corto plazo**: XGBoost con attention via sample_weight (ya funciona)
2. **Mediano plazo**: Agregar GRU como modelo secundario
3. **Largo plazo**: Transformer con VolatilityBiasedSelfAttention

### 6.2 Arquitectura Híbrida Propuesta

```
┌─────────────────────────────────────────────────────────────┐
│                    HYBRID ENSEMBLE                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Level 1: Base Models                                       │
│  ├── XGBoost (Range): 35% weight                           │
│  │   └── Sample weight: DynamicFactorWeighter              │
│  │                                                          │
│  ├── Transformer (Attention): 30% weight                   │
│  │   └── VolatilityBiasedSelfAttention                     │
│  │                                                          │
│  ├── AMD Detector: 20% weight                              │
│  │   └── Phase-aware filtering                             │
│  │                                                          │
│  └── ICT/SMC: 15% weight                                   │
│      └── Order Blocks + FVG                                │
│                                                             │
│  Level 2: Meta-Model                                        │
│  └── XGBoost (stacking)                                    │
│      └── Features: predictions + volatility + session      │
│                                                             │
│  Output: Unified Signal + Confidence                        │
└─────────────────────────────────────────────────────────────┘
```

---

## 7. FEATURES ÓPTIMOS POR MODELO

### 7.1 Features Críticos (de trading.yaml)

**Set Mínimo (14 features)** - Mejor performance:
```yaml
momentum: [macd_signal, macd_histogram, rsi]
trend: [sma_10, sma_20, sar]
volatility: [atr]
volume: [obv, ad, cmf, mfi]
patterns: [fractals_high, fractals_low, volume_zscore]
```

### 7.2 Features por Modelo

| Modelo | Features Prioritarios | Razón |
|--------|----------------------|-------|
| RangePredictor | atr, bollinger_width, volume_zscore | Volatilidad determina rango |
| AMDDetector | volume, obv, cmf, rsi | Detección de fases |
| ICT/SMC | support_levels, resistance_levels, sar | Estructura de mercado |
| TPSLClassifier | atr, direction, momentum | Probabilidad de hit |

### 7.3 Feature Engineering Adicional

```python
# Nuevos features propuestos
new_features = {
    'range_factor': (High - Low) / rolling_median(High - Low, 200).shift(1),
    'atr_ratio': ATR / ATR.rolling(20).mean(),
    'volume_momentum': Volume.pct_change(3),
    'session_code': encode_session(timestamp),  # 0=Asian, 1=London, 2=NY
    'hour_sin': np.sin(2 * np.pi * hour / 24),
    'hour_cos': np.cos(2 * np.pi * hour / 24),
}
```

---

## 8. OPTIMIZACIÓN DE FILTROS DIRECCIONALES

### 8.1 Hallazgo de Backtests

> "100% de trades ganadores fueron SHORT en XAUUSD 5m"

**Filtros que funcionaron**:
```python
SHORT_FILTERS = {
    'rsi': '> 55',      # Sobreextensión alcista
    'sar': 'above_price',  # SAR bajista
    'cmf': '< 0',       # Flujo vendedor
    'mfi': '> 55',      # Money flow alto (distribución)
}
```

### 8.2 Filtros Propuestos por Dirección

```python
class DirectionalFilters:
    """Filtros mejorados basados en backtests"""

    @staticmethod
    def short_signal_valid(indicators: dict) -> bool:
        """SHORT: 2+ confirmaciones requeridas"""
        confirmations = 0
        if indicators['rsi'] > 55: confirmations += 1
        if indicators['sar_above_price']: confirmations += 1
        if indicators['cmf'] < 0: confirmations += 1
        if indicators['mfi'] > 55: confirmations += 1
        return confirmations >= 2

    @staticmethod
    def long_signal_valid(indicators: dict) -> bool:
        """LONG: 3+ confirmaciones requeridas (más estricto)"""
        confirmations = 0
        if indicators['rsi'] < 35: confirmations += 1
        if not indicators['sar_above_price']: confirmations += 1
        if indicators['cmf'] > 0.1: confirmations += 1
        if indicators['mfi'] < 35: confirmations += 1
        return confirmations >= 3
```

---

## 9. PLAN DE IMPLEMENTACIÓN VUELTA 2

### Fase 2.1: Consolidación de Configuración
- [ ] Migrar `SYMBOLS` de `range_predictor_factor.py` a `SYMBOL_CONFIGS`
- [ ] Crear `config/symbols.yaml` centralizado
- [ ] Actualizar todos los modelos para usar config unificada

### Fase 2.2: Integración de Trainer
- [ ] Modificar `prediction_service.py` para usar `SymbolTimeframeTrainer`
- [ ] Cargar modelos desde `models/ml_first/{symbol}/{timeframe}/`
- [ ] Agregar fallback a legacy models durante transición

### Fase 2.3: Optimización de Filtros
- [ ] Implementar `DirectionalFilters` en `signal_generator.py`
- [ ] Ajustar thresholds según backtests
- [ ] Agregar SHORT bias para XAUUSD

### Fase 2.4: Ensemble Mejorado
- [ ] Actualizar pesos de `StrategyEnsemble` basado en performance
- [ ] Agregar adaptive weighting basado en volatility regime
- [ ] Integrar XGBoost meta-model

---

## 10. MÉTRICAS OBJETIVO ACTUALIZADAS

### 10.1 Con Infraestructura Existente

| Métrica | Actual | Objetivo Vuelta 2 | Objetivo Final |
|---------|--------|------------------|----------------|
| Win Rate (fuertes) | 33-44% | 60% | 80% |
| R:R Ratio | 1.2:1 | 1.8:1 | 2.5:1 |
| Profit Factor | 1.07 | 1.3 | 1.8 |
| Max Drawdown | 15% | 12% | 10% |

### 10.2 Quick Wins Identificados

1. **Usar filtros SHORT en XAUUSD**: +10% win rate estimado
2. **Integrar DynamicFactorWeighter**: Mejor selección de señales
3. **Cargar modelos separados**: Predicciones más precisas por símbolo
4. **Ajustar confidence threshold a 0.7**: Menos trades, mejor calidad

---

## 11. PRÓXIMOS PASOS

### Vuelta 2 - FASE 2: Análisis Profundo de Arquitectura
1. Revisar implementación de `VolatilityBiasedSelfAttention`
2. Analizar métricas de modelos entrenados existentes
3. Benchmark XGBoost vs Transformer en datos reales

### Vuelta 2 - FASE 3: Retroalimentación
1. Documentar decisiones arquitectónicas
2. Proponer refactoring mínimo viable
3. Definir tests de regresión

---

## 12. ANÁLISIS PROFUNDO DE ARQUITECTURA (FASE 2)

### 12.1 Métricas de Modelos Entrenados

**XAUUSD Movement Predictor - Modelos Existentes:**

| Modelo | Horizonte | R² High | R² Low | MAE High | MAE Low | Muestras |
|--------|-----------|---------|--------|----------|---------|----------|
| 5m→15min | 15 min | 38.85% | 40.24% | 0.76 USD | 0.78 USD | 135,199 |
| 15m→60min | 60 min | 48.32% | 55.55% | 1.42 USD | 1.37 USD | 45,500 |

**Observaciones:**
- El modelo 15m→60min tiene mejor R² (48-56% vs 39-40%)
- Más muestras no implica mejor modelo (5m tiene 3x muestras pero peor R²)
- Horizonte más largo = más predecible (menos ruido)

**Baseline Stats (5m→15min):**
```
mean_high: 1.52 USD
mean_low: 1.64 USD
total_range: 3.16 USD
```

### 12.2 Features Usados por Modelos Entrenados

**110 Features en MovementPredictor:**
```
Categorías:
├── Range Features (20): bar_range_usd, avg_range_usd_*, range_zscore_*, range_pctl_*
├── Momentum (16): momentum_*, momentum_abs_*, range_roc_*
├── ATR/Volatility (12): atr_*, atr_pct_*, vol_clustering_*
├── Price Position (12): price_position_*, dist_from_high_*, dist_from_low_*
├── Volume (12): volume_ma_*, volume_ratio_*, vol_range_*
├── High/Low Body (16): high_body, low_body, avg_high_move_*, high_low_ratio_*
├── Session (5): hour, day_of_week, is_london, is_ny, is_overlap
└── Candlestick (17): body_size, upper_wick, lower_wick, body_to_range, avg_body_size_*, bullish_candles_*
```

**Feature Redundancy Identificada:**
- 4 versiones de cada métrica (6, 12, 24, 48 períodos) = posible overfitting
- Recomendación: Reducir a 2-3 períodos clave (6, 24)

### 12.3 Punto de Desconexión Identificado

**prediction_service.py:157**
```python
from ..models.range_predictor import RangePredictor  # ← Legacy
self._range_predictor = RangePredictor()  # ← No usa modelos entrenados
```

**Debería ser:**
```python
from ..training.symbol_timeframe_trainer import SymbolTimeframeTrainer
self._trainer = SymbolTimeframeTrainer()
self._trainer.load('models/ml_first/')  # ← Cargar modelos separados
```

### 12.4 Arquitectura de VolatilityBiasedSelfAttention

**Componentes PyTorch implementados:**
```python
VolatilityBiasedSelfAttention
├── Multi-head attention con bias de volatilidad
├── attn_weight: (B, T) aplicado a Q·K^T
└── Softplus mapping para pesos suaves

VolatilityAttentionBlock
├── LayerNorm + Attention + Residual
└── LayerNorm + FeedForward + Residual

VolatilityTransformerEncoder
├── Input projection + Positional encoding
├── N capas de VolatilityAttentionBlock
└── Final LayerNorm

VolatilityRangePredictor  # ← Modelo completo
├── VolatilityTransformerEncoder
└── Output heads para delta_high, delta_low
```

**Estado**: Implementado pero NO usado en producción (solo XGBoost activo)

### 12.5 Comparación de Pipelines

| Aspecto | Pipeline Legacy | Pipeline Propuesto |
|---------|-----------------|-------------------|
| Modelos | `RangePredictor` genérico | `models/ml_first/{symbol}/{tf}/` |
| Factores | Hardcoded en código | `SYMBOL_CONFIGS` + dinámico |
| Atención | No usa | `DynamicFactorWeighter` |
| Símbolo | Mezcla todos | Separado por símbolo |
| Timeframe | Fijo | Separado por timeframe |
| Entrenamiento | Manual | `SymbolTimeframeTrainer` |

---

## 13. QUICK WINS IDENTIFICADOS

### 13.1 Cambio de Bajo Riesgo - Alto Impacto

**1. Cargar modelos entrenados en prediction_service.py**
```python
# Línea ~157: Cambiar de
from ..models.range_predictor import RangePredictor
# A
from ..models.movement_magnitude_predictor import MovementMagnitudePredictor
# Y cargar modelo específico por símbolo
```

**2. Usar SYMBOL_CONFIGS en signal_generator.py**
- Eliminar factores hardcodeados
- Usar `base_factor` de configuración centralizada

**3. Activar filtros direccionales en XAUUSD**
- SHORT bias demostrado en backtests (+28 puntos porcentuales)
- Implementar en `generate_signal()`

### 13.2 Impacto Estimado

| Cambio | Esfuerzo | Impacto Win Rate | Riesgo |
|--------|----------|------------------|--------|
| Cargar modelos separados | Bajo | +5-10% | Bajo |
| Filtros direccionales | Bajo | +10-15% | Bajo |
| Integrar DynamicFactorWeighter | Medio | +5-10% | Medio |
| Cambiar a Transformer | Alto | +10-20% | Alto |

---

*Documento generado: 2026-01-06*
*Estado: FASE 2 Vuelta 2 completada*
