Methodology.

Bar

Each market's native clearing interval is the atomic bar: 30 min for JEPX, 5 min for ERCOT / PJM-RT / CAISO / AEMO / NYISO. Rolling windows scale with bar size so the short window is ~1 day and the baseline is ~1 week regardless of market. No up- or down-sampling inside the core path.

Regime FSM

Four states:

Hysteresis: entering a state requires enter_k consecutive bars; exiting requires exit_k. Defaults are 2 in / 4 out. Scarcity exits only when price drops below 0.8× threshold to avoid flapping at the commercial boundary.

z-score

Short-window realized volatility, annualised: σ_short = std(log-returns, window) × √(bars/year). Baseline is the mean and standard deviation of σ_short over a longer window. z = (σ_short − μ_baseline) / σ_baseline. This is the crypto volatility z-score applied unchanged to electricity prices.

Spike probability

For every historical bar, compute the forward maximum price over the next horizon_min minutes. Bucket those maxima by (hour-of-day, is-weekend) in the market's local timezone. Each cell reports the share of bars where the maximum crossed a given threshold. That's the empirical conditional probability:

P(max price over next H min ≥ threshold | hour-of-day, is-weekend)

Zero model fit. No ML. The response carries bucket_size so callers can discount cells with thin sample support. If a market has no scarcity events in the loaded history, the corresponding cell is 0 — which is honest, not a bug. Use the base_rate for context.

Regime forecast

The live forecast endpoint (/v1/power/{m}/{a}/regime-forecast) ships a multinomial logistic regression trained on ~284k labelled bars with a chronological 80/20 split and a 24-bar (12 h) embargo between train tail and test head to suppress autocorrelation leakage. Two variants are trained side-by-side on the identical split and the per-horizon winner serves:

Current test-Brier (lower = better; unconditional baseline ≈ 0.43):

Horizonv1v1.5 (active)Ratio
60 min0.14060.01678.4×
120 min0.13480.03214.2×
240 min0.14460.07541.9×

The t-1 regime one-hot is the dominant feature — persistence does most of the work over short horizons. Weather adds incremental signal, particularly at 240 min where the persistence term has decayed further. We publish both variants' scores so the weight of persistence vs weather is visible; future ablations will separate them further.

Current weather is fetched from Open-Meteo at process boot and cached in memory; refreshes happen at the next deploy. A periodic in-process refresh is a planned follow-up. Model weights are exported as JSON and evaluated with numpy at serve time — no sklearn, lightgbm, or PyTorch in the serving container.

Errors

Every non-success response ships as application/problem+json (RFC 7807) with {type, title, status, detail, instance}. The type URI anchors into this section so callers can follow a failing response back to its definition.

400 Bad Request

The request was structurally valid but semantically rejected — e.g. target_regime=normal on spatial-influence (normal is uninformative). Fix: re-read the endpoint's accepted values in /v1/reference.

404 Not Found

Either the path is not routed, the market is not catalogued (GET /v1/markets for the full list), or the area is not tracked in the requested market (GET /v1/markets/{market}/areas). Admin endpoints also return 404 when the bearer token is missing or wrong — the 404 covers "you can't tell if this exists" by design.

422 Unprocessable Entity

Query or path parameter failed validation (type, range). limit must be a positive integer within the endpoint's cap; horizon_min must be one of 60 / 120 / 240; max_lag_bars must be 1..48. The response body carries an errors array with the field-level Pydantic errors so SDKs can surface them without re-parsing the detail string.

405 Method Not Allowed

The endpoint exists but your HTTP method isn't accepted. All public endpoints are GET-only; writes are not offered today. If you need a write surface that would be appropriate for a computational tool, it will ship as a new endpoint with an explicit doc entry, not by accepting POST on existing reads.

429 Too Many Requests

Rate limit hit (free tier: 60 req/min/IP). Response carries Retry-After (seconds until a token is back), X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (UNIX timestamp) headers. SDK clients use Retry-After for backoff; the raw JSON body is identical in structure to other problem+json responses. Paid tiers will widen the ceiling, not the shape.

500 Internal Server Error

Unexpected failure inside Amanoki. These are captured to Sentry (when the DSN is configured) and retried-safe on your side — our endpoints are idempotent reads. If the error persists over a span, see /status for the public health snapshot.

503 Service Unavailable

Dependency is degraded but not crashed — typically the weather cache failed to refresh and we fall back to a stale snapshot or to the v0 baseline. /v1/health/deep breaks down the affected dependencies. Retry with a modest backoff.

Not advice

Amanoki is a computational tool. We compute regime state, empirical spike probabilities, and calibration-scored forecast distributions over those regimes. We do not give trading, procurement, hedging, or investment advice in any jurisdiction. The methodology above is published so callers can independently verify the outputs; the responsibility for any decision made with those outputs stays with the caller.

Why regime and not point forecast

Procurement and trading desks don't need another price point estimate. They need to know whether the distribution around that estimate just shifted. A scarcity-flagged bar on a cold winter morning moves monthly procurement cost more than ten forecast points do. The API stabilises that signal. The regime forecast extends this to a probability distribution over states at t + horizon; calibration (Brier) is the target metric, not direction accuracy.

Limitations