Methodology
One model per market (hybrid-1.1.0 · keystone-1.0.0 · empire-1.0.0 · minuteman-1.0.0), each calibrated on its market's real settled load. A call never publishes without its inputs. Here is every input and every coefficient.
What we predict
For each eligible day we estimate P(this day ends up a coincident peak): the month's single peak for a monthly program like ERCOT 4CP, a slot in the season's ranked peaks for the rest. A day can only count if its load clears the bar it is chasing, the month-to-date maximum or the standing season ladder. We publish two numbers everywhere: p_raw (grid forecast vs the bar, before any demand response) and p_final (net of our estimated demand response). The gap between them is what we expect the market to do to itself.
The market models
The ERCOT modelhybrid-1.1.0 · ERCOT 4CP
The model in math
A calibrated logistic regression over 6 features. z = intercept + Σ wᵢ·featureᵢ, P = 1/(1+e⁻ᶻ).
| Feature | Weight | Meaning |
|---|---|---|
| intercept | -2.102917 | bias |
| margin_gw | 0.391734 | (effective_forecast_max_mw - month_max_mw) / 1000 |
| load_gw | -0.03674 | effective_forecast_max_mw / 1000 - load_center_gw |
| temp_anomaly_f | 0.280635 | heat above seasonal normal, degF (fit: ERA5 daily-max for the four load centers, 2022-2025, open-meteo archive; live: ERCOT zone vintages vs baseline) |
| dow_factor | 1.249877 | day-of-week load factor in [0,1] |
| days_remaining_norm | -2.830513 | days_remaining_in_month / 30 |
| survival_p | 2.67127 | (1 - q)^days_remaining, q = logistic(horizon params over margin_gw, load_gw); 0 when margin_gw <= 0 |
Mercury's features plus a fitted temperature anomaly and a structural survival term: the probability that no remaining day in the month beats today's expected max, from a 3-parameter exposure model. Fit on REAL ERCOT settled load, seasons 2022-2025. loso_folds hold per-season leave-one-out weights; the backtest grades each season with the fold that never saw it.
How it scored
Out-of-fold on real settled load, at the WARNING threshold: recall 93.8% (15 of 16 peaks) at 18.3% precision, with 134 false-alarm curtailment hours vs the blanket baseline's 178.
The baseline's recall of 100% is a ceiling it only reaches by flagging every candidate day (105 flagged days to our 82). Every call and every miss is on the ERCOT track record.
hybrid-1.0.0 succeeded mercury-1.0.0 (a five-feature calibrated logistic without the survival term) on July 5, 2026, before any month of the live season had closed. Mercury continues to run in shadow on every recompute, logged but never served, so the two models remain comparable on live data.
Program config
ERCOT 4CP · 2026
{
"peaks": {
"period": "month",
"count": 1
},
"eligible_months": [
6,
7,
8,
9
],
"interval_minutes": 15,
"eligible_window_ct": [
"13:00",
"20:00"
],
"levels": {
"watch": 0.25,
"warning": 0.5,
"action_interval": 0.2
},
"timezone": "America/Chicago"
}The PJM modelkeystone-1.0.0 · PJM 5CP
The model in math
A calibrated logistic regression over 4 features. z = intercept + Σ wᵢ·featureᵢ, P = 1/(1+e⁻ᶻ).
| Feature | Weight | Meaning |
|---|---|---|
| intercept | -2.422519 | bias |
| margin_gw | 0.169736 | (forecast_max_mw - cp_k_bar_mw) / 1000 |
| load_gw | 0.293142 | forecast_max_mw / 1000 - load_center_gw |
| days_remaining_norm | -1.946431 | business_days_remaining / 30 |
| survival_p | 1.007511 | P(Bin(n_remaining, q) <= k - entry_rank); q = logistic(horizon params); 0 when margin_gw <= 0 |
PJM 5CP season-top-k model, fit on REAL settled hourly RTO load and PJM's own ARCHIVED forecast vintages (load_frcstd_hist), seasons 2022-2025, business days only (NERC holidays out). Decision convention: the newest vintage evaluated by 10:00 ET supplies the day's forecast max. month_max_mw carries the season-to-date cp-k bar; bars_mw carries the ladder for entry-rank estimation; survival_p is the top-k binomial tail (the hybrid (1-q)^n is the k=1 case). No temperature feature in 1.0.0 (the forecast embeds weather); a fitted PJM temp anomaly is the 1.1.0 roadmap, mirroring hybrid's history. First fit-week days per season (fewer than k prior business days) are excluded. loso_folds grade each season with weights that never saw it.
How it scored
Out-of-fold on real settled load, at the WARNING threshold: recall 90.0% (18 of 20 peaks) at 50.0% precision, with 36 false-alarm curtailment hours vs the blanket baseline's 132.
The baseline's recall of 100% is a ceiling it only reaches by flagging every candidate day (86 flagged days to our 36). Every call and every miss is on the PJM track record.
The fit has no stand-in conventions: PJM archives its load forecast vintages, so all 320 training days use the newest forecast actually evaluated by 10:00 AM ET that morning. No temperature feature yet, since the forecast embeds weather. A fitted PJM temperature anomaly is the 1.1.0 roadmap, the same path hybrid took. PJM's official October determination uses unrestricted peaks, observed load plus estimated demand response added back. Our live standings read observed load and say so wherever they appear.
Program config
PJM 5CP · 2026
{
"peaks": {
"period": "season",
"count": 5
},
"eligible_months": [
6,
7,
8,
9
],
"interval_minutes": 60,
"eligible_window_ct": [
"12:00",
"20:00"
],
"levels": {
"watch": 0.25,
"warning": 0.5,
"action_interval": 0.2
},
"timezone": "America/New_York"
}The NYISO modelempire-1.0.0 · NYISO ICAP
The model in math
A calibrated logistic regression over 4 features. z = intercept + Σ wᵢ·featureᵢ, P = 1/(1+e⁻ᶻ).
| Feature | Weight | Meaning |
|---|---|---|
| intercept | -0.783323 | bias |
| margin_gw | 1.089542 | (forecast_max_mw - cp_k_bar_mw) / 1000 |
| load_gw | 1.30181 | forecast_max_mw / 1000 - load_center_gw |
| days_remaining_norm | -0.186612 | business_days_remaining / 30 |
| survival_p | -0.311782 | P(Bin(n_remaining, q) <= k - entry_rank); q = logistic(horizon params); 0 when margin_gw <= 0 |
NYCA season-top-1 model, fit on REAL settled hourly load and NYISO's archived daily isolf files (one true vintage per day), seasons 2022-2025, business days only (NERC holidays out). Decision convention: the newest vintage evaluated by 10:00 ET supplies the day's forecast max. month_max_mw carries the season-to-date cp-k bar; bars_mw carries the ladder for entry-rank estimation; survival_p is the top-k binomial tail (the hybrid (1-q)^n is the k=1 case). No temperature feature in 1.0.0 (the forecast embeds weather); a fitted temp anomaly is the 1.1.0 roadmap, mirroring hybrid's history. First fit-week days per season (fewer than k prior business days) are excluded. loso_folds grade each season with weights that never saw it.
How it scored
Out-of-fold on real settled load, at the WARNING threshold: recall 75.0% (3 of 4 peaks) (95% range 30%–95%) at 13.6% precision, with 38 false-alarm curtailment hours vs the blanket baseline's 46.
The baseline's recall of 100% is a ceiling it only reaches by flagging every candidate day (25 flagged days to our 22). Every call and every miss is on the NYISO track record.
Program config
NYISO ICAP · 2026
{
"peaks": {
"period": "season",
"count": 1
},
"eligible_months": [
6,
7,
8,
9
],
"interval_minutes": 60,
"eligible_window_ct": [
"12:00",
"20:00"
],
"levels": {
"watch": 0.25,
"warning": 0.5,
"action_interval": 0.2
},
"timezone": "America/New_York"
}The ISO-NE modelminuteman-1.0.0 · ISO-NE tag
The model in math
A calibrated logistic regression over 4 features. z = intercept + Σ wᵢ·featureᵢ, P = 1/(1+e⁻ᶻ).
| Feature | Weight | Meaning |
|---|---|---|
| intercept | -3.805343 | bias |
| margin_gw | 0.887247 | (forecast_max_mw - cp_k_bar_mw) / 1000 |
| load_gw | 1.673357 | forecast_max_mw / 1000 - load_center_gw |
| days_remaining_norm | -1.402993 | business_days_remaining / 30 |
| survival_p | 1.90801 | P(Bin(n_remaining, q) <= k - entry_rank); q = logistic(horizon params); 0 when margin_gw <= 0 |
ISO-NE season-top-1 model, fit on REAL settled hourly load and the settled-actual decision convention (hybrid's; no archived vintages exist keyless), seasons 2023-2025 (first season partial: keyless window opens mid-July), business days only (NERC holidays out). Decision convention: the day's own settled daily max stands in for the decision forecast (the hybrid backtest convention, disclosed on /methodology; the live engine uses the real morning forecast). month_max_mw carries the season-to-date cp-k bar; bars_mw carries the ladder for entry-rank estimation; survival_p is the top-k binomial tail (the hybrid (1-q)^n is the k=1 case). No temperature feature in 1.0.0 (the forecast embeds weather); a fitted temp anomaly is the 1.1.0 roadmap, mirroring hybrid's history. First fit-week days per season (fewer than k prior business days) are excluded. loso_folds grade each season with weights that never saw it.
How it scored
Out-of-fold on real settled load, at the WARNING threshold: recall 100.0% (3 of 3 peaks) (95% range 44%–100%) at 50.0% precision, with 6 false-alarm curtailment hours vs the blanket baseline's 30.
The baseline's recall of 100% is a ceiling it only reaches by flagging every candidate day (18 flagged days to our 6). Every call and every miss is on the ISO-NE track record.
Program config
ISO-NE capacity tag · 2026
{
"peaks": {
"period": "season",
"count": 1
},
"eligible_months": [
6,
7,
8,
9
],
"interval_minutes": 60,
"eligible_window_ct": [
"12:00",
"20:00"
],
"levels": {
"watch": 0.25,
"warning": 0.5,
"action_interval": 0.2
},
"timezone": "America/New_York"
}Validation
Every backtest number above is out-of-fold. Each model artifact carries leave-one-season-out weights (loso_folds): a season is graded by weights fit only on the other seasons in that model's corpus — four summers for ERCOT, PJM, and NYISO; three for ISO-NE, whose public feed opens mid-2023. The production weights are fit on all settled summers, but they never grade themselves. The published record comes exclusively from folds that had not seen the season being graded. The live season is the standing test in every market: calls are hash-chained the moment they publish and graded when the data settles.
The survival math generalizes across market shapes: entering PJM's ladder at rank r leaves 5−r survivable future exceedances, a binomial tail in which the 4CP formula is exactly the one-slot case. Both the TypeScript and Python implementations compute it, and shared test vectors hold them identical in CI.
Replays
Each replay page carries a fidelity label. "As it happened" days replay the predictions the engine actually published, tick by tick, next to the real forecast vintage that was newest at each moment. Nothing is recomputed, so a replay can never contradict what we served.
"Reconstructed" days predate the live engine. ERCOT does not archive its old load forecasts, so those days walk the settled hourly load forward under the backtest's own conventions (the highest load seen so far stands in for the forecast), while PJM's archived vintages let its reconstructions use the real forecasts. Either way the walk ends exactly on the graded call in the record, and the verdict shown is the record's, never the replay's.
Integrity
Every live call in every market is written to an append-only, SHA-256 hash-chained log before its interval closes. Nightly we publish the terminal hash at /v1/scorecard/attestations, so anyone can verify we never edited history.
WattMarkets is an information service, not a licensed advisor, and makes no guarantee of accuracy. Curtailment calls and their outcomes are yours.