Build a Natural Gas Price Monitoring Dashboard with Python and the Energy Volatility API
Learn how to build a real-time natural gas price monitoring dashboard using the Energy Volatility API. Track Henry Hub spot prices, volatility indices, and storage data.

Natural gas markets are among the most volatile commodity markets in the world. Henry Hub spot prices can swing 20–30% in a single week on weather forecasts, storage inventory reports, or LNG export disruptions. For energy procurement teams, utilities, and industrial consumers, missing these moves is expensive.
A real-time natural gas monitoring dashboard gives you the edge: continuous price tracking, volatility signals, storage trend analysis, and automated alerts when conditions cross your risk thresholds.
This guide shows you how to build one using the Energy Volatility API and Python.
What the Dashboard Tracks
A production-grade natural gas monitoring dashboard covers four data layers:
- Spot prices — Henry Hub cash price, day-ahead forward, and regional basis differentials
- Volatility indices — 30-day rolling volatility, annualized implied volatility, ATR (Average True Range)
- Storage data — EIA weekly injection/withdrawal vs. five-year average
- Seasonal spread — Winter vs. summer price differential to track injection season dynamics
Step 1: Environment Setup
pip install requests pandas plotly dash python-dotenvimport os
import requests
import pandas as pd
from datetime import datetime, timedelta
from dotenv import load_dotenv
load_dotenv()
ENERGY_API_KEY = os.getenv("YOUR_API_KEY")
ENERGY_API_BASE = "https://apivult.com/api/energy-volatility"
HEADERS = {
"X-RapidAPI-Key": ENERGY_API_KEY,
"Content-Type": "application/json"
}Step 2: Fetch Real-Time Henry Hub Data
def get_henry_hub_snapshot() -> dict:
"""Fetch current Henry Hub spot price and volatility metrics."""
resp = requests.get(
f"{ENERGY_API_BASE}/spot",
params={
"commodity": "natural_gas",
"hub": "henry_hub",
"metrics": "price,volatility_30d,atr_14d,change_pct"
},
headers=HEADERS
)
resp.raise_for_status()
return resp.json()["data"]
def get_price_history(days: int = 90) -> pd.DataFrame:
"""Fetch historical price series for trend analysis."""
end_date = datetime.utcnow().date()
start_date = end_date - timedelta(days=days)
resp = requests.get(
f"{ENERGY_API_BASE}/history",
params={
"commodity": "natural_gas",
"hub": "henry_hub",
"start": start_date.isoformat(),
"end": end_date.isoformat(),
"interval": "daily"
},
headers=HEADERS
)
resp.raise_for_status()
records = resp.json()["data"]["prices"]
df = pd.DataFrame(records)
df["date"] = pd.to_datetime(df["date"])
df.set_index("date", inplace=True)
return dfStep 3: Pull EIA Storage Data
Weekly EIA storage reports are among the most market-moving data points for natural gas. The Energy Volatility API aggregates these into a clean, structured feed.
def get_storage_data(weeks: int = 52) -> pd.DataFrame:
"""Fetch EIA natural gas storage injection/withdrawal history."""
resp = requests.get(
f"{ENERGY_API_BASE}/storage",
params={
"commodity": "natural_gas",
"region": "lower_48",
"weeks": weeks
},
headers=HEADERS
)
resp.raise_for_status()
records = resp.json()["data"]["storage"]
df = pd.DataFrame(records)
df["report_date"] = pd.to_datetime(df["report_date"])
df.set_index("report_date", inplace=True)
return df
def compute_storage_deficit(storage_df: pd.DataFrame) -> pd.DataFrame:
"""Calculate storage vs. five-year average deficit."""
storage_df["deficit_bcf"] = (
storage_df["five_year_avg_bcf"] - storage_df["current_inventory_bcf"]
)
storage_df["deficit_pct"] = (
storage_df["deficit_bcf"] / storage_df["five_year_avg_bcf"] * 100
)
return storage_dfStep 4: Calculate Volatility Signals
def compute_volatility_signals(price_df: pd.DataFrame) -> pd.DataFrame:
"""Add rolling volatility and signal columns to price history."""
df = price_df.copy()
# Log returns
df["log_return"] = (df["close_price"] / df["close_price"].shift(1)).apply(
lambda x: pd.NA if pd.isna(x) else __import__("math").log(x)
)
# 30-day rolling annualized volatility
df["vol_30d"] = df["log_return"].rolling(30).std() * (252 ** 0.5) * 100
# 7-day simple moving average
df["sma_7d"] = df["close_price"].rolling(7).mean()
# Bollinger Bands (20-day, 2 std)
rolling_20 = df["close_price"].rolling(20)
df["bb_upper"] = rolling_20.mean() + (rolling_20.std() * 2)
df["bb_lower"] = rolling_20.mean() - (rolling_20.std() * 2)
# Price vs. Bollinger Band position
df["bb_position"] = (df["close_price"] - df["bb_lower"]) / (
df["bb_upper"] - df["bb_lower"]
)
return dfStep 5: Set Up Threshold Alerts
def evaluate_alerts(snapshot: dict, price_df: pd.DataFrame) -> list[dict]:
"""Check current conditions against user-defined thresholds."""
alerts = []
current_price = snapshot["price_usd_mmbtu"]
current_vol = snapshot["volatility_30d_pct"]
# Price spike alert
if snapshot["change_pct_24h"] > 5.0:
alerts.append({
"severity": "HIGH",
"type": "PRICE_SPIKE",
"message": f"Henry Hub up {snapshot['change_pct_24h']:.1f}% in 24h — "
f"current: ${current_price:.3f}/MMBtu"
})
# Volatility regime alert
if current_vol > 60.0:
alerts.append({
"severity": "HIGH",
"type": "VOLATILITY_ELEVATED",
"message": f"30-day volatility at {current_vol:.1f}% — "
"hedging review recommended"
})
# Storage deficit alert
latest_row = price_df.iloc[-1]
if latest_row.get("deficit_pct", 0) > 10:
alerts.append({
"severity": "MEDIUM",
"type": "STORAGE_DEFICIT",
"message": f"Storage {latest_row['deficit_pct']:.1f}% below 5yr avg — "
"bullish price pressure likely"
})
return alertsStep 6: Build the Plotly Dash Dashboard
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import dash
from dash import dcc, html, callback, Output, Input
def build_price_chart(price_df: pd.DataFrame) -> go.Figure:
"""Build a candlestick + volatility subplot."""
fig = make_subplots(
rows=2, cols=1,
row_heights=[0.7, 0.3],
shared_xaxes=True,
subplot_titles=("Henry Hub Natural Gas ($/MMBtu)", "30-Day Volatility (%)")
)
# Candlestick
fig.add_trace(go.Scatter(
x=price_df.index,
y=price_df["close_price"],
name="Price",
line=dict(color="#3b82f6", width=2)
), row=1, col=1)
# Bollinger Bands
fig.add_trace(go.Scatter(
x=price_df.index,
y=price_df["bb_upper"],
name="BB Upper",
line=dict(color="#94a3b8", dash="dash", width=1),
showlegend=False
), row=1, col=1)
fig.add_trace(go.Scatter(
x=price_df.index,
y=price_df["bb_lower"],
name="BB Lower",
line=dict(color="#94a3b8", dash="dash", width=1),
fill="tonexty",
fillcolor="rgba(148,163,184,0.1)",
showlegend=False
), row=1, col=1)
# Volatility
fig.add_trace(go.Scatter(
x=price_df.index,
y=price_df["vol_30d"],
name="Volatility",
line=dict(color="#f97316", width=2),
fill="tozeroy",
fillcolor="rgba(249,115,22,0.1)"
), row=2, col=1)
fig.add_hline(y=60, line_dash="dot", line_color="red",
annotation_text="High Vol Threshold", row=2, col=1)
fig.update_layout(
height=600,
template="plotly_dark",
title="Natural Gas Price & Volatility Monitor",
hovermode="x unified"
)
return fig
# Dash app
app = dash.Dash(__name__)
app.layout = html.Div([
html.H1("Natural Gas Market Dashboard", style={"fontFamily": "sans-serif"}),
html.Div(id="alert-panel"),
dcc.Graph(id="price-chart"),
dcc.Graph(id="storage-chart"),
dcc.Interval(id="refresh", interval=5 * 60 * 1000, n_intervals=0) # 5-min refresh
])
@callback(
[Output("price-chart", "figure"),
Output("alert-panel", "children")],
Input("refresh", "n_intervals")
)
def update_dashboard(n):
snapshot = get_henry_hub_snapshot()
price_df = compute_volatility_signals(get_price_history(90))
alerts = evaluate_alerts(snapshot, price_df)
alert_divs = [
html.Div(
f"⚠ [{a['severity']}] {a['message']}",
style={"color": "red" if a["severity"] == "HIGH" else "orange",
"fontFamily": "monospace", "padding": "4px"}
)
for a in alerts
]
return build_price_chart(price_df), alert_divs
if __name__ == "__main__":
app.run(debug=True, port=8050)Running the Dashboard
python dashboard.py
# Open http://localhost:8050 in your browserThe dashboard auto-refreshes every 5 minutes. For production, deploy it behind nginx with gunicorn:
gunicorn dashboard:server -b 0.0.0.0:8050 --workers 2What You Get
| Signal | Use Case |
|---|---|
| Real-time spot price | Trigger procurement decisions at target levels |
| 30-day rolling volatility | Size hedges proportionally to market risk |
| Bollinger Band position | Identify mean-reversion entry points |
| Storage deficit alerts | Anticipate winter price spikes early |
| 24h price change alerts | React to EIA reports and weather events |
Expected Business Impact
Natural gas buyers who monitor volatility in real time typically:
- Reduce hedge timing costs by 12–18% by avoiding peak volatility windows
- Improve budget accuracy for energy-intensive operations
- Catch injection season trends 4–6 weeks earlier than monthly reporting cycles
Start monitoring natural gas markets in real time. Get your Energy Volatility API key and deploy this dashboard today.
More Articles
Build a Real-Time Energy Price Alert System with Python and the Volatility API
Learn how to build automated energy price monitoring and threshold alerts using the Energy Volatility API — with Python, webhooks, and email notifications.
April 3, 2026
Energy Market Risk Management: Integrating Real-Time Volatility Data into Trading Systems
Learn how to integrate the Energy Volatility API into risk management systems to calculate VaR, track price volatility, and trigger hedging alerts using Python.
March 31, 2026