# Training Improvements for Range Prediction Models

## Date: 2026-01-05
## Version: 2.1.0

---

## Problems Identified

### 1. Data Leakage in Sample Weighting
**Problem**: The original weighting used current candle information to compute weights, which could leak future information into the training process.

**Solution**: Implemented `shift(1)` in rolling calculations to ensure only past information is used.

```python
# BEFORE (leakage)
factor = candle_range.rolling(window=200).median()

# AFTER (no leakage)
factor = candle_range.rolling(window=200).median().shift(1)
```

### 2. Suboptimal Weight Mapping
**Problem**: The original power-based weighting (`weight = (movement/threshold)^exponent`) didn't properly distinguish between noise and signal.

**Solution**: Implemented softplus attention mapping that:
- Gives near-zero weight to movements < factor (noise)
- Gives proportional attention to movements > factor (signal)

```python
def weight_smooth(m, w_max=3.0, beta=4.0):
    x = beta * (m - 1.0)
    w = np.log1p(np.exp(x)) / beta  # ~0 if m<1, ~m-1 if m>1
    return np.clip(w, 0.0, w_max)
```

### 3. No Symbol/Timeframe Separation
**Problem**: Models were trained on mixed data without separating by symbol or timeframe, causing:
- Gold patterns mixed with crypto patterns
- 5m patterns mixed with 15m patterns

**Solution**: Created `SymbolTimeframeTrainer` that trains separate models for each (symbol, timeframe) combination.

### 4. No Holdout for Backtesting
**Problem**: No systematic exclusion of recent data for out-of-sample testing.

**Solution**: Automatic exclusion of last N years (configurable, default 1 year) for backtesting.

---

## New Components

### 1. `dynamic_factor_weighting.py`
Standalone module for dynamic factor-based sample weighting.

**Key Functions**:
- `compute_factor_median_range()`: Rolling median with shift(1)
- `compute_move_multiplier()`: Movement / factor ratio
- `weight_smooth()`: Softplus attention mapping

### 2. `symbol_timeframe_trainer.py`
Complete training pipeline for symbol-specific models.

**Features**:
- Per-symbol models (XAUUSD, BTCUSD, EURUSD)
- Per-timeframe models (5m, 15m)
- Automatic holdout split
- Dynamic factor weighting integration
- GPU support for XGBoost

### 3. Updated `sample_weighting.py` (v2.0)
**New Configuration Options**:
```python
@dataclass
class SampleWeightConfig:
    # ... existing options ...

    # NEW: Dynamic Factor Options
    use_dynamic_factor: bool = True
    factor_window: int = 200
    softplus_beta: float = 4.0
    softplus_w_max: float = 3.0
```

---

## Usage Examples

### Basic Training with Dynamic Weights
```python
from training import SampleWeighter, SampleWeightConfig

config = SampleWeightConfig(
    use_dynamic_factor=True,  # Enable new weighting
    factor_window=200,
    softplus_beta=4.0,
    softplus_w_max=3.0
)

weighter = SampleWeighter(config)
weights, valid_mask = weighter.compute_sample_weights(df, 'target_high', 'target_low')
```

### Symbol-Specific Training
```python
from training import SymbolTimeframeTrainer, TrainerConfig

config = TrainerConfig(
    symbols=['XAUUSD', 'BTCUSD'],
    timeframes=['5m', '15m'],
    train_years=5.0,
    holdout_years=1.0,  # Last year for backtesting
    use_dynamic_factor_weighting=True
)

trainer = SymbolTimeframeTrainer(config)

# Train all combinations
data_dict = {
    'XAUUSD': {'5m': df_xau_5m, '15m': df_xau_15m},
    'BTCUSD': {'5m': df_btc_5m, '15m': df_btc_15m}
}
results = trainer.train_all(data_dict)

# Or train single combination
results = trainer.train_single(df_xauusd, 'XAUUSD', '15m')

# Predict
predictions = trainer.predict(features, 'XAUUSD', '15m')
```

### Get Holdout Data for Backtesting
```python
# Get last year of data (excluded from training)
holdout_df = trainer.get_holdout_data(df)

# Use for backtesting
backtest_results = backtester.run(holdout_df, predictions)
```

---

## Configuration Recommendations

### For XAUUSD (Gold)
```python
TrainerConfig(
    symbols=['XAUUSD'],
    horizons={'5m': 3, '15m': 3},  # 15m and 45m horizons
    factor_window=200,  # ~16h of 5m data
    softplus_beta=4.0,
    softplus_w_max=3.0
)
```

### For BTCUSD (Bitcoin)
```python
TrainerConfig(
    symbols=['BTCUSD'],
    horizons={'5m': 3, '15m': 3},
    factor_window=150,  # Faster adaptation for crypto
    softplus_beta=3.0,
    softplus_w_max=4.0  # Higher max for volatile markets
)
```

### For EURUSD (Forex)
```python
TrainerConfig(
    symbols=['EURUSD'],
    horizons={'5m': 3, '15m': 3},
    factor_window=250,  # More stable, longer window
    softplus_beta=5.0,
    softplus_w_max=2.5  # Lower max for less volatile
)
```

---

## Weight Interpretation Guide

| Multiplier (m) | Weight (w) | Interpretation |
|----------------|------------|----------------|
| m < 0.5 | ~0 | Very small movement, ignore |
| m = 1.0 | ~0.17 | Typical movement |
| m = 2.0 | ~1.0 | 2x normal, moderate attention |
| m = 3.0 | ~2.0 | 3x normal, high attention |
| m >= 4.0 | 3.0 (capped) | Large movement, maximum attention |

---

## Files Modified

1. `/training/sample_weighting.py` - Added dynamic factor methods
2. `/training/__init__.py` - Added new exports
3. `/models/range_predictor.py` - Updated docstring

## Files Created

1. `/training/dynamic_factor_weighting.py` - Standalone dynamic weighting
2. `/training/symbol_timeframe_trainer.py` - Symbol-specific trainer
3. `/training/TRAINING-IMPROVEMENTS.md` - This documentation

---

## Next Steps

1. **Backtest** the new models on holdout data
2. **Compare** metrics with previous approach
3. **Tune** hyperparameters per symbol if needed
4. **Monitor** prediction quality on live data
