# Reporte de Analisis: RangePredictor R^2 Negativo
## Trading Platform - Sprint 1, Tarea S1-T1

**Fecha:** 2026-01-07
**Ejecutor:** Claude Opus 4.5 (ML-SPECIALIST)
**Estado:** COMPLETADO

---

## 1. RESUMEN EJECUTIVO

### 1.1 Problema Identificado

El modelo `RangePredictor` presenta **R^2 negativo** en todas las evaluaciones:

| Modelo | Symbol | Timeframe | Target | R^2 |
|--------|--------|-----------|--------|-----|
| GBPUSD_5m_high_h3 | GBPUSD | 5m | high | **-0.6309** |
| GBPUSD_5m_low_h3 | GBPUSD | 5m | low | **-0.6558** |
| GBPUSD_15m_high_h3 | GBPUSD | 15m | high | **-0.6944** |
| GBPUSD_15m_low_h3 | GBPUSD | 15m | low | **-0.7500** |

**Interpretacion:** Un R^2 negativo significa que el modelo predice PEOR que simplemente usar la media historica como prediccion.

### 1.2 Impacto

- Predicciones de rango inutiles para trading
- Sistema de senales ML no operativo
- Backtesting con win rate bajo (42.1%)

---

## 2. ANALISIS DE CAUSAS RAIZ

### 2.1 Causa 1: Targets Normalizados con Escala Incorrecta

**Archivo:** `src/data/targets.py`

**Hallazgo:**
El target se calcula en valores absolutos (USD) pero las features estan normalizadas.
En el entrenamiento, los valores de target son muy pequenos (0.0005 - 0.001) debido a normalizacion implicita.

```python
# Linea 206-207 de targets.py
df[f'delta_high_{horizon.name}'] = future_high - df['close']  # Valores en USD
df[f'delta_low_{horizon.name}'] = df['close'] - future_low   # Valores en USD
```

**Problema:**
- Para GBPUSD, delta_high podria ser 0.0005 (5 pips)
- El modelo XGBoost tiene dificultad con valores tan pequenos
- La varianza del target es minima comparada con el ruido

**Solucion Propuesta:**
1. Normalizar targets por ATR antes de entrenar
2. Usar targets en pips o puntos en lugar de precio absoluto
3. Escalar features y targets de forma consistente

---

### 2.2 Causa 2: Features No Predictivas para el Target

**Archivo:** `src/data/features.py`

**Hallazgo:**
Las features son principalmente indicadores tecnicos (RSI, MACD, Bollinger) que son:
- Lagging indicators (basados en precio pasado)
- No tienen relacion directa con rango futuro
- Estan diseados para direccion, no para magnitud

**Features Actuales (Lineas 17-27):**
```python
'minimal': [
    'rsi', 'macd', 'macd_signal', 'bb_upper', 'bb_lower',
    'atr', 'volume_zscore', 'returns', 'log_returns'
]
```

**Problema:**
- RSI predice condicion de sobrecompra/sobreventa, NO rango futuro
- MACD predice tendencia, NO magnitud
- Solo `atr` tiene relacion con volatilidad futura

**Solucion Propuesta:**
1. Agregar features de volatilidad: ATR lags, volatilidad historica
2. Agregar features de sesion: hora, dia de semana (codificados ciclicamente)
3. Agregar features de momentum de volatilidad: cambio en ATR
4. Reducir features de direccion no relevantes

---

### 2.3 Causa 3: Sample Weighting Agresivo

**Archivo:** `src/training/sample_weighting.py`

**Hallazgo:**
El weighting de samples (softplus con beta=4.0) es muy agresivo:
- Reduce peso de movimientos "normales" casi a cero
- Solo entrena efectivamente con movimientos extremos
- Esto causa sesgo hacia predicciones de alto rango

**Configuracion Actual (Lineas 66-69):**
```python
softplus_beta: float = 4.0      # MUY agresivo
softplus_w_max: float = 3.0
```

**Problema:**
- Modelo aprende solo de 24-35% de los datos (high flow periods)
- Predicciones sesgadas hacia valores altos
- Varianza de prediccion muy baja (no captura distribucion real)

**Solucion Propuesta:**
1. Reducir softplus_beta a 2.0 o menos
2. Aumentar min_weight para incluir mas samples
3. Considerar weighting uniforme como baseline

---

### 2.4 Causa 4: Data Leakage Potencial

**Archivo:** `src/training/sample_weighting.py`, `src/data/corrected_targets.py`

**Hallazgo:**
Aunque se usa `shift(1)` en el factor de rolling median, hay posible leakage en:
1. Targets que incluyen precio actual en calculo de futuros
2. Features que usan datos futuros implicitamente

**Verificacion Requerida:**
```python
# Linea 126-129 sample_weighting.py
factor = candle_range.rolling(
    window=window,
    min_periods=min_periods
).median().shift(1)  # Correcto - usa shift(1)

# Linea 190-195 targets.py - VERIFICAR
for i in range(start, end + 1):  # start=1, correcto
    future_highs.append(df['high'].shift(-i))
```

**Resultado:** El codigo de targets usa `start_offset=1`, lo cual es correcto.
No hay data leakage evidente en targets, pero hay que verificar features.

---

### 2.5 Causa 5: Hiperparametros XGBoost No Optimizados

