Layer 3 — Meteorological Data
Public NWP model data — GFS, ECMWF, HRRR, and NWS-LAMP — plus observed data sources for all weather variables.
Layer 3 is the foundation: public numerical weather prediction (NWP) model output and observed ground truth. This is the same data available to everyone — the engine's value is in what it does with it, not in exclusive access.
NWP Models
GFS (Global Forecast System)
| Attribute | Detail |
|---|---|
| Operator | NOAA/NCEP |
| Resolution | 0.25 deg (~28km) global |
| Ensemble members | 31 (GEFS) |
| Update frequency | 4 runs/day (00Z, 06Z, 12Z, 18Z) |
| Forecast horizon | 384 hours (16 days) |
| Access | NOMADS server, Open-Meteo API |
| Cost | Free, no API key |
| Variables used | temperature_2m_max, temperature_2m_min, precipitation_sum, wind_speed_10m_max, wind_gusts_10m_max, shortwave_radiation_sum |
GFS is the primary ensemble source for probability estimation. The 31-member GEFS ensemble provides the base probability distribution for all five weather variables.
ECMWF (European Centre for Medium-Range Weather Forecasts)
| Attribute | Detail |
|---|---|
| Operator | ECMWF |
| Resolution | 0.25 deg (operational), variable (ensemble) |
| Ensemble members | 51 |
| Update frequency | 2 runs/day (00Z, 12Z) |
| Forecast horizon | 15 days |
| Access | Open Data (free subset) via Open-Meteo |
| Cost | Free (Open Data tier); paid for full resolution |
| Engine model ID | ecmwf_ifs025 |
ECMWF is widely regarded as the best global NWP model. The 51-member ensemble provides richer probability information than GFS. The engine uses both GFS and ECMWF with model-specific bias correction and pooled member counting (82 total members).
HRRR (High-Resolution Rapid Refresh)
| Attribute | Detail |
|---|---|
| Operator | NOAA/NCEP |
| Resolution | 3km (CONUS only) |
| Ensemble members | Deterministic (single run) |
| Update frequency | Hourly |
| Forecast horizon | 48 hours |
| Access | NOMADS, Open-Meteo |
| Cost | Free |
HRRR provides the highest spatial resolution for US locations. It's deterministic (no ensemble), so it's used as a high-resolution point estimate rather than a probability source.
NWS-LAMP (Localized Aviation MOS Program)
| Attribute | Detail |
|---|---|
| Operator | NOAA/MDL |
| Resolution | Station-level (2000+ US stations) |
| Update frequency | Hourly |
| Forecast horizon | 38 hours |
| Access | FTP (tgftp.nws.noaa.gov), LAMP web services |
| Purpose | Baseline to beat — LAMP is the benchmark for short-range station-level forecasts |
LAMP is the most directly comparable public forecast to what the engine produces. If the engine can't beat LAMP at D+1 station-level temperature forecasting, the bias correction and ensemble weighting aren't adding value.
LAMP access: The engine parses the live LAMP bulletin at lamp.mdl.nws.noaa.gov/lamp/Data/bull/lavlamp.txt — a single ~5 MB text file containing all ~2,000 CONUS stations, updated hourly. The parser extracts the TMP row (hourly temperature in degrees F) for each ICAO station and derives the daily high from the maximum across forecast hours. Implementation: src/data_ingestion/lamp_forecast.py.
Observed Data (Ground Truth)
IEM (Iowa Environmental Mesonet)
The primary source for US station data. Free, clean, station-level ASOS/AWOS data covering all major airport stations with decades of history. Used for all non-irradiance variables.
API: mesonet.agron.iastate.edu/cgi-bin/request/daily.py
IEM API quirks:
- Requires state-specific network codes (e.g.,
NY_ASOS,IL_ASOS) - Uses 3-letter station identifiers (strip leading
Kfrom ICAO:KORD→ORD) - Returns ALL columns regardless of the
varsparameter - Column names differ from
varsparameter names:max_temp_f(notmax_tmpf)min_temp_f(notmin_tmpf)precip_in(notprecip)max_wind_speed_kts(notmax_sknt)max_wind_gust_kts(notgust_sknt)
Variables downloaded from IEM:
| Function | Variable | Output Column | Unit |
|---|---|---|---|
download_daily_highs() | Daily max temperature | max_temp_f | °F |
download_daily_lows() | Daily min temperature | min_temp_f | °F |
download_daily_precip() | Daily precipitation | precip_inches | inches (renamed from precip_in) |
download_daily_max_wind() | Max sustained wind | max_wind_mph | mph (converted from max_wind_speed_kts * 1.15078) |
download_daily_max_gust() | Max wind gust | max_gust_mph | mph (converted from max_wind_gust_kts * 1.15078) |
All implemented in src/data_ingestion/iem_observed.py.
ERA5 Reanalysis
IEM has no solar radiation data. The engine uses ERA5 reanalysis as independent ground truth for irradiance.
| Attribute | Detail |
|---|---|
| Source | Open-Meteo ERA5 Archive API |
| URL | archive-api.open-meteo.com/v1/archive |
| Variable | shortwave_radiation_sum (MJ/m^2) |
| Independence | ERA5 assimilates millions of global observations — its irradiance values are independent of GFS/ECMWF forecasts |
Implementation: download_era5_irradiance() in src/data_ingestion/open_meteo_reanalysis.py.
NWS Climatological Reports
The official settlement source for ForecastEx contracts. Published the morning after each observation day. Contains verified maximum/minimum temperature for the official reporting period.
Phase 0 — Baseline Accuracy (All Variables)
Temperature — Daily High (GFS)
Period: January 1 – March 31, 2026 (90 days, 10 cities, 900 city-days)
| Station | City | MAE (°F) | Bias (°F) | RMSE (°F) |
|---|---|---|---|---|
| KNYC | New York City | 1.41 | -0.77 | 1.99 |
| KORD | Chicago | 1.05 | +0.01 | 1.38 |
| KLAX | Los Angeles | 0.99 | -0.22 | 1.32 |
| KDFW | Dallas | 1.11 | -0.96 | 1.35 |
| KIAH | Houston | 1.00 | -0.55 | 1.41 |
| KPHX | Phoenix | 1.47 | -1.45 | 1.78 |
| KDEN | Denver | 1.17 | -0.68 | 1.49 |
| KMIA | Miami | 1.34 | -1.22 | 1.61 |
| KATL | Atlanta | 1.58 | -1.42 | 1.83 |
| KSFO | San Francisco | 1.33 | -0.13 | 1.67 |
| Overall | 1.24 | -0.74 | 1.60 |
Key findings:
- Consistent cold bias — GFS underforecasts daily highs at 9/10 cities (mean bias -0.74°F)
- City-specific patterns — Phoenix (-1.45°F), Atlanta (-1.42°F), and Miami (-1.22°F) have the largest biases
- NYC has highest RMSE (1.99°F) — the most variable forecast errors, making it the best ForecastEx market for probability-based trading
- Chicago is near-zero bias (+0.01°F) — hardest city to improve upon with correction alone
Rainfall (GFS precipitation_sum vs IEM precip_in)
Period: Jan–Mar 2026, 10 cities.
| Metric | Value |
|---|---|
| MAE | 0.036 inches |
| Bias | -0.0018 inches |
Low MAE reflects the preponderance of dry days. GFS quantitative precipitation forecasts are essentially unbiased at the daily level, though individual storm events can have large errors.
Wind Speed (GFS wind_speed_10m_max vs IEM max_wind_speed_kts)
Period: Jan–Mar 2026, 10 cities.
| Metric | Value |
|---|---|
| MAE | 7.16 mph |
| Bias | +7.09 mph (massive positive bias) |
GFS 10m grid-average wind systematically overforecasts compared to IEM station-level sustained wind. This is a known scale mismatch: GFS represents the average wind over a ~28km grid cell, while ASOS measures point observations at a single anemometer. The multiplicative correction ratio of ~1.61x addresses this effectively.
Irradiance (GFS shortwave_radiation_sum vs ERA5)
Period: Jan–Mar 2026, 10 cities.
| Metric | Value |
|---|---|
| MAE | 1.49 MJ/m^2 |
| Bias | -0.13 MJ/m^2 |
GFS irradiance forecasts have a small negative bias against ERA5 reanalysis. The engine normalises to a clear-sky index (CSI = actual / theoretical clear-sky) before applying the Beta distribution, which handles the bounded [0, 1] domain naturally.
Phase 1 — Bias Correction Results (All Variables)
Temperature — Additive Correction
Per-city, per-month additive bias correction trained on Jan–Feb 2026, tested on March 2026:
| Station | City | Raw MAE | Corrected MAE | Raw Bias | Corrected Bias |
|---|---|---|---|---|---|
| KATL | Atlanta | 1.27 | 0.93 | -1.16 | +0.38 |
| KDEN | Denver | 1.49 | 1.28 | -0.58 | +0.15 |
| KDFW | Dallas | 1.12 | 0.91 | -0.90 | +0.09 |
| KIAH | Houston | 1.17 | 0.97 | -0.88 | -0.49 |
| KLAX | Los Angeles | 0.92 | 0.89 | -0.20 | +0.03 |
| KMIA | Miami | 1.38 | 0.98 | -1.30 | -0.13 |
| KNYC | New York City | 1.60 | 1.65 | -0.62 | +0.24 |
| KORD | Chicago | 1.02 | 1.13 | -0.52 | -0.80 |
| KPHX | Phoenix | 1.42 | 0.61 | -1.41 | +0.06 |
| KSFO | San Francisco | 1.22 | 1.25 | +0.18 | +0.48 |
| Overall | 1.26 | 1.06 | -0.74 | 0.00 |
Key findings:
- Bias eliminated — overall reduced from -0.74°F to 0.00°F
- MAE improved 16% — from 1.26°F to 1.06°F
- NYC and Chicago slightly worse — training bias overshoots for March. Suggests seasonal or regime-specific correction needed.
- Correction is simple —
corrected = raw + bias, per-city, per-month
Rainfall — Multiplicative Correction
ratio = mean(observed_wet) / mean(forecast_wet) per (station, month)
corrected = raw * ratio
With p_zero_obs and p_zero_fct stored for zero-inflated Gamma probability computation. The multiplicative method preserves the zero bound (additive correction can produce negative rainfall).
Wind — Multiplicative Correction
Same method as rainfall. The large scale mismatch (GFS overforecasts by ~7 mph) is captured by the ratio parameter, which is typically ~0.6 (GFS values need to be scaled down to match station observations).
Irradiance — Multiplicative Correction
Applied to raw MJ/m^2 values before clear-sky index computation. Small correction needed (GFS irradiance bias is only -0.13 MJ/m^2).
Phase 1 — Calibration Scores (All Variables)
| Variable | Brier (Raw) | Brier (Calibrated) | Improvement | Distribution | Bias Method |
|---|---|---|---|---|---|
| Temperature (DH) | 0.0374 | 0.0349 | +0.0025 | Gaussian | Additive |
| Rainfall | 0.0403 | 0.0292 | +0.0111 | Gamma (zero-inflated) | Multiplicative |
| Wind Speed | 0.0514 | 0.0462 | +0.0052 | Weibull | Multiplicative |
| Irradiance | 0.0530 | 0.0498 | +0.0032 | Beta (CSI) | Multiplicative |
All variables comfortably pass the Brier < 0.10 target. Rainfall shows the largest calibration improvement, likely because the zero-inflated distribution introduces systematic raw probability biases that isotonic regression corrects effectively.
Access via Open-Meteo
Rather than pulling raw GRIB2 files from NOMADS, the engine uses the Open-Meteo API — a free service that provides parsed, JSON-formatted NWP data with no API key required.
This simplifies the ingest pipeline significantly: instead of downloading, parsing, and spatially interpolating multi-gigabyte GRIB2 files, the engine makes REST API calls for specific locations and variables.
Three endpoints used:
| Endpoint | Purpose |
|---|---|
ensemble-api.open-meteo.com/v1/ensemble | Live GFS 31-member + ECMWF 51-member ensemble |
historical-forecast-api.open-meteo.com/v1/forecast | Archived deterministic forecasts for backtesting |
archive-api.open-meteo.com/v1/archive | ERA5 reanalysis for irradiance ground truth |
The download_historical_forecasts() function in src/data_ingestion/open_meteo.py accepts configurable variables, unit_params, and value_columns parameters, allowing it to serve all registered weather variables through a single function. The download_ensemble_forecast() function similarly accepts a value_col parameter for generalized column naming.