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:
low— realized-vol z-score below the low-enter threshold forenter_kconsecutive bars.normal— neither low nor high; the common case.high— realized-vol z-score above the high-enter threshold forenter_kconsecutive bars.scarcity— absolute price ≥ the market's scarcity threshold (¥80/kWh for JEPX; per-market thresholds in the catalog). Takes priority over the z-score ladder.
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:
- v1 (current-bar): area one-hot + UTC hour one-hot + is-weekend + current z-vol + five Open-Meteo weather variables (temperature, wind, shortwave radiation, cloud cover, humidity).
- v1.5 (lag-augmented): v1 features + z-vol
lags at
t-1 / -2 / -6 / -24bars + regime one-hot att-1.
Current test-Brier (lower = better; unconditional baseline ≈ 0.43):
| Horizon | v1 | v1.5 (active) | Ratio |
|---|---|---|---|
| 60 min | 0.1406 | 0.0167 | 8.4× |
| 120 min | 0.1348 | 0.0321 | 4.2× |
| 240 min | 0.1446 | 0.0754 | 1.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
- Spike probability has a single conditioning dimension (hour × weekend). Weather / z-vol quartile conditioning on the spike table is deferred.
- Regime forecast v1.5 is linear in features. Reservoir / physics-informed predictors are under evaluation but have not beaten v1.5 on held-out Brier at this scale.
- No cross-market propagation yet — each (market, area) is independent.
- ERCOT / PJM / CAISO / AEMO / NYISO are catalogued only. Adapters land when their feeds are registered.
- Empirical frequencies; they do not account for regime changes in the underlying market structure (rule changes, new capacity). We retrain weekly to pick up drift within a 1-2 week lag.