**Archivo:** `src/models/range_predictor.py`

**Configuracion Actual (Lineas 146-162):**
```python
'xgboost': {
    'n_estimators': 200,
    'max_depth': 5,
    'learning_rate': 0.05,
    'subsample': 0.8,
    'colsample_bytree': 0.8,
    'min_child_weight': 3,
    'gamma': 0.1,
    'reg_alpha': 0.1,
    'reg_lambda': 1.0,
}
```

**Problema:**
- `max_depth=5` puede ser muy profundo para datos ruidosos
- `learning_rate=0.05` combinado con `n_estimators=200` puede overfit
- `min_child_weight=3` puede ser muy bajo

**Solucion Propuesta:**
1. Reducir `max_depth` a 3
2. Aumentar `min_child_weight` a 10
3. Aumentar regularizacion (`reg_alpha`, `reg_lambda`)
4. Usar early stopping mas agresivo

---

## 3. PLAN DE CORRECCION

### 3.1 Fase 1: Correccion de Targets (Prioridad ALTA)

**Archivo:** `src/data/targets.py`

**Cambios:**
1. Normalizar targets por ATR:
```python
# Agregar normalizacion
df[f'delta_high_{horizon.name}_norm'] = (future_high - df['close']) / df['ATR']
df[f'delta_low_{horizon.name}_norm'] = (df['close'] - future_low) / df['ATR']
```

2. Usar targets normalizados en entrenamiento

**Beneficio Esperado:** Targets en escala [-3, 3] en lugar de [0, 0.001]

---

### 3.2 Fase 2: Correccion de Features (Prioridad ALTA)

**Archivo:** `src/data/features.py`

**Cambios:**
1. Agregar features de volatilidad:
```python
'volatility': [
    'atr',
    'atr_ratio',  # ATR / rolling_median(ATR)
    'atr_pct_change',
    'range_pct',  # (high-low)/close
    'true_range',
    'realized_volatility_10',
    'realized_volatility_20'
]
```

2. Agregar features de sesion (ya existen en `create_time_features`):
```python
# Ya implementado correctamente
df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
```

3. Usar solo features relevantes para prediccion de rango

---

### 3.3 Fase 3: Ajuste de Sample Weighting (Prioridad MEDIA)

**Archivo:** `src/training/sample_weighting.py`

**Cambios:**
```python
# Configuracion menos agresiva
SampleWeightConfig(
    softplus_beta=2.0,        # Reducir de 4.0
    softplus_w_max=2.0,       # Reducir de 3.0
    min_weight=0.3,           # Aumentar de 0.1
    filter_low_ratio=False    # Incluir todos los samples
)
```

---

### 3.4 Fase 4: Optimizacion de Hiperparametros (Prioridad MEDIA)

**Archivo:** `src/models/range_predictor.py`

**Cambios:**
```python
'xgboost': {
    'n_estimators': 100,      # Reducir
    'max_depth': 3,           # Reducir de 5
    'learning_rate': 0.03,    # Reducir
    'subsample': 0.7,
    'colsample_bytree': 0.7,
    'min_child_weight': 10,   # Aumentar de 3
    'gamma': 0.5,             # Aumentar de 0.1
    'reg_alpha': 1.0,         # Aumentar de 0.1
    'reg_lambda': 10.0,       # Aumentar de 1.0
}
```

---

## 4. CRITERIOS DE EXITO

| Metrica | Valor Actual | Minimo Aceptable | Objetivo |
|---------|--------------|------------------|----------|
| R^2 (validacion) | -0.65 | > 0.05 | > 0.15 |
| MAE (normizado) | N/A | < 0.5 ATR | < 0.3 ATR |
| Direccion | 98% | > 60% | > 65% |
| Win Rate Backtest | 42% | > 50% | > 55% |

---

## 5. ORDEN DE EJECUCION

1. **S1-T2:** Implementar normalizacion de targets por ATR
2. **S1-T3:** Verificar no hay data leakage en features
3. **S1-T4a:** Reducir agresividad de sample weighting
4. **S1-T4b:** Ajustar hiperparametros XGBoost
5. **S1-T5:** Reentrenar modelos con correcciones
6. **S1-T6:** Validar R^2 > 0 en datos OOS

---

## 6. ARCHIVOS A MODIFICAR

| Archivo | Tipo de Cambio | Lineas Estimadas |
|---------|---------------|------------------|
| `src/data/targets.py` | Agregar normalizacion | +20 |
| `src/data/features.py` | Agregar features volatilidad | +50 |
| `src/training/sample_weighting.py` | Reducir agresividad | ~10 |
| `src/models/range_predictor.py` | Ajustar hiperparametros | ~15 |
| `scripts/train_symbol_timeframe_models.py` | Usar targets normalizados | ~20 |

---

## 7. RIESGOS

| Riesgo | Probabilidad | Mitigacion |
|--------|--------------|------------|
| R^2 sigue negativo | Media | Plan B: modelo baseline (media movil) |
| Normalizacion introduce leakage | Baja | Usar ATR shift(1) |
| Overfitting a nuevos hiperparametros | Media | Walk-forward validation |

---

**Reporte completado:** 2026-01-07
**Siguiente paso:** S1-T2 - Implementar normalizacion de targets

