Skip to content

Metahemeralism/forward_heatmap

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Electricity Forward / Futures Pricer

An interactive Streamlit app that prices electricity forward contracts under the Lucia-Schwartz two-factor log-price model with Merton-style jumps. The UI mirrors a Black-Scholes option-heatmap dashboard — sidebar parameters on the left, headline cards, a forward curve, a time-to-delivery × volatility heatmap, and a Monte Carlo sanity panel on the right.


Why this model

Electricity prices behave very differently from stock prices. Three stylised facts drive the model choice:

Feature What it captures Model component
Mean reversion Prices revert to the marginal cost of generation (power is mostly non-storable) Short-term factor $X_t$ — Ornstein-Uhlenbeck
Seasonality Annual demand / hydro / weather cycles Deterministic $f(t)$ — annual cosine harmonic
Spikes Grid stress, outages, weather Compound-Poisson jumps on $X_t$
Long-run drift Technology, carbon, capacity changes Long-term factor $Y_t$ — arithmetic Brownian motion

Combined:

$$\ln S_t = f(t) + X_t + Y_t$$

$$dX_t = -\kappa X_t, dt + \sigma_X, dW_X + J, dN, \quad dY_t = \mu_Y, dt + \sigma_Y, dW_Y$$

with $dW_X, dW_Y = \rho, dt$, jumps $J \sim \mathcal{N}(\mu_J, \sigma_J^2)$, and Poisson intensity $\lambda$.

Under the risk-neutral measure Q, the forward price has the closed form:

$$F(0, T) = e^{f(T)} \cdot \underbrace{\exp!\Big(\mathbb{E}[D] + \tfrac{1}{2}\mathrm{Var}(D)\Big)}_{\text{Gaussian MGF of diffusion part}} \cdot \underbrace{\exp!\Big(\lambda \int_0^T (\phi_J(e^{-\kappa u}) - 1), du\Big)}_{\text{jump MGF}}$$

where $\phi_J(a) = \mathbb{E}[e^{aJ}] = \exp(a \mu_J + \tfrac{1}{2} a^2 \sigma_J^2)$. The jump integral has no clean closed form when $\kappa > 0$, so it is computed via scipy.integrate.quad.


Features

  • Headline cards — spot price and 1-year forward side by side.
  • Forward curve$F(0, T)$ over a user-specified delivery horizon, with the seasonality ripple, mean-reversion decay, and jump convexity all visible.
  • Heatmap$F(0, T)$ as a function of time-to-delivery × short-term volatility $\sigma_X$. Direct analogue of the Black-Scholes call/put heatmap.
  • Monte Carlo simulator — sample paths overlaid with the closed-form forward curve. The MC mean converges to $F(0, T)$, which validates both the formula and the simulator.
  • Teaching expanders — each panel has a short "how to read this" box explaining what's happening and which parameters to vary.

Installation

Built against the local data-driven conda environment.

conda activate data-driven
pip install -r requirements.txt

Requirements: streamlit, numpy, scipy, pandas, matplotlib, seaborn.


Run

conda activate data-driven
streamlit run app.py

Opens at http://localhost:8501 by default.


File layout

File Role
models.py Lucia-Schwartz + jumps model and closed-form forward. Heavily commented with derivations.
simulation.py Monte Carlo path generator (exact OU step + Poisson-thinning jumps) for validation and path plots.
app.py Streamlit UI — sidebar inputs, headline cards, forward curve, heatmap, MC panel.
.streamlit/config.toml Dark theme configuration.
requirements.txt Python dependencies.

Sanity checks

The model has five internal consistency checks you can reproduce with:

conda activate data-driven
python -c "
import numpy as np
from models import LuciaSchwartzJumpModel, JumpParams
from simulation import simulate_paths

# 1. F(0,0) == S0 identity
m = LuciaSchwartzJumpModel(S0=50.0)
assert abs(m.forward_price(0.0) - 50.0) < 1e-9

# 2. Shape propagation
assert m.forward_curve(np.linspace(0.1, 2.0, 5)).shape == (5,)

# 3. Closed-form matches MC (no jumps)
m0 = LuciaSchwartzJumpModel(S0=50.0, sigma_X=0.4, sigma_Y=0.05,
                             jumps=JumpParams(intensity=0.0))
_, S, _, _ = simulate_paths(m0, T_horizon=1.0, n_paths=5000, seed=0)
assert abs(S[:, -1].mean() / m0.forward_price(1.0) - 1) < 0.02

# 4. Closed-form matches MC (with jumps)
m1 = LuciaSchwartzJumpModel(S0=50.0, sigma_X=0.4, sigma_Y=0.05)
_, S, _, _ = simulate_paths(m1, T_horizon=1.0, n_paths=20000, seed=0)
assert abs(S[:, -1].mean() / m1.forward_price(1.0) - 1) < 0.03

# 5. log-forward decomposition sums back to log F(0,T)
comps = m1.log_forward_components(1.0)
assert abs(sum(comps.values()) - np.log(m1.forward_price(1.0))) < 1e-9

print('All OK.')
"

Parameter cheat sheet

Reasonable ranges to start exploring (tune to your market):

Parameter Meaning Typical range Effect
kappa Mean-reversion speed (1/year) 0.5 – 50 Half-life of shocks = ln(2) / κ
sigma_X Short-term vol 0.3 – 2.0 Near-term uncertainty; damps at long T
sigma_Y Long-term vol 0.05 – 0.20 Dominates at long horizons (not damped)
mu_Y Long-term drift 0.00 – 0.05 Slope of the log-forward curve
rho Correlation −1 – 1 Coupling between short and long factors
amplitude Seasonality (log) 0.1 – 0.4 0.2 ≈ ±20% around trend
lambda Jump intensity (1/year) 2 – 20 Average jumps per year
mu_J Mean log-jump 0.1 – 0.5 Positive → up-spikes dominate
sigma_J Log-jump std 0.2 – 0.6 Jump-size dispersion

Roadmap / possible extensions

  • Historical calibration — fit parameters to real day-ahead prices (Elexon BMRS for GB, EPEX / Nord Pool elsewhere).
  • European option pricing — use the same MGF structure for calls/puts on $S_T$.
  • Swing options / storage valuation — Monte Carlo + least-squares Monte Carlo (Longstaff-Schwartz) on top of the existing simulator.
  • Weekly harmonic — add a second seasonal term to capture weekday/weekend.
  • Multi-market spark spreads — couple this model with a gas price model and price a gas-generator's option value.

References

  • Lucia, J. J. and Schwartz, E. S. (2002). Electricity prices and power derivatives: Evidence from the Nordic power exchange. Review of Derivatives Research, 5(1), 5–50.
  • Cartea, Á. and Figueroa, M. G. (2005). Pricing in electricity markets: A mean reverting jump diffusion model with seasonality. Applied Mathematical Finance, 12(4), 313–335.
  • Merton, R. C. (1976). Option pricing when underlying stock returns are discontinuous. Journal of Financial Economics, 3(1–2), 125–144.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages