oyi77
Initial commit: Complete FinTS forecasting pipeline
2828671
Raw
History Blame Contribute Delete
3.54 kB
"""
Temporal walk-forward splitting for time-series validation.
No random shuffle — strict chronological order only.
"""
import pandas as pd
from typing import List, Tuple
from datetime import datetime
def walk_forward_split(
df: pd.DataFrame,
train_size: int,
test_size: int,
step: int = None
) -> List[Tuple[pd.DataFrame, pd.DataFrame]]:
"""
Generate walk-forward train/test splits.
Args:
df: DataFrame with datetime index (sorted chronologically)
train_size: Number of rows for training window
test_size: Number of rows for test window
step: Step size between splits (default: test_size)
Returns:
List of (train_df, test_df) tuples
Example:
If train_size=252, test_size=63 (1 year train, 1 quarter test):
Split 1: rows 0-251 train, 252-314 test
Split 2: rows 63-314 train, 315-377 test
...
"""
if not isinstance(df.index, pd.DatetimeIndex):
raise ValueError("DataFrame must have DatetimeIndex")
if not df.index.is_monotonic_increasing:
raise ValueError("Index must be sorted chronologically")
if step is None:
step = test_size
splits = []
total_rows = len(df)
start = 0
while start + train_size + test_size <= total_rows:
train_end = start + train_size
test_end = train_end + test_size
train_df = df.iloc[start:train_end]
test_df = df.iloc[train_end:test_end]
splits.append((train_df, test_df))
start += step
return splits
def expanding_window_split(
df: pd.DataFrame,
initial_train_size: int,
test_size: int,
step: int = None
) -> List[Tuple[pd.DataFrame, pd.DataFrame]]:
"""
Generate expanding window splits (train set grows over time).
Args:
df: DataFrame with datetime index (sorted chronologically)
initial_train_size: Initial training window size
test_size: Test window size
step: Step size between splits (default: test_size)
Returns:
List of (train_df, test_df) tuples
"""
if not isinstance(df.index, pd.DatetimeIndex):
raise ValueError("DataFrame must have DatetimeIndex")
if not df.index.is_monotonic_increasing:
raise ValueError("Index must be sorted chronologically")
if step is None:
step = test_size
splits = []
total_rows = len(df)
train_end = initial_train_size
while train_end + test_size <= total_rows:
test_end = train_end + test_size
train_df = df.iloc[:train_end]
test_df = df.iloc[train_end:test_end]
splits.append((train_df, test_df))
train_end += step
return splits
def single_train_test_split(
df: pd.DataFrame,
train_ratio: float = 0.8
) -> Tuple[pd.DataFrame, pd.DataFrame]:
"""
Simple train/test split maintaining temporal order.
Args:
df: DataFrame with datetime index (sorted chronologically)
train_ratio: Fraction of data for training
Returns:
(train_df, test_df)
"""
if not isinstance(df.index, pd.DatetimeIndex):
raise ValueError("DataFrame must have DatetimeIndex")
if not df.index.is_monotonic_increasing:
raise ValueError("Index must be sorted chronologically")
split_idx = int(len(df) * train_ratio)
return df.iloc[:split_idx], df.iloc[split_idx:]