This is a modular, production-ready implementation of the energy price prediction pipeline. The original monolithic notebook (final_v3.ipynb) has been refactored into reusable Python modules with a clean, streamlined notebook interface.
This pipeline predicts 24-hour ahead energy price movements (Long/Neutral/Short) using:
- News signals: German energy news classified and embedded using NLP models
- Time-decay aggregation: Exponentially weighted aggregation of news signals
- Energy market features: Price, load, and temporal features
- Machine learning: XGBoost and LightGBM classifiers with hyperparameter optimization
- Zero-shot topic attribution: Article titles are processed with
MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7, returning the top label and confidence score; low-confidence items tagged as "other" are re-run on descriptions to recover additional signal, yet the current median score (~0.26) indicates most articles sit near decision boundaries. - Sentence embeddings: Titles are encoded via
paraphrase-multilingual-MiniLM-L12-v2, cached as 384-dimensional vectors, and reused across runs to keep notebook execution fast. - Time-decayed features: For every forecast timestamp, topic counts and embedding averages are computed with exponential decay, producing hourly features that capture both discrete classifications and dense semantic context before dimensionality reduction (UMAP or PCA to 50 components).
- Dataset assembly: Decayed NLP features are merged with price, load, and temporal baselines, standardised per split, and evaluated through expanding-window RidgeCV to rank decay parameters prior to tree-based model training.
- Quality diagnostics:
visualization.plot_embedding_qualityprojects the raw article embeddings with UMAP and t-SNE; the present plots show limited clustering by topic, consistent with the low zero-shot confidence and high share of "other" assignments.
- Refine label set and prompts: Shorten and disambiguate German topic statements, consider hierarchical routing (energy vs. non-energy before fine-grained classes), and drop or merge rarely triggered labels to reduce overlap driving low confidence.
- Enrich classification inputs: Concatenate title and description, optionally translate mixed-language snippets, and filter boilerplate updates so the zero-shot model sees fuller, more contextual text.
- Confidence-aware feature weighting: Multiply time-decayed topic counts and embedding contributions by the classifier score, track per-window score statistics, and suppress articles with confidence below a chosen threshold to limit noise.
- Evaluate alternative embeddings: Benchmark models such as
sentence-transformers/paraphrase-multilingual-mpnet-base-v2orintfloat/multilingual-e5-large, and explore supervised dimensionality reduction (e.g., LDA or supervised UMAP) to improve downstream separability. - Supervised calibration path: Label a small corpus of articles to fine-tune or calibrate the classifier, using the current zero-shot outputs as weak labels; even a lightweight adapter can raise median confidence and sharpen topic-driven signals.
- Broaden decay search: Extend lookback/decay grids, test asymmetric windows for topics vs. embeddings, and include alternative decay functions (piecewise linear, sigmoid) to better capture how narratives influence prices over time.
├── config/
│ ├── __init__.py
│ ├── pipeline_config.py # All configurable parameters
│ └── model_config.py # Model-specific configurations
│
├── scripts/
│ ├── __init__.py
│ ├── device_utils.py # GPU/CPU detection, resource management
│ ├── profiling.py # Performance profiling and telemetry
│ ├── data_ingestion.py # Data loading and preprocessing
│ ├── feature_engineering.py # NLP, embeddings, time-decay features
│ ├── model_utils.py # XGBoost/LightGBM model training
│ ├── evaluation.py # Metrics, bootstrap CIs, statistical tests
│ ├── save_models.py # Model persistence utilities
│ └── visualization.py # Plotting functions
│
├── notebooks/
│ ├── pipeline_execution.ipynb # Clean, streamlined execution notebook
│ └── outputs/figures/ # Generated visualizations
│
├── prelim_results/
│ └── FINAL_RUN_3.ipynb # Final pipeline run used in research paper
│
├── data/
│ └── MasterFetcher.py # Data fetching utility
│
├── outputs/ # Model outputs and artifacts
│ └── .cache/ # Embedding and feature caches
│
├── Research Paper.pdf # Published research paper
├── requirements.txt
└── README.md
- Functions organized by purpose in separate modules
- Easy to locate, debug, and test specific functionality
- Clear separation of concerns
- All functions can be imported and used in other projects
- No code duplication
- Modular design allows mixing and matching components
- All parameters centralized in
config/pipeline_config.py - Easy to modify without touching code
- Configuration inheritance and overrides supported
- GPU-aware device detection and optimization
- Embedding and feature caching to disk
- Parallel processing where applicable
- Resource profiling for bottleneck identification
- Reduced from 122 cells (~4000 lines) to ~25 cells (~300 lines)
- Focus on results and outputs, not implementation
- Clear, step-by-step execution flow
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install all CPU packages from requirements.txt
pip install -r requirements.txt
# Install GPU packages via conda (recommended for Linux/GPU servers)
conda install -c rapidsai -c conda-forge -c nvidia cuml-cu12 cupyNote: cuML and cupy are commented out in requirements.txt because they require special installation via conda or the RAPIDS pip index. The conda installation method shown above is the most reliable.
Place your data files in the data/ directory:
german_news_v1.csv- News articles with title, description, publishedAtenergy_baseline.csv- Energy market data with price, load, timestamp
Open notebooks/pipeline_execution.ipynb in Jupyter and run cells sequentially.
Edit config/pipeline_config.py to modify:
- Time-decay parameters (λ, lookback window)
- Target definition (spread deadband)
- Cross-validation settings
- Grid search ranges
- Model hyperparameters
- Hierarchical topic routing (stage definitions, thresholds)
Central configuration file containing:
- Time-series parameters (forecast horizon, lookback window)
- Feature engineering parameters (time decay, PCA components)
- Dataset split ratios (train/val/test)
- Cross-validation settings
- NLP configuration (embedding model, topic labels)
- XGBoost hyperparameter distributions
- Data paths
Key Parameters:
FORECAST_HORIZON_HOURS = 24- Prediction horizonDEFAULT_LOOKBACK_WINDOW = 336- News lookback (2 weeks)DEFAULT_DECAY_LAMBDA = 0.05- Time decay rateSPREAD_TARGET_DEADBAND = 10.0- Neutral class threshold (EUR/MWh)HIERARCHICAL_TOPIC_GROUPS- Stage-1 routing categories mapped to leaf topicsHIERARCHICAL_ROUTING_SETTINGS- Stage order, confidence thresholds and fallback behaviour
Functions:
detect_compute_device()- Auto-detect GPU/CPU and recommend batch sizesensure_tensor_device()- Move tensors to specified deviceresolve_hf_device()- Resolve device for HuggingFace models
Usage:
from scripts import device_utils
device_config = device_utils.detect_compute_device(task='embeddings', verbose=True)Classes:
StageProfiler- Context manager for resource monitoring
Usage:
from scripts import profiling
with profiling.StageProfiler("Data Loading", device_config):
# Your code here
passFunctions:
run_ingestion_stage()- Load and preprocess news and energy data
Returns:
news_df- Preprocessed news dataframeenergy_df- Preprocessed energy dataframemaster_df- Master feature dataframe with target variable
Main Functions:
run_embedding_stage()- Hierarchical zero-shot topic classificationcompute_embeddings()- Generate sentence embeddings (cached)compute_time_decayed_topic_counts()- Time-weighted topic aggregationcompute_time_decayed_embeddings()- Time-weighted embedding aggregationreduce_embeddings_gpu_first()- UMAP/PCA dimensionality reduction (cached)grid_search_time_decay_params()- Parameter optimization using Ridge CV
Key Features:
- Disk caching for embeddings and reduced features
- GPU-first computation with CPU fallback
- Efficient binary search for time-window lookups
Classes:
ExpandingWindowSplitter- Custom time-series CV splitter
Functions:
build_xgb_classifier()- Build XGBoost classifier with device optimizationrun_xgb_random_search()- Hyperparameter tuning with RandomizedSearchCV
Functions:
bootstrap_confidence_interval()- Bootstrap 95% CIs for metricscompare_models_statistically()- McNemar's test for model comparison_safe_multiclass_auc()- Robust multiclass AUC computationactions_to_returns()- Convert trading signals to returnsget_column_name()- Helper for flexible column matching
Functions:
plot_confusion_matrices()- Side-by-side confusion matricesplot_feature_importance()- Bar plot of top features
- Load news and energy data
- Create target variable (3-class spread classification)
- Generate temporal features (hour, day, week, month)
- Create lagged features (24h, 168h lags)
2A. Topic Classification
- Hierarchical routing with stage-1 categories (Nachfrage, Angebot, Brennstoffpreise, Makrofinanzen, Geopolitik, Wetter, Sonstiges)
- Stage-2 selection of leaf topics ensures compatibility with downstream feature aggregation
- Model:
MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7 - Batch processing with GPU acceleration
- Re-classification of "other" articles using descriptions
- Diagnostics:
classification_stage1,classification_stage1_scorecolumns added for QC
2B. Sentence Embeddings
- Generate 384-dim embeddings using multilingual MiniLM
- GPU-accelerated inference (fp16 on CUDA)
- Disk caching for reuse
3A. Time-Decayed Topic Counts
- Exponential time decay:
weight = exp(-λ * hours_since) - Vectorized computation across timestamps
- Separate weighted count per topic
3B. Time-Decayed Embeddings
- Weighted average of embeddings in time window
- Binary search for efficient time-range queries
- Same decay formula as topics
3C. Dimensionality Reduction
- Reduce 384-dim embeddings to 50-dim
- GPU-first UMAP (cuML) with CPU fallback
- Disk caching with checksum validation
- Grid search over (lookback_window, decay_lambda) combinations
- Ridge classifier with expanding-window CV
- Parallel evaluation using joblib
- Rank by validation accuracy
- XGBoost RandomizedSearchCV (softmax objective + inverse-frequency sample weights)
- Expanding-window time-series CV
- GPU acceleration when available
- Regularised hyperparameter distributions from config (100–400 estimators, explicit
min_child_weight,reg_alpha,reg_lambda) - Native argmax probabilities downstream (no additional threshold tuning)
- Confusion matrices
- Classification reports
- Feature importance plots
- Bootstrap confidence intervals
- McNemar's test for model comparison
# In config/pipeline_config.py or notebook
DEFAULT_LOOKBACK_WINDOW = 504 # 3 weeks instead of 2
DEFAULT_DECAY_LAMBDA = 0.01 # Slower decayTRAIN_RATIO = 0.6
VAL_RATIO = 0.3
TEST_RATIO = 0.1N_CV_SPLITS = 3 # Fewer splits for faster iteration
CV_STEP_SIZE_HOURS = 48 # Larger step sizeLOOKBACK_WINDOWS = [168, 336, 504, 672] # 1, 2, 3, 4 weeks
TIME_DECAY_LAMBDAS = [0.001, 0.01, 0.05, 0.1, 0.2]- First run generates embeddings and caches to disk
- Subsequent runs load from cache (much faster)
- Cache invalidated automatically on data changes
- Install CUDA toolkit and cupy for NVIDIA GPUs
- Models automatically detect and use GPU
- Batch sizes auto-adjusted based on GPU memory
- joblib parallelizes grid search and Ridge CV
- Set
n_jobs=-1to use all cores - GPU-accelerated models use
n_jobs=1(GPU handles parallelism)
- Process data in batches for large datasets
- Use
news_sampleparameter for quick testing - Clear GPU cache after heavy operations
# Add parent directory to path
import sys
sys.path.insert(0, str(Path('../scripts').parent.resolve()))- Check CUDA installation:
torch.cuda.is_available() - Install correct PyTorch version for your CUDA version
- Apple Silicon: Use MPS backend (automatic)
- Reduce batch size in config
- Use smaller PCA components
- Process fewer news articles for testing
- Check if GPU is being used
- Ensure caching is working (check
.cache/embeddings/) - Reduce batch size if GPU memory is limited
Issue: The current train/validation/test splits do not include temporal gaps between splits, which can cause subtle data leakage through feature engineering.
Root Cause: Features use lookback windows (up to 504 hours) for time-decayed news aggregation. Without gaps between splits:
- Last training sample: hour 5089
- First validation sample: hour 5090
- Validation sample's features include news from hours [4586:5090] (504-hour lookback)
- This overlaps with training period [0:5089]
- News from hours [4586:5089] appears in BOTH training targets AND validation features
Impact:
- Validation and test metrics may be slightly optimistic
- Model may have access to information from the "future" during training through overlapping lookback windows
- Not a severe issue but affects strict temporal validation
Acknowledged Workaround: We acknowledge this limitation. The temporal ordering is preserved (no shuffling), and the expanding window CV splitter maintains chronological order, so the impact is relatively minor. A proper fix would require adding 24-48 hour gaps between splits at the cost of reduced training data.
Potential Fix (not implemented):
# In assemble_time_decay_datasets(), add gap parameter:
gap_size = 24 # 24-hour gap between splits
train_end = int(num_samples * train_ratio)
val_start = train_end + gap_size # Skip 24 hours
val_end = val_start + int(num_samples * val_ratio)
test_start = val_end + gap_size # Skip another 24 hours- Create function in appropriate module (e.g.,
feature_engineering.py) - Import in notebook
- Call function and merge with
feature_df
- Create training function in
model_utils.py - Add configuration to
model_config.py - Call from notebook
- Add metric function to
evaluation.py - Use with
bootstrap_confidence_interval()
| Aspect | Original (final_v3.ipynb) |
Refactored |
|---|---|---|
| Lines of code | ~4000 | ~300 (notebook) + ~2000 (modules) |
| Notebook cells | 122 | ~25 |
| Configuration | Scattered | Centralized |
| Reusability | Low (copy-paste) | High (import) |
| Testability | Difficult | Easy |
| Maintainability | Low | High |
| Git diff clarity | Poor | Excellent |
| IDE support | Limited | Full |
If you use this pipeline in your research, please cite:
@software{energy_price_prediction_pipeline,
title={Energy Price Prediction Pipeline with News Signals},
author={ZHAW AREP Team},
year={2025},
note={Modular ML pipeline for energy market forecasting}
}
[Add your license here]
For questions or issues, please contact [your contact information]
Happy modeling! 🚀