Calculate a volatility index for the weather, to wager if things have become more volatile over time
  • Go 71.9%
  • HTML 28.1%
Find a file
2026-09-21 15:10:14 -04:00
data data/*: adding the CSV data I was working with 2026-04-30 22:31:17 -04:00
examples graphs: adding screenshots of the new graphs 2026-09-21 15:10:14 -04:00
static serve: replace year-list CSV box with an "every N years" control 2026-09-21 14:04:10 -04:00
.gitignore *: TODOs and such 2026-04-30 22:46:11 -04:00
CLAUDE.md add serve command: local dashboard for composite + volatility series 2026-09-21 13:39:07 -04:00
composite.go composite: fix stations subquery column name (id vs station_id) 2026-04-30 23:19:44 -04:00
db.go Restructure as multi-command CLI; add spatial station filtering and ingestion pipeline 2026-04-30 22:52:31 -04:00
go.mod Restructure as multi-command CLI; add spatial station filtering and ingestion pipeline 2026-04-30 22:52:31 -04:00
ingest.go ingest: fix CSV parser for NOAA wide format 2026-04-30 23:14:12 -04:00
LICENSE Initial commit 2026-05-01 02:30:28 +00:00
main.go add serve command: local dashboard for composite + volatility series 2026-09-21 13:39:07 -04:00
README.md graphs: adding screenshots of the new graphs 2026-09-21 15:10:14 -04:00
serve.go serve: add year-over-year and seasonal-overlay comparison panels 2026-09-21 13:59:27 -04:00
station.go Restructure as multi-command CLI; add spatial station filtering and ingestion pipeline 2026-04-30 22:52:31 -04:00
TODO.md add serve command: local dashboard for composite + volatility series 2026-09-21 13:39:07 -04:00
volatility.go Restructure as multi-command CLI; add spatial station filtering and ingestion pipeline 2026-04-30 22:52:31 -04:00

weather-vix

Notice: This is a fun project that might be informative for retrospect, but I would guess not great for forecasting. Further, it likely has zero business or investment significance.

Overview

Roald suggested that weather fluctuations have become more common lately. Earlier hot days and later cold days. Sudden changes within days of each other.

To deduce such volatility would need a mechanic of calculating this fluctuation — similar to the stock market ^VIX Volatility Index.

Approach

Weather stations open and close over the decades, each at a slightly different location and elevation. Naively comparing measurements across time mixes stations, introducing geographic noise. This tool addresses that by:

  1. Finding all NOAA GHCN-D stations within a radius of a target lat/long (defaults to zip 27560, Raleigh NC, 50 km)
  2. Storing all historical daily observations (TMAX, TMIN, PRCP, SNOW, SNWD) in a local DuckDB database
  3. Computing a spatially-weighted composite temperature series — each station's daily reading weighted by 1/distance² to the target — so the series is stable even as stations appear and disappear
  4. Computing a rolling standard deviation of day-over-day TMAX changes (30/90/365-day windows), annualized

Current results: 343 stations, 3M+ observations from 1891 to present, producing ~49k composite dates and a full-length volatility index.

Usage

go build ./...

# Download NOAA data for all stations within 50 km of 27560 (default target; cached in data/cache/)
./weather-vix ingest

# Compute spatially-weighted composite TMAX/TMIN series
./weather-vix composite

# Compute rolling volatility index (stores 30/90/365-day windows)
./weather-vix vix

# Query results directly
duckdb weather.db "SELECT date, value FROM volatility WHERE window_days=30 ORDER BY date DESC LIMIT 10;"

# Export to CSV for plotting
duckdb weather.db "COPY volatility TO 'volatility.csv' (HEADER, DELIMITER ',');"

# Or just look at it: local dashboard with composite temps + volatility overlay
./weather-vix serve
# → open http://localhost:8080

Graphs

In the local dashboard the current views are:

Composite Temperature (°C)

This view has tight cycles becuase it includes each year's full seasons.

composite temperature

Volatility Index (annualized σ of day-over-day ΔTMAX)

You can see the general trend of the volatility index of the cycles does not particularly trend over the 100+ years.

Volatility Index

Same Calendar Date, Year Over Year

To normalize the seasonal frequency, this view let's you choose a particular date of the year, and plot the volatility of that date over the timeline. Say whether December 1st (+/- 1 day) has become more volatile over the years.

Same date, year over year

Compare Years (seasonal overlay)

This view normalizes to see seasonal volatility over a sample of the timeline, all overlayed to a 12 month period. So if the color of the newer years floats to the top as generally being more volatile weather changes than previous years.

compare years

Running this for your own location

The whole pipeline targets one point in space. To swap it for anywhere else:

  1. Get lat/long for your area. NOAA's own GHCN-D station list has no zip field — only lat/long — so that's the real search key here. A zip code is just a convenience other tools geocode to lat/long before ever talking to NOAA. Look yours up with any map (e.g. right-click a spot on Google Maps and copy the coordinates, or search " lat long").

  2. Run the pipeline with --lat/--long/--radius-km, using a dedicated --db for that location (composite/vix each work off a single DB's worth of stations, so don't mix locations into one file):

    ./weather-vix ingest    --lat 40.7128 --long -74.0060 --radius-km 50 --db nyc.db
    ./weather-vix composite --db nyc.db
    ./weather-vix vix       --db nyc.db
    
    duckdb nyc.db "SELECT date, value FROM volatility WHERE window_days=30 ORDER BY date DESC LIMIT 10;"
    
    # Or view it in the dashboard instead of querying directly:
    ./weather-vix serve --db nyc.db --addr :8081
    

    All three flags are optional and independently overridable — omit --radius-km to keep the 50 km default, etc. ingest is idempotent and caches downloaded CSVs under --cache, so re-running it (e.g. to pull in fresh days) only fetches what's missing.

Research Notes

  • Volatility definition: rolling std-dev of day-over-day TMAX delta, annualized (σ * sqrt(365))
  • Spatial weighting: inverse-distance-squared; stations at zero distance use an epsilon of 0.01 km²
  • Station coverage: USW/USC prefix stations (ASOS/COOP network) have century-long TMAX/TMIN records; US1NC* stations (CoCoRaHS citizen science) are denser but precipitation-only and recent

Open questions / future work

  • Seasonal decomposition to distinguish anomalous volatility from expected seasonal swings
  • Including PRCP as an additional volatility signal
  • Normalizing the VIX scale to a baseline period for interpretability

Data Sources

References