Skip to main content

Python client for Volatile forecast data via the public GraphQL API.

Project description

Volatile Forecast SDK

Python client for authenticated access to forecast dataset queries on the Volatile public GraphQL API. It focuses on a small, typed surface (execute_dataset_query, my_datasets, me, token lifecycle) with client-side validation so integrators pick only supported bidding zones, IANA time zones, percentile fields, and aggregation modes.

About Volatile

Volatile is an energy intelligence company focused on making European power-market data and forecasting workflows easier to consume and operationalize.
This SDK is one of the tools provided to help customers integrate Volatile forecast products directly into Python-based analytics, trading, and automation pipelines.

Install

From PyPI (recommended):

pip install volatile-forecast-sdk

Runtime dependency: tzdata (IANA zone database for zoneinfo on all platforms).

Usage examples (login, dataset listing, query execution, and token-only auth) are available in examples/.

License and access model

This SDK is open source under the Apache-2.0 license and is publicly installable.

API usage still requires a valid Volatile customer account and API credentials. The license for this SDK does not grant free API access, and API use is governed by your contract and applicable API Terms of Service.

API endpoint

VolatileForecastClient() targets production by default (https://api.volatile.de). You can override base_url when needed:

from volatile_forecast_sdk import VolatileForecastClient, DEFAULT_BASE_URL

client = VolatileForecastClient()  # same as VolatileForecastClient(DEFAULT_BASE_URL)
client = VolatileForecastClient("https://api.staging.example.com")

Existing access token, same default:

client = VolatileForecastClient.from_token("eyJ…")  # optional: base_url="https://…"

from_token() accepts the token first, with base_url as an optional keyword argument.

Discover parameters (PFM datasets)

For products such as Volatile Load PFM-1, Volatile Spot PFM-1, and Volatile IDC DAS-1, the SDK ships a structured catalog that mirrors the server rules (zone, issue time, and dataset-specific optional keys):

from volatile_forecast_sdk import format_parameter_hints, list_catalog_dataset_names

print(list_catalog_dataset_names())
print(format_parameter_hints("Volatile Load PFM-1"))

Use ForecastQueryParams for a fluent builder with validation and tab-friendly enums:

from volatile_forecast_sdk import (
    VolatileForecastClient,
    ForecastQueryParams,
    EuropeanBiddingZone,
    CommonTimeZone,
    ForecastPercentileField,
    TimeAggregation,
)

client = VolatileForecastClient()
client.login("user", "pass")

params = (
    ForecastQueryParams.for_dataset("Volatile Load PFM-1")
    .with_zone(EuropeanBiddingZone.NO_2)
    .with_time_zone(CommonTimeZone.EUROPE_BERLIN)
    .with_forecast_issue_latest()
    .with_fields(ForecastPercentileField.P50, ForecastPercentileField.P90)
    .with_aggregation(TimeAggregation.FIFTEEN_MINUTES)
    .with_forecast_horizon_hours(168)
)

result = client.execute_dataset_query(name="Volatile Load PFM-1", parameters=params)
rows = result.parsed_rows()

IDC DAS example (lean parameter set):

from volatile_forecast_sdk import CommonTimeZone, EuropeanBiddingZone, ForecastQueryParams

params = (
    ForecastQueryParams.for_dataset("Volatile IDC DAS-1")
    .with_zone(EuropeanBiddingZone.DE_LU)
    .with_time_zone(CommonTimeZone.EUROPE_BERLIN)
    .with_forecast_issue_latest()
)
result = client.execute_dataset_query(name="Volatile IDC DAS-1", parameters=params)

European bidding zones are exposed as EuropeanBiddingZone and EUROPEAN_BIDDING_ZONE_CODES (ENTSO-E-style strings). Time zones use CommonTimeZone presets plus validate_time_zone("Europe/Stockholm") against the full IANA set (zoneinfo.available_timezones()).

Refresh token policy

VolatileForecastClient applies a default RefreshTokenPolicy:

  • Proactive: if the access token looks like a JWT, refresh when wall-clock time is within leeway_seconds of exp (requires a refresh token from login).
  • Reactive: on HTTP 401 or GraphQL errors that look like auth failures, refresh once (configurable) and retry.

Disable automation (manual refresh only):

from volatile_forecast_sdk import VolatileForecastClient, RefreshTokenPolicy

client = VolatileForecastClient(refresh_token_policy=RefreshTokenPolicy(enabled=False))

Tune behavior:

RefreshTokenPolicy(
    enabled=True,
    leeway_seconds=300,
    max_reactive_refreshes=2,
    proactive_refresh=True,
    reactive_refresh_on_graphql_auth_error=True,
)

Helpers: client.access_token_expires_at_unix(), decode_jwt_exp_unix(token) (signature not verified—scheduling only).

Optional credentials_provider for fully-unattended processes

Pass a callable that returns (username, password) to let the SDK recover from a hard auth failure (e.g. refresh-token expiry or server-side session revocation) without operator intervention:

import os
from volatile_forecast_sdk import VolatileForecastClient

client = VolatileForecastClient(
    credentials_provider=lambda: (
        os.environ["VOLATILE_USER"],
        os.environ["VOLATILE_PASSWORD"],
    ),
    max_relogins_per_hour=4,   # default — caps password-grant volume
)
client.login(os.environ["VOLATILE_USER"], os.environ["VOLATILE_PASSWORD"])

The provider only fires when reactive-refresh can't recover; a hard cap of max_relogins_per_hour defends the auth endpoint from password-grant storms if the API gets stuck in a misconfigured state.

Observability — client.metrics

Every client exposes a plain-dict metrics attribute customers can sample at any time and feed into Prometheus / structlog / etc:

client.metrics
# {"request_count": 42, "request_errors": 0,
#  "token_refreshes": 1, "rate_limit_retries": 0, "relogins": 0}

The counters are best-effort (no locking). Reset by assigning a fresh dict if you want windowed metrics.

Rate-limit retries (HTTP 429)

The public API enforces per-user, per-operation budgets. When you exceed a budget it replies with HTTP 429 plus a Retry-After header. The SDK detects this automatically, sleeps the indicated number of seconds, and retries — up to max_rate_limit_retries times. Each sleep is capped at rate_limit_retry_cap_seconds so a misbehaving server can't pin your process indefinitely.

client = VolatileForecastClient(
    access_token="...",
    max_rate_limit_retries=3,           # default
    rate_limit_retry_cap_seconds=60.0,  # default
)

If the budget is exhausted, the SDK raises RateLimitError — a subclass of HTTPError — carrying the parsed metadata so you can decide what to do next:

from volatile_forecast_sdk import RateLimitError

try:
    client.execute_dataset_query(name="Volatile IDC DAS-1", parameters=params)
except RateLimitError as e:
    # e.retry_after, e.tier, e.window, e.limit, e.operation
    log.warning("Throttled on %s (%s): wait %ss", e.operation, e.window, e.retry_after)
    raise

Set max_rate_limit_retries=0 to disable the built-in loop entirely (useful if you want to handle 429s with your own scheduler).

Classic dict parameters

You can still pass a plain mapping (no validation beyond the API):

from volatile_forecast_sdk import VolatileForecastClient, normalize_bidding_zone, validate_time_zone

client = VolatileForecastClient()
client.login("user", "pass")

params = {
    "forecast_issue_time": "latest",
    "zone_code": normalize_bidding_zone("NO_2"),
    "time_zone": validate_time_zone("Europe/Berlin"),
}
result = client.execute_dataset_query(name="Volatile Spot PFM-1", parameters=params)

API reference (high level)

Symbol Role
VolatileForecastClient GraphQL HTTP client, token policy, execute_dataset_query
DEFAULT_BASE_URL Production API root (https://api.volatile.de); pass a different base_url to override
ForecastQueryParams Validated fluent builder for query parameters
EuropeanBiddingZone, EUROPEAN_BIDDING_ZONE_CODES, normalize_bidding_zone Curated bidding-zone codes
CommonTimeZone, validate_time_zone, list_common_european_time_zones IANA time zones
ForecastPercentileField, IDCField, TimeAggregation Allowed fields / aggregation values
ForecastStatus Per-slot forecast-quality enum (0..7) returned by IDC datasets — see Forecast quality below
format_parameter_hints, parameter_hints_for_dataset, STANDARD_FORECAST_DATASETS, IDC_DAS_DATASETS, STANDARD_PFM_DATASETS Documentation-oriented catalog
RefreshTokenPolicy, decode_jwt_exp_unix Token lifecycle
DatasetQueryResult.parsed_rows() Normalize row JSON strings vs objects

The SDK mirrors the public GraphQL schema exposed by https://api.volatile.de/graphql/.

Forecast quality (IDC datasets)

IDC DAS datasets attach a per-slot quality label so consumers can filter or weight the forecast. Two columns are returned alongside the quantile / MC fields:

  • forecast_status (int 0..7) — see table below.
  • in_scope (bool) — true for ID1 slots in the 1–6 h horizon (the MC layer's active scope). false for ID3 slots or ID1 slots outside that window; MC columns (signal, expected_edge, p_buy, p_sell, …) will be null by design — not a degradation. The LightGBM quantile columns (id1_p10/50/90, id3_p10/50/90) are populated regardless.
forecast_status Name Meaning
0 OK All inputs fresh; VWAP-S0 (≤6 h) or DA-S0 (>6 h by design)
1 OK_DA_FALLBACK ≤6 h slot, no VWAP, crawler is fresh → liquidity gap, DA-S0 used
2 OK_IDA_FALLBACK Same as 1 but intraday auction is newer than DA → IDA-S0 used
3 WATCH Non-critical input source stale OR borderline VWAP age (15–30 min)
4 VWAP_STALE VWAP older than 30 min — likely crawler lag
5 IMPUTED LightGBM substituted defaults for one or more missing features; data feed is fresh, model output is mildly biased
6 STALE_CRITICAL Data feed itself is stale: EPEX continuous-trades crawler down within MC scope, or day-ahead price missing for the delivery date
7 FAILED LightGBM produced NaN at this slot (model load / feature build failure)

Statuses are monotonically worse — higher is more degraded. Suggested thresholds:

  • LightGBM-only traders: skip slots with forecast_status >= 5.
  • MC-signal traders: skip slots with forecast_status >= 4.
  • Conservative consumers: only trade forecast_status <= 1.

Market reference (VWAP)

IDC DAS datasets also return the live continuous-trades VWAP that the MC signal is anchored to, so you can see the signal and its price basis together:

  • vwap (EUR/MWh) — the most-recent EPEX SIDC continuous-trades VWAP (epex_api_live, quarter-hourly product) for the delivery slot. This is the same S0 anchor the Monte-Carlo signal is built on. null for slots not yet traded (typically the far end of the horizon).
  • vwap_observed_at — the 5-minute execution-time bucket that VWAP was observed in, rendered in the query's time_zone and labelled by the bucket start: vwap_observed_at = 2026-07-15T07:05:00 means the trades executed in the 07:05:0007:10:00 window. Use it to judge the VWAP's age; that age is exactly what forecast_status grades (OKWATCHVWAP_STALE).

Both are opt-in via fields and included when fields is omitted. vwap is the live low-latency tape, not the delayed settled FTP feed.

Classifier signals (added 0.7.0)

Six per-product classifier outputs from the forecasting model, returned per delivery slot alongside the quantiles:

  • id1_spike_prob, id3_spike_prob — probability that the product's ID−DA spread lands in the top decile of historical magnitudes ("spike"). Populated across the full forecast horizon. Useful for widening expected ranges or de-risking around volatile slots.
  • id1_direction_prob, id3_direction_prob — probability that the product's index settles above the live VWAP anchor (the same vwap field above). > 0.5 means the model expects the price to move up from the current market level, < 0.5 down.
  • id1_conviction, id3_conviction|direction_prob − 0.5|, the model's own sizing input: how far from a coin flip the direction call is.

Coverage — null is expected, not a degradation: the direction/conviction models exist per (product, hours-to-delivery) cell — id1: 1–3 h, id3: 3–5 h to delivery. Outside those windows the fields are null because no routing model covers that horizon. spike_prob has no such restriction.

All six are opt-in via fields and included when fields is omitted (IDCField.ID1_SPIKE_PROB, IDCField.ID1_DIRECTION_PROB, IDCField.ID1_CONVICTION, and the ID3_* counterparts).

Difference between status 5 and status 6

These are the two most commonly confused values, and they imply different operational responses:

  • 5 IMPUTED — the model improvised. Input features had one or more NaNs at inference time and the wrapper substituted a default (categorical missing-path, Ridge median, or analytical_cf=0). The data feed is fresh; only some engineered features were unavailable. Forecast is in the right ballpark; reduce position sizing.
  • 6 STALE_CRITICAL — the data feed stopped. The EPEX continuous- trades crawler is stale within the MC horizon, or the day-ahead price is missing entirely. The model may have produced a number, but it's reflecting old or absent market reality. Skip the slot.
from volatile_forecast_sdk import (
    ForecastQueryParams, ForecastStatus, IDCField, VolatileForecastClient,
)

params = (
    ForecastQueryParams.for_dataset("Volatile IDC DAS-1")
    .with_zone("DE_LU")
    .with_forecast_issue_latest()
    .with_fields(
        IDCField.ID1_P50, IDCField.ID3_P50,
        IDCField.SIGNAL, IDCField.EXPECTED_EDGE,
        IDCField.FORECAST_STATUS, IDCField.IN_SCOPE,
    )
)
result = client.execute_dataset_query(
    name="Volatile IDC DAS-1", parameters=params,
)
rows = [
    r for r in result.parsed_rows()
    if r.get("in_scope") and int(r.get("forecast_status", 7)) < ForecastStatus.VWAP_STALE
]

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

volatile_forecast_sdk-0.7.1.tar.gz (39.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

volatile_forecast_sdk-0.7.1-py3-none-any.whl (33.8 kB view details)

Uploaded Python 3

File details

Details for the file volatile_forecast_sdk-0.7.1.tar.gz.

File metadata

  • Download URL: volatile_forecast_sdk-0.7.1.tar.gz
  • Upload date:
  • Size: 39.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for volatile_forecast_sdk-0.7.1.tar.gz
Algorithm Hash digest
SHA256 9e3117bbec40569c792c7f85c433c0464e6b0ac30eb9aba5c2a12e61a413cdc0
MD5 ddb91c9e70649667f5de9bcf7055e2ce
BLAKE2b-256 197e28d04f7bc19950756ded78b27fbe658d783a8eedc6a7d706c2b5cad8eeee

See more details on using hashes here.

File details

Details for the file volatile_forecast_sdk-0.7.1-py3-none-any.whl.

File metadata

File hashes

Hashes for volatile_forecast_sdk-0.7.1-py3-none-any.whl
Algorithm Hash digest
SHA256 41665c128a5646d70b90ac2467f74956004ae10c49abc892808f6ae4663e38dd
MD5 e72ea501b2da6fa1d8cb13a466690919
BLAKE2b-256 5113b301d95e52f9f0fd8a870424b9039d8cef3b8da3a296682f07c93be116ff

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page