尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Five-layer quantitative analytics platform for European gas and power markets. Covers cointegration scanning, forward curve PCA (TTF M+1 to M+18), Nordic price decomposition, ML regime detection (LSTM/HMM/FinBERT), VaR simulation, and BESS project finance. 31 modules, 9 live data sources.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
title: European Gas and Power Market Intelligence Platform
short_description: Quantitative analytics for physical energy traders
emoji: ⚡
colorFrom: blue
colorTo: green
sdk: streamlit
sdk_version: "1.56.0"
app_file: app.py
pinned: false
European Gas and Power Market Intelligence Platform
A five-layer quantitative analytics platform built to mirror the analytical workflow of a European physical energy trader.檢視完整 README ↗A five-layer quantitative analytics platform built to mirror the analytical workflow of a European physical energy trader.
The platform integrates live fundamental data from ENTSO-E Transparency, GIE AGSI+/ALSI, Nord Pool, and ICE/Yahoo Finance into a unified analytical environment covering storage balances, forward curve dynamics, cross-commodity price transmission, machine learning regime detection, portfolio risk simulation, and battery storage project economics. All models are implemented from scratch in Python; no third-party signal providers or data vendors are used beyond the public API layer. The architecture escalates from raw market observation in Layer 1 to actionable composite signals in Layer 5, following the analytical hierarchy a physical desk would apply before taking a position.
Representative outputs from runs between June and October 2026. Live values move with the market; the methodology behind each figure is in the linked source file.
| Module | Result | Source |
|---|---|---|
| Nordic Price Decomposition (Layer 2) | R² = 0.79 over rolling 90-day window; continental price (NL) coefficient 23.83 EUR/MWh per σ, dominant driver in gas-marginal regime | models/nordic_decomp.py |
| Storage Refill Monte Carlo (Layer 2) | P50 terminal fill and probability of meeting EU 90% mandate computed across 1,000 bootstrap paths from current AGSI fill level, calibrated from 5 years of daily injection history | models/storage_monte_carlo.py |
| TTF Seasonal Strategy Backtest (Layer 2) | Full-sample hit rate 43%; ex-crisis average P&L +0.7 EUR/MWh; Sharpe computed across gas years 2018-2026 | models/ttf_backtest.py |
| Multi-Pair Cointegration Scanner (Layer 2) | 6-asset universe (NO2, NL, TTF, DE, FR, NBP); all pairs ranked by Engle-Granger p-value, Ornstein-Uhlenbeck half-life, and real-time z-score entry signal | views/2_Quant_Analysis.py |
| Forward Curve PCA (Layer 2) | PC1 (level) explains approximately 85-90% of M+1-M+18 panel variance; PC2 (slope/storage carry) and PC3 (curvature) generate calendar-spread and butterfly trade signals when z-score exceeds ±2σ | views/2_Quant_Analysis.py |
| 7x7 Cross-Commodity Correlation Grid (Layer 3) | 90-day rolling Pearson ρ(TTF, Brent) approximately +0.59; ρ(copper, aluminium) approximately +0.45; lead-lag sweep ±10 days across all 21 pairs | views/3_Macro_Signals.py |
| D4 Risk Dashboard (Page 6) | 95% and 99% 1-day VaR and Expected Shortfall via non-parametric bootstrap on 6-signal portfolio; 4 named stress scenarios (cold snap, Norwegian outage, Hormuz extension, EUR/USD shock) | views/6_Risk_Dashboard.py |
| BESS Optimiser (Page 7) | NPV, IRR (bisection method), and simple payback for Nordic BESS across DAM arbitrage, FCR-N (EUR 8,000/MW/yr), and FCR-D (EUR 12,000/MW/yr); sensitivity table across ±20% CAPEX, FCR prices, and energy spread | views/7_BESS_Optimiser.py |
Primary deployment: huggingface.co/spaces/TobGud/power-trader-portfolio
The HuggingFace Space builds from the hf-deploy branch. A Space that has gone to sleep takes about a minute to wake. After a restart, the first visit to a data-heavy page (Live Monitor, Quant Analysis) fetches live data for two to four minutes; later visits are served from cache. All panels degrade gracefully without API keys: the TTF price, Nord Pool day-ahead prices, forward curve, and commodity correlations load without credentials. Gas storage, ENTSO-E flows, hydro, solar, and generation data require free API keys from GIE and ENTSO-E (the overview page shows which sources are active, and each affected panel names the key it needs).
A Dockerfile and render.yaml are included for self-hosted or Render deployment.
app.py is the router: it applies the design system once per run and declares a grouped top navigation with st.navigation (on narrow screens Streamlit moves the menu into the sidebar). Page modules live in views/ (deliberately not pages/: Streamlit treats a pages/ folder as a legacy multipage app until the router has run once, which would let a cold deep link bypass the navigation and theme), and URL paths are fixed so direct links keep working. A leftover pages/ folder next to app.py, even one holding only __pycache__, has the same effect on the first request after a server start (a deep link shows "Page not found" before the router takes over); tests/test_app_layout.py fails while one exists.
| Menu | Pages | URL path |
|---|---|---|
| Overview | Landing page: market snapshot, data-source status, layer directory | / |
| Markets | Live Monitor (Layer 1), Macro Signals (Layer 3), Energy Globe (3D infrastructure) | /Live_Monitor, /Macro_Signals, /Energy_Globe |
| Analytics | Quant Analysis (Layer 2), ML Models (Layer 4) | /Quant_Analysis, /ML_Models |
| Signals & Risk | Mispricing Dashboard (Layer 5), Risk Dashboard (D4) | /Mispricing_Dashboard, /Risk_Dashboard |
| Projects | BESS Optimiser | /BESS_Optimiser |
The overview page opens with a market snapshot strip: TTF, JKM, Brent, EUA (CO2.L) and EUR/USD from Yahoo Finance, plus day-ahead daily averages for NO2 and DE-LU (ENTSO-E A44, with Nord Pool for days ENTSO-E has not yet published; NL stands in only when ENTSO-E does not answer). Above it, the hero draws the NordLink interconnector with the latest delivery-day price at each end and the direction of the export incentive. Each quote shows its day-over-day change and a short sparkline, is cached for 15 minutes, and falls back to a placeholder when a source is unavailable.
Energy Globe. The physical system behind the prices is a page of the platform (views/8_Energy_Globe.py): an interactive CesiumJS globe embedded with st.iframe, so it runs wherever the platform runs, locally or hosted, with no separate server. It draws four layers from bundled, licensed datasets (data/globe/) plus live figures:
| Layer | Content | Source |
|---|---|---|
| Shelf facilities | 124 Norwegian shelf installations, storage units, onshore plants and landfalls, and offshore wind farms | Sodir FactMaps (NLOD) |
| Export pipelines | 66 Norwegian shelf export pipelines (gas, oil, condensate, CO2) and 8 receiving terminals | Sodir FactMaps (NLOD) |
| LNG terminals | 38 European LNG import terminals; gauge markers sized by live send-out with an arc for utilisation; send-out, stocks and capacities per gas day | Global Energy Monitor GGIT (CC BY 4.0); live: GIE ALSI |
| Chokepoints | 10 maritime chokepoints with oil and LNG flows | U.S. EIA, World Oil Transit Chokepoints (Mar 2026) |
data/energy_globe.py requests the public ALSI facility listing and then each facility in operation with the GIE key (AGSI_API_KEY; one GIE account covers AGSI+ and ALSI), paced below GIE's 60-requests-per-minute limit and cached for three hours, and joins the figures to the terminal markers through the EIC crosswalk in the facilities dataset. Without the key, every terminal still appears with its capacity. Camera views, layer switches, the basemap choice (Esri World Dark Gray, NASA VIIRS Black Marble night lights or Esri World Imagery, all keyless) and the details card run inside the frame, so they never rerun the script. CesiumJS 1.138.0 loads from jsDelivr with Subresource Integrity. Tables under the globe carry the same data.
Other pages link straight into a view through utils/globe.py (/Energy_Globe?view=<key>):
| Link | Where | View |
|---|---|---|
| European gas infrastructure | Overview | Norwegian shelf, export pipelines and LNG terminals across Europe |
| Norwegian shelf and pipelines | Overview, Layer 3 supply mix | Shelf facilities and export pipelines over the North Sea |
| European LNG terminals | Overview, Layer 1 LNG tab | LNG terminals with live send-out over north-west Europe |
| LNG shipping chokepoints | Overview, Layer 3 TTF vs JKM | Chokepoints between the Gulf, Asia and Europe |
| Strait of Hormuz | Overview, Risk Dashboard stress scenarios | Hormuz and the Gulf |
Optional: with GLOBE_URL pointing at a running instance of the stand-alone gods-eye-view globe (where the datasets were first built), the page also offers each view there, encoding the camera and the globe's share-link layer tokens in the URL hash.
The visual language lives in utils/theme.py. The concept is a survey sheet from a grid operator's drawing office: cool engineering paper with a measuring grid, petrol ink, fjord teal for the Nordic hydro system and cobalt for the Continental gas-fired system, Big Shoulders Display for titles and figures and Archivo for text. Structure is drawn like a single-line diagram: a busbar under every page title, meters instead of cards, and a landing page that lays the eight pages out as taps on a bus. The module holds the design tokens, the global stylesheet, reusable components (page header, section header, meter with optional sparkline, callout, tag, meter strip, interconnector, diagram tap, footer) and a registered Plotly template (ptp) that every chart uses. .streamlit/config.toml mirrors the same tokens in Streamlit's native theme. The helpers in utils/helpers.py (kpi_card, commentary, pill, delta_span) keep their signatures and render through the design system.
┌─────────────────────────────────────────────────────┐
│ DATA LAYER │
│ ENTSO-E A44/A65/A69/A75/B09/B16/B19/B31 │
│ GIE AGSI+ · GIE ALSI · Nord Pool · ICE/Yahoo │
│ Fraunhofer ISE (incl. SMARD data) · FinBERT RSS │
└───────────────────────┬─────────────────────────────┘
│
┌─────────────────────────────────▼──────────────────────────────────┐
│ Layer 1: Live Market Monitor │
│ EU/DE gas storage · LNG sendout · TTF spot + forward strip │
│ Nord Pool day-ahead · Norwegian hydro · Cross-border flows │
│ German solar duck curve · Coal dispatch (fuel switching) │
│ FI minus IT-North day-ahead spread (Nordic vs gas-marginal Italy) │
│ TTF minus JKM card on the LNG send-out tab │
└─────────────────────────────────┬──────────────────────────────────┘
│
┌─────────────────────────────────▼──────────────────────────────────┐
│ Layer 2: Quantitative Analysis (14 models) │
│ Storage Monte Carlo · Gas-Power OLS · Spike detector │
│ TTF seasonal strategy · Supply stack (merit order) │
│ Nordic decomposition · Wind forecast error · Granger causality │
│ Storage-price OLS · Hydro lead/lag · Seasonal norm │
│ NO2/NL cointegration · Multi-pair scanner · Forward curve PCA │
│ Rolling cointegration stability heatmap (180-day windows) │
└─────────────────────────────────┬──────────────────────────────────┘
│
┌─────────────────────────────────▼──────────────────────────────────┐
│ Layer 3: Geopolitical and Macro Signals (5 panels) │
│ TTF + 28-event geopolitical timeline · EU supply mix 2020-2026 │
│ Gas-fertiliser-food spillover chain · 7x7 cross-commodity grid │
│ TTF vs JKM inter-basin LNG spread (EUR/MWh, 1-year percentile) │
└─────────────────────────────────┬──────────────────────────────────┘
│
┌─────────────────────────────────▼──────────────────────────────────┐
│ Layer 4: Machine Learning (3 models) │
│ PyTorch LSTM forecaster (64-32 units, honest baseline check) │
│ 4-state Gaussian HMM regime classifier │
│ Realised-volatility overlay: vol by regime, transition-spike test │
│ FinBERT news sentiment + Granger causality vs TTF returns │
└─────────────────────────────────┬──────────────────────────────────┘
│
┌─────────────────────────────────▼──────────────────────────────────┐
│ Layer 5: Mispricing Dashboard (8-signal composite scorecard) │
│ TTF seasonal · EU storage · NO2/NL spread · Gas-power residual │
│ Clean spark spread · Norwegian hydro · TTF vs storage · Fuel sw. │
└─────────────────────────────────┬──────────────────────────────────┘
┌─────────────┴───────────────────────────────────┐
│ │
┌───────────────────▼────────────────────────────┐ ┌─────────────────▼───────────────┐
│ Page 6: Risk Dashboard │ │ Page 7: BESS Optimiser │
│ VaR/ES bootstrap (1,000 paths) │ │ DAM + FCR-N/D revenue │
│ 4 named stress scenarios │ │ NPV / IRR / payback │
│ Rolling correlation matrix │ │ ±20% sensitivity table │
└────────────────────────────────────────────────┘ └─────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ Page 8: Energy Globe (3D physical infrastructure, CesiumJS) │
│ Norwegian shelf facilities and export pipelines (Sodir) │
│ 38 European LNG terminals with live GIE ALSI send-out and stocks │
│ 10 maritime chokepoints with EIA oil and LNG flows │
│ Curated views linked from Layers 1 and 3 and the Risk Dashboard │
└────────────────────────────────────────────────────────────────────┘
The layered model reflects how a physical desk processes information: raw observation (Layer 1) feeds quantitative model estimation (Layer 2), which contextualises within the macro and geopolitical regime (Layer 3), cross-validates against statistical learning signals (Layer 4), and synthesises into a ranked composite view (Layer 5). Pages 6 and 7 apply that signal infrastructure to portfolio risk and project evaluation respectively, and Page 8 maps the physical system behind the prices.
| Module | Layer / Page | Methodology | Citation / File |
|---|---|---|---|
| Gas Storage Monitor | Layer 1 | GIE AGSI+ EU/DE fill vs 5-year seasonal band (min/mean/max/P10/P90); fetched through a shared GIE client that retries rate limits and never returns a partial history | data/gas_storage.py, data/gie.py |
| LNG Terminal Sendout | Layer 1 | GIE ALSI daily sendout aggregation across Belgium, the Netherlands and France (Great Britain's terminals have not reported to ALSI since the end of 2020); WoW change alert at -15% | data/lng_terminals.py |
| TTF Spot + Forward Strip | Layer 1 | ICE TTF front-month via Yahoo Finance; MA30/MA90; synthetic M+1-M+18 strip from historical seasonal index | data/prices.py, data/forward_curve.py |
| Day-Ahead Prices | Layer 1 | ENTSO-E A44 first (NO1-NO5, SE3, DE-LU, NL, FI; time-weighted per CET delivery day), Nord Pool for the system price (SYS) and days ENTSO-E lacks, with retries; Nordic-Continental spread; Norwegian zonal (NO1-NO5 vs SYS) | data/spot_prices.py |
| FI minus IT-North Spread | Layer 1 | Daily FI minus IT-North day-ahead spread per CET delivery day (15-minute and hourly prices averaged, coverage-checked, DST-aware); KPIs: latest, 30-day average, z-score vs trailing 1-year; two-panel 1-year chart; IT-North from energy-charts.info (SMARD, CC BY 4.0) with ENTSO-E A44 fallback; FI from Nord Pool, extended with ENTSO-E A44 | data/italy_prices.py, |
| Source | Endpoint | Update Frequency | Platform Use |
|---|---|---|---|
| GIE AGSI+ | REST API (agsi.gie.eu) | Daily | EU and German gas storage fill %, working volume, seasonal bands |
| GIE ALSI | REST API (alsi.gie.eu) | Daily | LNG terminal sendout: Belgium, the Netherlands, France; WoW alert |
| GIE ALSI (per facility) | REST API (alsi.gie.eu, facility listing + one request per facility) | Daily (per gas day), cached 3 h | Energy Globe: send-out, utilisation of DTRS, tank stocks of DTMI per EU LNG terminal |
| Sodir FactMaps | Bundled (data/globe/, NLOD) | Static snapshot | Energy Globe: Norwegian shelf facilities, export pipelines, receiving terminals |
| Global Energy Monitor GGIT | Bundled (data/globe/, CC BY 4.0) | Static snapshot | Energy Globe: European LNG import terminal positions, capacity and start-up |
| U.S. EIA World Oil Transit Chokepoints | Bundled (data/globe/, public domain) | Static snapshot (Mar 2026) | Energy Globe: chokepoint oil and LNG flows |
| ENTSO-E A44 | Transparency Platform (entsoe-py) | Daily | Day-ahead prices: NO2, NL, DE, FR for regression and cointegration; FI history and IT-North fallback for the FI minus IT-North spread |
| ENTSO-E A65 | Transparency Platform (entsoe-py) | Hourly | German total load for live merit order demand input |
| ENTSO-E A69 | Transparency Platform (entsoe-py) | Day-ahead | Wind generation forecasts for forecast error tracker |
| ENTSO-E A75 | Transparency Platform (entsoe-py) | Daily | German actual generation by fuel: hard coal, lignite, solar |
| ENTSO-E B09/B10 | Transparency Platform (entsoe-py) | Hourly | Physical cross-border flows: NordLink, NorNed, NSN, Skagerrak |
| ENTSO-E B16/B18/B19 | Transparency Platform (entsoe-py) | 15-minute | Actual solar (B16) and wind (B18 offshore, B19 onshore) generation for Germany, fetched in 90-day chunks and converted to energy from hourly mean power; fallback Fraunhofer ISE |
| ENTSO-E B31 | Transparency Platform (entsoe-py) | Weekly | Norwegian hydro reservoir levels (EIC: 10YNO-0--------C) |
Notes on data availability. Gas storage (GIE), ENTSO-E flows, hydro, wind, solar, and generation all require free API keys. The platform degrades gracefully without them: all Nord Pool spot, energy-charts.info (IT-North) and yfinance data loads without credentials, covering approximately 15 of the 36 modules (the Energy Globe's static layers need no key). The public Nord Pool endpoint only serves roughly the latest two months of delivery days (older dates return HTTP 401), so keyless histories built on it (the FI leg of the FI minus IT-North spread, and the feature matrix fallback used by Layers 2 and 4) are limited to that window until ENTSOE_API_KEY is set. API2 Rotterdam coal futures (MTF=F) are no longer served by Yahoo Finance, so the 7x7 correlation grid uses Henry Hub natural gas (NG=F) in that slot. GIE allows 60 requests per minute per address, and the Hugging Face Space shares its outbound address with other tenants, so a GIE request can be refused: data/gie.py retries rate limits and gateway errors, and a fetch that still fails is retried within five minutes instead of being cached for an hour.
The Forward Curve PCA tab constructs a synthetic M+1-M+18 TTF forward panel using the model:
F(t, m) = S(t) x seasonal(m) x (1 + α x storage_z(t) x exp(-m/τ))
where S(t) is the TTF spot price, seasonal(m) is a month-specific index calibrated from the long-run annual mean ratio, storage_z(t) is the EU storage fill deviation from the 5-year same-day mean expressed in standard deviations, and the exponential decay exp(-m/τ) attenuates the storage signal for distant tenors. Parameters α = 0.08 and τ = 6 are stored in config/settings.py and are calibrated against the historical relationship between storage deviations and the winter/summer spread.
Principal components are extracted via sklearn PCA on the de-meaned panel. PC1 (level) consistently explains 85-90% of variance. PC2 captures the storage-carry slope (summer/winter spread) and PC3 captures curvature (butterfly structure). Each PC score is normalised using an expanding z-score with a 30-observation minimum burn-in period, ensuring no look-ahead bias in historical signal evaluation. A PC2 z-score beyond +2σ indicates the forward curve is steep relative to historical norms at current storage levels, generating a sell-summer/buy-winter calendar trade signal. The methodology follows Litterman and Scheinkman (1991) on level/slope/curvature factor decomposition of interest rate curves, applied to commodity forward curves in the manner described in Cortazar and Schwartz (1994).
This panel is model-derived, not observed futures quotes. The platform discloses this prominently in a KPI card on the tab and in the methodology expander, consistent with the audit commitment to model transparency.
The Nordic Decomposition tab estimates, over a rolling 90-day window, the multivariate OLS:
NO2 = β₀ + β₁ x NL + β₂ x TTF + β₃ x hydro_pct + β₄ x de_wind_gwh + ε
Regressors are standardised before fitting so that coefficient magnitudes are directly comparable across factors of different units. The model degrades gracefully: if ENTSO-E hydro or wind data is unavailable, it falls back to a 2-factor or 3-factor specification, clearly labelled in the UI.
The output includes a rolling 90-day beta time series for each factor, a "current dominant driver" badge identifying the factor with the highest |β x standardised current value| contribution, and a factor contribution stacked bar for the latest observation. In a gas-marginal regime, β₁ (NL) dominates, typically around 20-25 EUR/MWh per σ, confirming NL-NO2 coupling via NordLink and NorNed. In a hydro-driven regime (wet spring, full reservoirs), β₃ turns negative and large, reflecting Norwegian reservoir dispatch pressure on Nordic prices.
The scanner applies Engle-Granger two-step cointegration to all pairs in the 6-asset universe: NO2, NL, TTF, DE (ENTSO-E A44), FR (ENTSO-E A44), and NBP (yfinance). For each pair, both orderings are tested and the better-fitting direction (lower p-value) is retained.
For pairs with p-value below 0.05, the model computes: (1) the OLS hedge ratio β, (2) the OU mean-reversion speed λ and half-life h = -ln(2)/λ via OLS of Δε on ε_{t-1}, and (3) an expanding-window z-score of the spread residual, ensuring no future data is used in the normalisation at any historical date. The trade signal fires when |z| exceeds the configured threshold (default ±1.5σ) and the pair passes the 5% significance criterion.
Historical performance is evaluated via a simple signal backtest: enter at |z| > threshold, hold for a maximum of 3× the OU half-life, and exit at |z| < 0.5 or max holding period. Hit rate and average days to convergence are reported alongside the current signal. This follows the spread trading framework described in Geman (2005) and the practical OU parameterisation from Pole (2007).
A full-sample p-value cannot distinguish a persistent equilibrium from one that held only in part of the sample. The Rolling Cointegration Stability panel therefore re-runs the same test on rolling 180-observation windows whose end dates are spaced 21 trading days apart and anchored on the latest observation. Each pair receives the share of windows with p < 0.05, the current streak of windows sharing the latest status, and the date of the last break (a cointegrated window followed by a non-cointegrated one). Shares of at least 75% are labelled Persistent and at least 25% Episodic; the label appears in the ranking table, and the signal commentary flags lower confidence when an actionable z-score sits on a pair whose cointegration is not persistent. Shorter windows have less test power and strongly mean-reverting power prices reject the unit-root null easily, so shares are best read comparatively across pairs.
The Layer 4 overlay asks whether the HMM's regime changes coincide with volatility shocks in NO2 day-ahead prices. Realised volatility is the rolling 30-trading-day standard deviation of daily price changes in EUR/MWh. Absolute rather than log changes are used because Nordic day-ahead prices can approach zero or turn negative, which makes log changes undefined or explosive, and because the HMM's own volatility input is measured in EUR/MWh. A spike is a day with realised volatility above its in-sample 90th percentile; a transition is a day whose Viterbi-decoded regime differs from the previous day's.
The observed statistic is the share of transitions with a spike within ±5 trading days. The chance baseline is the share of all scored days with that property, i.e. the probability that a randomly placed transition would qualify. Significance comes from an exact one-sided circular-shift test: the transition sequence is rotated against the fixed spike sequence by every lag of at least 11 days, which preserves the clustering of both sequences, and p = (1 + rotations matching or beating the observed count) / (1 + rotations). Because the HMM observation vector already contains a 5-day volatility z-score, the test concerns timing rather than independence.
The BESS Optimiser models Nordic battery economics as a multi-market revenue stack. DAM arbitrage revenue is estimated from the historical NO2 spread distribution: the rolling 5-day price range is computed and the top-20% mean of that distribution, scaled by configured efficiency and dispatch hours, gives an achievable round-trip spread. Only 20% of days are assumed viable for profitable dispatch, reflecting both price level requirements and grid constraints.
FCR-N and FCR-D revenues are treated as capacity-based annuities (not degraded with battery age) using Nordic market averages of EUR 8,000/MW/yr and EUR 12,000/MW/yr respectively, with a 30% availability discount on FCR-D to reflect the symmetric SoC requirement. The financial model applies:
NPV = -CAPEX + sum over t=1 to N of [CF_t / (1+r)^t]
where CF_t = (energy revenue degraded at rate (1-d)^(t-1)) + FCR revenue - OPEX. OPEX is 1% of CAPEX per year (fixed). Depreciation is straight-line over the asset lifetime, shown in the cash flow waterfall for accounting context but excluded from the NPV and IRR calculation. IRR is solved by bisection over [0%, 300%]. The sensitivity table varies CAPEX, FCR prices, and DAM spread each by ±20%, holding other inputs constant.
The framework follows Hameed and Træholt (2025) on multi-market BESS optimisation in Nordic systems and Lohndorf and Wozabal (2024) on the value of information in intraday electricity markets.
A full P0/P1/P2/P3 pre-deployment audit was conducted and documented in files/AUDIT_2026-06-09.md. Key disclosures:
P0 theoretical fixes. The Clean Spark Spread signal in Layer 5 originally applied the current EUA spot price across all 504 historical observations, making the percentile distribution meaningless as a backtest measure. This was corrected to use the historical CO2.L price series merged by date; the signal is silently dropped if that history is unavailable. The BESS FCR sensitivity function was rewritten to use a correctly discounted annuity adjustment.
Model limitations disclosed in-app. The Forward Curve PCA panel states prominently that its M+1-M+18 panel is model-derived, not observed futures quotes. The BESS Optimiser notes that FCR price averages mask significant auction-by-auction and seasonal variation, and that grid connection costs and contracted FCR exposure are excluded. The D4 Risk Dashboard P&L is computed from historical signal performance without transaction costs, slippage, or market impact.
No look-ahead bias. All rolling z-scores in the cointegration, PCA, and Mispricing Dashboard use expanding-window normalisation: at each historical date, only data available up to that date enters the mean and standard deviation calculation. This is verified by inspection in models/feature_assembly.py and throughout views/2_Quant_Analysis.py.
ML honesty. The LSTM forecaster displays a banner if its test-set MAE exceeds the naïve persistence baseline, explicitly distinguishing "the model trains" from "the model adds forecasting value."
October 2026 review (v2.1). A source check of the Live Monitor found German coal generation four times too high: ENTSO-E reports average power per 15-minute interval, and the daily totals had summed those readings as if they were hourly energy. Energy is now the hourly mean summed over each day, and the corrected series matches published figures (2024 lignite 71.0 TWh against 71.1 TWh from Fraunhofer ISE). The same review stopped failed GIE requests from being cached for an hour, which had shown "AGSI key required" and a partial LNG total on the live Space although the key was set.
The platform is a one-person project developed with an AI coding agent, Claude Code. The agent writes most of the code, and most commits on main carry its Co-Authored-By trailer. The author decides what to build and why, frames each model in trading terms, reviews the work and the checks behind it, and decides what ships.
AI shortens the path from an idea to a working feature; it does not replace checking the result, which is where most of the review effort goes.
The platform requires Python 3.11+ and Streamlit 1.56 or later (top navigation, horizontal containers and the theme options in .streamlit/config.toml). API keys for GIE AGSI+ (agsi.gie.eu) and ENTSO-E Transparency (transparency.entsoe.eu) are free to register and unlock the full feature set. Without keys, approximately 15 of the 36 modules load using public data only.
git clone https://github.com/Gudbjerg/POWER-TRADER-PORTFOLIO.git
cd POWER-TRADER-PORTFOLIO
pip install -r requirements.txt
# requirements.txt lists PyTorch and Transformers for Layer 4. For the smaller CPU-only
# PyTorch build, install it before the requirements:
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install transformers>=4.38.0
export ENTSOE_API_KEY=<your_key>
export AGSI_API_KEY=<your_key>
export GLOBE_URL=<http://127.0.0.1:5173> # optional: also offers each globe view in the stand-alone gods-eye-view globe
streamlit run app.py
app.py is the only entry point; open the pages through the top navigation or directly by URL path (for example http://localhost:8501/Live_Monitor).
A Dockerfile and render.yaml are included for self-hosted or Render deployment. The image builds with CPU-only PyTorch and expects a persistent disk mounted at /mnt/data for the FinBERT sentiment history file.
pip install pytest
pytest -m "not network" # unit tests for the pure model and data helpers (offline, deterministic)
pytest # adds Streamlit AppTest smoke tests for the router and every page (live data, several minutes)
The offline suite runs on every push to main (.github/workflows/tests.yml). The smoke tests assert that each page runs end to end without raising; missing ENTSO-E or GIE keys are treated as an expected, gracefully degraded state.
Built by Tobias Gudbjerg, Equity Sales Trader at Fearnley Securities in Oslo. MSc Mathematical Trading and Finance, Bayes Business School, City St George's, University of London (2026). Previously almost three years at ABG Sundal Collier in Copenhagen and London.
Engle, R. F. and Granger, C. W. J. (1987). "Co-Integration and Error Correction: Representation, Estimation, and Testing." Econometrica, 55(2), 251-276.
Cortazar, G. and Schwartz, E. S. (1994). "The Valuation of Commodity-Contingent Claims." Journal of Derivatives, 1(4), 27-39.
Geman, H. (2005). Commodities and Commodity Derivatives: Modelling and Pricing for Agriculturals, Metals and Energy. Wiley Finance.
Hameed, Z. and Træholt, C. (2025). "Multi-market revenue stacking for grid-scale BESS in Nordic power systems." Applied Energy.
Hamilton, J. D. (1989). "A New Approach to the Economic Analysis of Nonstationary Time Series and the Business Cycle." Econometrica, 57(2), 357-384.
Litterman, R. and Scheinkman, J. (1991). "Common Factors Affecting Bond Returns." Journal of Fixed Income, 1(1), 54-61.
Lohndorf, N. and Wozabal, D. (2024). "Value of information in intraday electricity markets." Operations Research.
Pole, A. (2007). Statistical Arbitrage: Algorithmic Trading Insights and Techniques. Wiley Finance.
Bayes Business School coursework. The analytical framework draws on: SMM591 Commodity Derivatives and Trading; SMM284 Applied Machine Learning; SMM620 FX Trading and Hedging; SMM921 Market Microstructure.
Code: MIT License (see LICENSE), shared for portfolio review. Data fetched at runtime remains subject to the terms of the original providers: ENTSO-E Transparency Platform, Gas Infrastructure Europe (GIE), Nord Pool Group, Yahoo Finance, and Bundesnetzagentur | SMARD.de (IT-North prices via energy-charts.info, CC BY 4.0). The Energy Globe bundles static datasets under their own licences: Sodir FactMaps (NLOD), Global Energy Monitor GGIT (CC BY 4.0) and U.S. EIA (public domain); attribution is shown on the page and in data/globe/README.md. The Hugging Face deployment also ships cached snapshots of fetched data (German coal generation from ENTSO-E, the model feature matrix and the headline sentiment history) so that a fresh Space starts warm.
| Norwegian Hydro Reservoirs | Layer 1 | ENTSO-E B31 weekly aggregate for Norway (EIC: 10YNO-0--------C); P10/P50/P90 percentile bands | data/hydro.py |
| Nordic Cross-Border Flows | Layer 1 | ENTSO-E B09/B10 physical flows: NordLink (1,400 MW), NorNed (700 MW), NSN (1,400 MW), Skagerrak (1,700 MW); daily utilisation % | data/power_flows.py |
| German Solar Duck Curve | Layer 1 | ENTSO-E B16 (primary); Fraunhofer ISE energy-charts.info (fallback); hourly mean over 14-day window; cannibalisation signal | data/solar.py |
| German Coal Generation | Layer 1 | ENTSO-E A75 average power per 15-minute interval converted to energy (hourly mean MW summed per UTC day); hard coal and lignite quarterly TWh and 90-day daily trend; incremental daily cache with a seven-day revision overlap, shipped with the Space; fuel-switching indicator at TTF > 50 EUR/MWh | data/generation.py |
| Storage Refill Monte Carlo | Layer 2 | Empirical bootstrap, 1,000 paths; daily injection draws by calendar month from 5-year AGSI history (no look-ahead); P10/P25/P50/P75/P90 fan chart | models/storage_monte_carlo.py |
| Gas-to-Power OLS Regression | Layer 2 | OLS of NL day-ahead on TTF; expanding-window residual z-score; R² as regime indicator (low R² = renewables-marginal) | models/gas_power_regression.py |
| Price Spike Detector | Layer 2 | Rolling 30-day z-score across NO1/NO2/SE3/NL/FI; alert threshold | z |
| TTF Seasonal Strategy | Layer 2 | Rules-based injection-withdrawal: buy summer average, sell winter average when spread exceeds round-trip storage cost; annual P&L, Sharpe, ex-crisis stats | models/ttf_backtest.py |
| German Supply Stack | Layer 2 | Static merit order (wind/solar/nuclear/lignite/coal/gas/oil) with dynamic SRMC for gas (TTF/efficiency + EUA × emission factor) and coal; live ENTSO-E A65 demand override | models/supply_stack.py |
| Nordic Price Decomposition | Layer 2 | Rolling 90-day multivariate OLS: NO2 ~ NL + TTF + hydro_pct + de_wind_gwh; standardised regressors; dominant driver badge; factor contribution bar | models/nordic_decomp.py |
| Wind Forecast Error | Layer 2 | ENTSO-E A69 day-ahead forecast vs B18/B19 actuals; daily error (GWh, %); rolling 7-day RMSE; scatter correlation with next-day price change | models/wind_forecast_error.py |
| Granger Causality (Sentiment) | Layer 2 | FinBERT-scored daily headline sentiment; SSR F-test at lags 1-2 days vs TTF daily returns; p-value badge | views/2_Quant_Analysis.py |
| Storage-Price OLS Regression | Layer 2 | OLS of TTF on EU storage fill %; supply-risk premium = actual TTF minus storage-implied fair value; seasonal scatter with confidence bands | views/2_Quant_Analysis.py |
| Hydro Lead/Lag | Layer 2 | Pearson cross-correlation between hydro_pct and NO2 day-ahead at lags 0-21 days; peak predictive lag annotated | views/2_Quant_Analysis.py |
| TTF Seasonal Norm Tracker | Layer 2 | 7-year historical percentile bands (P10/P25/P50/P75/P90) per day-of-year; current position vs historical distribution | views/2_Quant_Analysis.py |
| NO2/NL Cointegration | Layer 2 | Engle-Granger two-step test; OLS hedge ratio; Ornstein-Uhlenbeck half-life via ADF on residuals; expanding-window z-score (no look-ahead); trade backtest with hit rate and average holding period | views/2_Quant_Analysis.py |
| Multi-Pair Cointegration Scanner | Layer 2 | Engle-Granger across all pairs in 6-asset universe (NO2, NL, TTF, DE, FR, NBP); ranked by p-value; OU half-life; real-time z-score signal; signal frequency and backtest hit rate; rolling-window Stability column | views/2_Quant_Analysis.py |
| Rolling Cointegration Stability | Layer 2 | Scanner's Engle-Granger test (same statsmodels settings, both directions, lower p-value) re-run on rolling 180-observation windows with ends every 21 trading days on a common date grid; pairs x window-end heatmap of p-values with a colour break at 0.05; per-pair share of cointegrated windows, current streak and last break date; Persistent / Episodic / Rare / Absent labels feed the scanner ranking and signal commentary | models/coint_stability.py |
| Forward Curve PCA | Layer 2 | Synthetic M+1-M+18 panel via F(t,m) = spot × seasonal_m × (1 + α × storage_z × exp(-m/τ)); sklearn PCA on de-meaned panel; expanding z-score on PC1/PC2/PC3; calendar-spread (PC2) and butterfly (PC3) signals | Litterman and Scheinkman (1991); views/2_Quant_Analysis.py |
| Geopolitical Event Overlay | Layer 3 | 29 annotated events (2022-2026): Hormuz cluster, Ras Laffan strikes, Norwegian outages, Russia cut-off, US LNG tariff threat; entries from 20 April 2026 onwards (EU ban on short-term Russian LNG, US-Iran MoU, Hormuz re-closure, June heatwave, Commission demand-curb letter, IRGC tanker strike, ENTSOG winter outlook) each carry a source URL; 5/20/60-day TTF momentum ribbon | data/events.py, data/events.json |
| EU Gas Supply Mix | Layer 3 | Annual supply-source breakdown 2020-2026: Russia pipeline, Norwegian pipeline, LNG (US/Qatari/other), Algerian, domestic; structural shift visualisation | views/3_Macro_Signals.py |
| Cross-Commodity Spillover | Layer 3 | TTF to European fertiliser (Haber-Bosch gas intensity: 1,100 m³/tonne urea equivalent) to global wheat/corn; copper-power industrial linkage; aluminium smelter stress indicator | views/3_Macro_Signals.py |
| 7x7 Correlation Grid | Layer 3 | 90-day rolling Pearson on daily log-returns across TTF, Brent, API2 coal, EUA, copper, aluminium, BDI; lead-lag sweep at τ ∈ [-10, +10] days; top 15 pairs by | ρ |
| TTF vs JKM LNG Spread | Layer 3 (card in Layer 1) | JKM front month (USD/MMBtu) converted to EUR/MWh as JKM / EURUSD / 0.293071 (EURUSD matched as of each settlement, at most 3 days old); TTF minus JKM on common settlement dates (no interpolation); rolling 1-year percentile (mid-rank, no look-ahead); KPI row, three-panel chart, computed commentary on cargo competition between basins | models/lng_arbitrage.py, data/lng_prices.py |
| LSTM Price Forecaster | Layer 4 | 2-layer PyTorch LSTM (64 → 32 units, dropout 0.2) predicting the next-day price change on top of the last price; feature matrix: NO2/NL/TTF/spread/volatility/storage/hydro/wind/calendar; time-ordered 70/15/15 split; honest baseline check (MAE vs persistence); weights stored as JSON | models/lstm_model.py |
| HMM Regime Classifier | Layer 4 | 4-state Gaussian HMM (hmmlearn); states: hydro-driven, gas-driven, renewables-driven, geopolitical stress; Viterbi decoding; 30-day regime history | models/hmm_model.py |
| HMM Realised-Volatility Overlay | Layer 4 | Rolling 30-trading-day realised volatility of NO2 day-ahead (std of daily EUR/MWh changes; log variant with non-positive prices guarded) on a secondary axis over the regime timeline; mean/median volatility per regime; share of regime transitions within ±5 trading days of a volatility spike (above the 90th percentile) against a chance baseline, with an exact circular-shift p-value | models/regime_volatility.py |
| FinBERT Sentiment | Layer 4 | ProsusAI/finbert (HuggingFace); energy-filtered RSS headlines from six feeds (BBC Business, Guardian Energy, LNG World News, Energy Monitor, Power Technology, Offshore Technology); daily net sentiment (positive - negative); 120-day history, rebuilt from the paged feed archives in the background when it holds fewer than 21 days | data/sentiment.py |
| Mispricing Dashboard | Layer 5 | 8-signal composite scorecard; each signal: current value, historical percentile (expanding window, 2-7 year history), Bullish/Bearish/Neutral badge, High/Medium/Low confidence; sorted by extremity from median | views/5_Mispricing_Dashboard.py |
| Risk Dashboard (D4) | Page 6 | 6-signal backtestable portfolio; non-parametric 1,000-path bootstrap for 95%/99% 1-day VaR and Expected Shortfall; 4 named stress scenarios; 30-day rolling signal correlation matrix | views/6_Risk_Dashboard.py |
| BESS Optimiser | Page 7 | Multi-market revenue stack: DAM arbitrage (historical NO2 spread, top-20% dispatch days), FCR-N + FCR-D (capacity-based, Nordic market averages); linear degradation on energy revenues; NPV, IRR (bisection 0-300%), simple payback; ±20% sensitivity table | Hameed and Træholt (2025); Lohndorf and Wozabal (2024); views/7_BESS_Optimiser.py |
| Energy Globe | Page 8 | CesiumJS 3D globe of Norwegian shelf facilities and export pipelines (Sodir), European LNG import terminals (GEM) joined to live GIE ALSI send-out, utilisation (send-out / DTRS) and tank stocks (inventory / DTMI) through an EIC crosswalk, and EIA chokepoint flows; paced and cached ALSI pass; curated views linked from other pages | data/energy_globe.py, components/energy_globe.py, views/8_Energy_Globe.py |
| Nord Pool Data Portal | REST API (nordpoolgroup.com) | Daily | Nordic system price (SYS) and day-ahead prices for days ENTSO-E has not yet published, or all prices without an ENTSO-E key; the public endpoint serves roughly the latest two months of delivery days |
| ICE/Yahoo Finance | yfinance (TTF=F, BZ=F, CO2.L, NBP=F) | Daily | TTF spot, Brent crude, EU carbon (EUA), NBP gas |
| Yahoo Finance | yfinance (JKM=F, EURUSD=X) | Daily | Platts JKM LNG front month (USD/MMBtu) and EUR/USD for the TTF vs JKM spread |
| Yahoo Finance | yfinance (HG=F, ALI=F, BDRY) | Daily | LME copper proxy, LME aluminium proxy, Baltic Dry (BDRY ETF) |
| Fraunhofer ISE | energy-charts.info REST API | Hourly | German solar generation and DE-LU price fallback (no key required) |
| Bundesnetzagentur SMARD (via energy-charts.info) | energy-charts.info price API (bzn=IT-North) | 15-minute (hourly before 1 Oct 2025) | IT-North day-ahead prices for the FI minus IT-North spread; CC BY 4.0, attribution: Bundesnetzagentur | SMARD.de (no key required) |
| ProsusAI FinBERT | HuggingFace Transformers | On-demand | Energy headline classification: positive/negative/neutral |
| RSS News Feeds | BBC Business, Guardian Energy, LNG World News, Energy Monitor, Power Technology, Offshore Technology | 6-hourly cache | Raw headline text for FinBERT pipeline |