Net price area
AOC
Accumulates the signed area of five-minute dispatch prices, so negative prices offset positive prices.
net_area = 0
FOR EACH 5-minute price:
net_area = net_area + (price × 5 / 60)
RETURN net_area
Battery arbitrage
SPREAD
The strongest gross arbitrage margin available to an ideal 1 MW battery across 1-hour, 2-hour, and 4-hour charge/discharge windows. Longer windows are normalized per MWh so every duration is comparable.
FOR duration IN [1 hour, 2 hours, 4 hours]:
best_profit = maximum profit from an earlier non-overlapping charge block
followed by a discharge block of the same duration
margin[duration] = best_profit / duration_hours
RETURN MAX(margin)
Severity-weighted price spikes
SPIKE
Combines all three price-spike tiers into one score while retaining their relative severity.
score = 0
FOR EACH 5-minute price:
IF price >= $500/MWh: score = score + 3
ELSE IF price >= $300/MWh: score = score + 2
ELSE IF price >= $150/MWh: score = score + 1
RETURN score
Price volatility
σ
Measures the root-mean-square change between consecutive prices.
changes = each price - its previous price
squared_changes = each change × itself
RETURN SQUARE_ROOT(AVERAGE(squared_changes))
Signal significance strength
SIG
Shows the strongest family-corrected signal on a sparse five-step scale in either direction.
families = [price balance, price extremes, price movement]
FOR EACH family:
adjusted_p = MIN(metric p-values) × metrics in family
strongest_p = MIN(adjusted family p-values)
IF strongest_p <= 0.001: strength = 5
ELSE IF strongest_p <= 0.005: strength = 4
ELSE IF strongest_p <= 0.02: strength = 3
ELSE IF strongest_p <= 0.10: strength = 2
ELSE IF strongest_p <= 0.32: strength = 1
ELSE: strength = 0
DISPLAY strength away from centre in the strongest signal's direction
Shared scoring
Similar Days
Compares a metric with seasonally similar recent days, weighting matching weekdays more heavily.
baseline = same-season results from previous 365 days
weight matching weekdays 3×
p = two-sided tail probability, including ties
significant when p <= 0.05
Shared scoring
All Days
Compares a metric with every available recent day using equal weighting.
baseline = all results from previous 730 days
give every result equal weight
p = two-sided tail probability, including ties
significant when p <= 0.05
Volatility decomposition
Five-minute volatility layers
A robust, causal decomposition separates calendar patterns, a slowly changing level, residual carry, and five-minute innovations. Dollar differences support zero and negative prices. Every estimate uses the original five-minute observations.
price[t] = calendar[t] + slow_level[t] + residual_carry[t] + observed_shock_carry[t] + innovation[t]
innovation[t] = ordinary[t] + shock[t] # mutually exclusive contributions
calendar = robust harmonic regression fitted to earlier prices
residual_carry[t] = phi × bounded residual[t - 1]
realized_volatility[t] = sqrt(mean(last 12 consecutive innovations squared))
total_variance = ordinary_variance + shock_variance
percentile = empirical position versus a frozen historical reference
shock: |innovation / prior local scale| > max(4, historical P99.5)
extreme shock: above historical P99.9 (and the large-shock threshold)
Low < P25; typical P25–75; elevated P75–90; high P90–99; extreme P99+
All views use the same model and original five-minute signals.
Daily, weekday and weekend patterns are fitted robustly; annual terms and daily/annual interactions require at least eighteen months of fitting history. Each calendar year uses up to two earlier years, excluding a four-week temporal holdout. A ninety-day warmup makes the causal level and scale independent of the displayed range. Short datasets bootstrap after eight weeks; annual seasonality remains unassessed.
The sidebar shows price structure, residual layers, ordinary and shock volatility, and the distribution of volatility percentiles. Signed-log scaling changes drawing only. Exact timestamp inspection returns an original five-minute result. The historical reference contains fitted innovations; holdout validation is reported separately. Volatility bands are descriptive, not latent economic regimes. An extreme innovation is not proof that an event was impossible to forecast with weather, demand or bids.
View the production decomposition source. Research: robust seasonal and spike identification; dynamic harmonic regression; jump-robust volatility estimation.
Period moves
Descriptive comparisons of mean price, five-minute volatility, the P90–P10 price range, and the shares of negative prices and prices above $300/MWh. Both sides use matching five-minute slots and the same elapsed part of the period. Weekly views align weekdays; other views align calendar days and times. Invalid leap/month-end days and missing observations are excluded from both sides. Volatility uses identical consecutive pairs on both sides. Prior 3 years averages the three matched yearly metrics with equal weights. In All time, moves cover the latest year to date.
change = matched_current_metric - matched_reference_metric
IF reference > 0 and metric is a price / volatility / range:
percentage_change = 100 × change / reference
ELSE: show absolute change only
Negative / high-price shares: change is in percentage points
Green ↑ = higher; red ↓ = lower; these are not significance tests.
Price structure
Slow level and residual persistence
The volatility sidebar replaces the former full-period linear trend with an adaptive slow level and a bounded AR(1) residual carry. These components use earlier observations and are separated from calendar seasonality and new price innovations. Residual half-life describes fitted persistence; it is not a statistical proof of mean reversion.
Volatility distribution
Volatility bands
The sidebar classifies each assessed five-minute interval by its trailing-hour innovation volatility relative to the region's frozen historical reference: low below P25, typical P25–75, elevated P75–90, high P90–99 and extreme P99+. The distribution shows how much of the displayed period occupies each band. These bands describe observed volatility and do not identify hidden economic regimes.
Expected versus actual
Expected
Compares the actual historical price with four nested recency-weighted expected ranges.
weight = 0.5 ^ (age_in_days / 180)
expected_history = same season + time slot
IF effective sample size >= 12:
DRAW P1 to P99
DRAW P5 to P95 over it
DRAW P20 to P80 over it
DRAW P40 to P60 over it
IF actual_price < P40:
DRAW actual price in red
ELSE IF actual_price > P60:
DRAW actual price in green
ELSE:
DRAW actual price in black
Comparison overlay
Last week avg
Aligns prices from the equivalent period one week earlier with the current chart.
source_start = displayed_period_start - 1 week
source_end = source_start + displayed_period_length
FOR EACH selected display bucket:
aligned_price = AVERAGE(prices in matching source bucket)
DRAW aligned_price at the equivalent current-period time
Comparison overlay
Last year avg
Aligns prices from the equivalent period 52 weeks earlier with the current chart.
source_start = displayed_period_start - 52 weeks
source_end = source_start + displayed_period_length
FOR EACH selected display bucket:
aligned_price = AVERAGE(prices in matching source bucket)
DRAW aligned_price at the equivalent current-period time
Comparison overlay
Prior 3 years avg
Averages three equivalent 52-week-offset periods into one aligned comparison line.
source_periods = [52 weeks ago, 104 weeks ago, 156 weeks ago]
FOR EACH selected display bucket:
aligned_values = matching prices from all 3 source periods
aligned_price = AVERAGE(aligned_values)
DRAW aligned_price at the equivalent current-period time