| name | multi-timeframe-training |
| description | DEPRECATED in v5.6.0 — see joint-multi-tf-v560 skill. Documents the v5.2.0 dual-model approach (train separate 15Min/1Hour models, combine via weighted voting). Still relevant for: (1) loading legacy v5.5.0 dual models, (2) understanding the historical aggregation layer, (3) resampling pattern via origin='start'. |
| author | Claude Code |
| date | "2024-12-29T00:00:00.000Z" |
Multi-Timeframe Training Pattern (v5.2.0)
DEPRECATED in v5.6.0: Replaced by single joint model per symbol with broadcast 1Hour context features. See joint-multi-tf-v560 for the current architecture.
Why deprecated: The Mar-25 evaluation (see "Multi-TF Strategy" section below) showed dual-model + weighted voting was NOT universally better — only CLSK improved, while CNM/MOH/PSTG degraded. The aggregation layer added combinatorial uncertainty (direction threshold, agreement boost, weight ratios) without letting the models learn to complement each other.
What this skill is still useful for:
- Loading and inferring with legacy v5.5.0 and earlier dual models (still supported via
MultiTimeframePricePredictor)
- Understanding the
resample_to_timeframe() origin='start' pattern for bar alignment (still used for any data resampling)
- Historical context on the PDT window scaling pattern
Experiment Overview
| Item | Details |
|---|
| Date | 2024-12-29 |
| Goal | Train separate models for each timeframe using resampled data from base timeframe |
| Environment | training notebook, multi_timeframe.py, GPUVectorizedTradingEnv |
| Status | Success |
Context
Single-timeframe training limits strategy adaptability. Different timeframes capture different market dynamics:
- 1Hour: Short-term momentum, intraday patterns
- 4Hour: Medium-term trends, reduced noise
- 1Day: Long-term trends, swing trading
Solution: Train separate models for each timeframe, resampling from cached 1Hour data.
Verified Workflow
1. Configure Training Timeframes (notebook)
TIMEFRAME = '1Hour'
TRAINING_TIMEFRAMES = ['1Hour']
2. Build Training Combinations
training_combinations = [(s, tf) for tf in TRAINING_TIMEFRAMES for s in SYMBOLS_TO_TRAIN_FINAL]
total_combinations = len(training_combinations)
print(f'TRAINING {len(SYMBOLS_TO_TRAIN_FINAL)} SYMBOLS × {len(TRAINING_TIMEFRAMES)} TIMEFRAMES = {total_combinations} models')
3. Resample Data with Proper Alignment
from alpaca_trading.signals.multi_timeframe import resample_to_timeframe
df_base = PREFETCHED_DATA[symbol]
if training_tf != TIMEFRAME:
df = resample_to_timeframe(df_base, training_tf)
print(f' Resampled {len(df_base)} bars -> {len(df)} bars for {training_tf}')
else:
df = df_base
4. Resampling Function (multi_timeframe.py)
def resample_to_timeframe(df: pd.DataFrame, timeframe: str) -> pd.DataFrame:
"""Resample OHLCV data to a different timeframe."""
if df.empty:
return df
if not isinstance(df.index, pd.DatetimeIndex):
df.index = pd.to_datetime(df.index)
freq_map = {
"1Min": "1min",
"5Min": "5min",
"15Min": "15min",
"30Min": "30min",
"1Hour": "1h",
"2Hour": "2h",
"4Hour": "4h",
"1Day": "1D",
"1Week": "1W",
}
freq = freq_map.get(timeframe, "1min")
resampled = pd.DataFrame()
resampled["open"] = df["open"].resample(freq, origin='start').first()
resampled["high"] = df["high"].resample(freq, origin='start').max()
resampled["low"] = df["low"].resample(freq, origin='start').min()
resampled["close"] = df["close"].resample(freq, origin='start').last()
if "volume" in df.columns:
resampled["volume"] = df["volume"].resample(freq, origin='start').sum()
return resampled.dropna()
5. Model Naming Convention
model_filename = f'{symbol}_{training_tf}.pt'
local_model_path = f'models/rl_symbols/{model_filename}'
drive_model_path = f'{DRIVE_MODEL_DIR}/{model_filename}'
6. Live Trader Model Loading
for model_file in model_dir.glob('*.pt'):
filename = model_file.stem
parts = filename.split('_')
symbol = parts[0]
timeframe = parts[1] if len(parts) > 1 else '1Hour'
model = load_native_model(str(model_file))
if symbol not in models_by_symbol:
models_by_symbol[symbol] = {}
models_by_symbol[symbol][timeframe] = model
Failed Attempts (Critical)
| Attempt | Why it Failed | Lesson Learned |
|---|
| Resampling without origin='start' | Bars aligned to clock hours, not market open | Use origin='start' for equity market alignment |
| Using 'H' instead of 'h' | Deprecated in pandas 2.2+ | Use lowercase 'h' for hours |
| Training all timeframes in parallel | OOM errors | Train sequentially, clean up between |
| Mixing 1Hour and 4Hour data sources | Data inconsistency | Always resample from same base data |
| Not cleaning GPU memory between TFs | Memory accumulation, crash | Call cleanup_gpu_memory() between each |
Final Parameters
base_timeframe: '1Hour'
training_timeframes:
- '1Hour'
origin: 'start'
open: 'first'
high: 'max'
low: 'min'
close: 'last'
volume: 'sum'
1Hour: '1h'
2Hour: '2h'
4Hour: '4h'
1Day: '1D'
1Week: '1W'
Key Insights
- origin='start' is CRITICAL: Without it, 4Hour bars start at 00:00, not market open
- Lowercase frequency strings: pandas 2.2+ deprecated uppercase 'H'
- Sequential training: GPU memory doesn't allow parallel multi-TF training
- Same base data: All timeframes should resample from same cached data
- Consistent naming:
SYMBOL_TIMEFRAME.pt enables auto-discovery
Data Requirements
| Timeframe | Min Bars (training) | From 1Hour (4 years) |
|---|
| 1Hour | 2000 | ~10,000 bars |
| 4Hour | 500 | ~2,500 bars |
| 1Day | 125 | ~1,000 bars |
v5.2.0 Update: 15Min + 1Hour Dual-Timeframe (2026-03-14)
Sub-Hourly Training (15Min)
When adding sub-hourly timeframes (e.g., 15Min), additional adjustments are needed:
PDT Window Scaling
_bars_per_hour = {'15Min': 4, '30Min': 2, '1Hour': 1}
pdt_window_scaled = int(1200 * _bars_per_hour.get(training_tf, 1))
env_config_tf = dataclass_replace(
env_config,
timeframe=training_tf,
pdt_window_bars=pdt_window_scaled,
)
Selectivity Boost for Sub-Hourly
if _bars_per_hour.get(training_tf, 1) > 1:
env.set_reward_weight_override('selectivity', 0.05)
Selection at Finest Timeframe
from alpaca_trading.signals.multi_timeframe import TIMEFRAME_HIERARCHY
_tf_order = {tf: i for i, tf in enumerate(TIMEFRAME_HIERARCHY)}
SELECTION_TIMEFRAME = min(TRAINING_TIMEFRAMES, key=lambda tf: _tf_order.get(tf, 99))
Timeframe-Aware PDT Detection (vectorized_env.py)
_bars_per_day_map = {'15Min': 26, '30Min': 13, '1Hour': 7, '4Hour': 2, '1Day': 1}
self._bars_per_trading_day = _bars_per_day_map.get(self.config.timeframe, 7)
Key Differences from Higher-TF Resampling
| Aspect | Higher TF (4Hour from 1Hour) | Lower TF (15Min) |
|---|
| Data source | Resample from 1Hour cache | Fetch directly from API at 15Min |
| PDT window | Standard (1200 bars) | Scaled (4800 bars for 15Min) |
| Selectivity | No boost needed | +0.05 boost to counter overtrading |
| Bars/day | Fewer (2 for 4Hour) | More (26 for 15Min) |
v5.2.1 Bug Fixes (2026-03-15)
Bugs Found and Fixed
| Bug | Root Cause | Fix |
|---|
| 15Min timeframe silently ignored | env_config always used timeframe='1Hour' — per-TF dataclass_replace() was documented in this skill but missing from notebook | Added env_config_tf = dataclass_replace(env_config, timeframe=training_tf, pdt_window_bars=scaled) to cells 32+37 |
| PDT hardcoded in active path | _execute_actions_with_sizing (line ~2283) had bars_since_entry <= 7 — the non-sizing path (line ~2177) was already fixed | Changed to <= self._bars_per_trading_day |
Selection used single TIMEFRAME | SelectionRunnerConfig(timeframe=TIMEFRAME) ignored TRAINING_TIMEFRAMES | Changed to timeframe=_finest_training_tf using TIMEFRAME_HIERARCHY ordering |
Key Lesson
Document != Implement: This skill documented the per-TF dataclass_replace() pattern and selectivity boost in v5.2.0, but the notebook cells were never updated. The skill was correct — the notebook was wrong. Always verify notebook cells match skill documentation after codebase changes.
Multi-TF Strategy (ACTIVE as of v5.2.0)
Models exist for 15Min + 1Hour:
MultiTimeframePricePredictor aggregates signals from both timeframes
- Live trader auto-discovers models by filename pattern (
SYMBOL_TIMEFRAME.pt)
- No live trader code changes needed — existing multi-TF infrastructure handles both
References
notebooks/training.ipynb: TRAINING_TIMEFRAMES configuration
alpaca_trading/signals/multi_timeframe.py: resample_to_timeframe()
scripts/live_trader.py: Multi-TF model loading pattern
alpaca_trading/gpu/vectorized_env.py: _bars_per_trading_day for timeframe-aware PDT
scripts/run_backtest.py: Auto-detects timeframe from model filename