Python client for the Ampion Trading Platform — Numerai-style tournament for European day-ahead power prices.
Project description
ampion-api
Python client for the Ampion Trading Platform — Numerai-style tournament for European day-ahead power prices.
Submit ML-generated forecasts for German day-ahead + balance settlement prices. Models that pass the 28-day evaluation (5 performance criteria, flat EUR drawdown caps) are funded on day 29 with a 70/30 profit split.
SDK v0.5.3. Pip-installable from PyPI. Adds walk-forward CV (
walk_forward), era-aware scoring (spearman_per_era), tournament round + submission-slot discovery (get_current_round,get_submission_slot,list_rounds), per-round qualification status (get_round_status), and aampion selftestconsole script for one-line install verification. The v0.4.1 4-scalar submission contract (delivery_ts,da_forecast,imbalance_forecast,size_signal,bid_offset) is unchanged.
Round 1 specifics (launches 2026-07-01).
bid_offsetis frozen at 0 for Round 1 — the backend ignores any trader-submitted value and the upload response surfacesbid_offset_override_applied: trueso callers can confirm the freeze. See Round 1 rulebook below for the six passing criteria.
Quick Start
pip install ampion>=0.5.3
export AMPION_API_KEY=ampn_... # from app.ampiontrading.com/settings
ampion selftest # verify install + backend reachability
If pre-PyPI install needed: pip install git+https://github.com/RathishanM/Ampion.git#subdirectory=ampion-api.
import ampion
napi = ampion.AmpionAPI()
model = napi.create_model("my-gbm")
train = napi.download_dataset("v1/train.parquet")
# ... fit model on train, produce a v0.4 4-scalar predictions DataFrame ...
napi.upload_predictions(preds, model_id=model["id"])
# Check your Round 1 progress at any time:
status = napi.get_round_status() # current round
print(status["round_status"], status["profit_pct"], status["criteria"])
Notebooks
The fastest way to learn the platform — each notebook runs top to bottom against live data.
| Notebook | What you'll build |
|---|---|
01_hello_ampion.ipynb |
Auth, download data, submit a baseline prediction |
02_wind_signal.ipynb |
Multi-signal wind/solar/load strategy with seasonal calibration |
03_ml_model.ipynb |
Gradient-boosted model with time-series CV and confidence gating |
04_ensemble.ipynb |
Ensemble three models, show the composite beats any single one |
pip install "ampion-api[notebooks]"
jupyter notebook notebooks/
Full Example
import pandas as pd
import ampion
napi = ampion.AmpionAPI() # AMPION_API_KEY env var, or pass api_key=...
# 1. Register a model (once)
napi.create_model("my-gbm", description="GBM on wind + solar signals")
# 2. Download 2+ years of training data
train = napi.download_dataset("v1/train.parquet")
# 3. Train your model (use any framework)
# ... your model code here ...
# 4. Download live data for the current week
live = napi.download_dataset("v1/live.parquet")
# 5. Generate predictions (v0.4 4-scalar canonical shape)
predictions = pd.DataFrame({
"delivery_ts": live["delivery_ts"],
"da_forecast": [82.4, 75.1, 0.0, ...], # EUR/MWh
"imbalance_forecast": [88.1, 79.3, 0.0, ...], # EUR/MWh
"size_signal": [0.72, 0.68, 0.0, ...], # [0, 1] -- 0 = forecast-only, no bid
"bid_offset": [0.30, 0.65, 0.0, ...], # [0, 1] -- 0=safer fill at imbalance side, 1=aggressive at DA side
})
# 6. Submit
napi.upload_predictions(predictions, model_id="my-gbm")
# 7. Check the leaderboard
print(napi.get_leaderboard())
Authentication
The client picks up your API key in this order:
api_key=...passed toAmpionAPI()AMPION_API_KEYenvironment variable
napi = ampion.AmpionAPI() # env var
napi = ampion.AmpionAPI(api_key="vlt_...") # explicit
napi = ampion.AmpionAPI() # no key -> only public endpoints work
print(napi.get_leaderboard(10)) # leaderboard is public, no key needed
Create or revoke keys at app.ampiontrading.com/settings.
Helpers
ampion.walk_forward(df, ...)— walk-forward CV iterator over erasampion.spearman_per_era(preds, labels, eras)— era-aware scoringampion.xy(df, target)— split a DataFrame into X / y for a given targetampion.feature_cols(df)/ampion.target_cols(df)— column accessorsAmpionAPI.get_current_round()/get_submission_slot()/list_rounds()— round + slot discoveryAmpionAPI.diagnostics()/download_meta_model()/get_model_mmc()— post-Round-1 diagnostics (HTTP 202 pre-launch)
See strategy-template/tournament_bot.py for a working end-to-end daily bot using these helpers.
Datasets
| Dataset | Period | Rows | Targets included |
|---|---|---|---|
v1/train.parquet |
2021-01-01 to (today - 2 - 91 days) | growing, ~46k by mid-2026 | Yes |
v1/validation.parquet |
rolling last 91 days, ending (today - 2) | ~2.2k | Yes |
v1/live.parquet |
next delivery day | 24 | No (target NaN by invariant) |
The validation slice rolls forward daily and is exactly 13 weeks long, matching
cv_protocol.val_size_hours (2184h). Your local Spearman / Sharpe on the
validation slice should track what we score you on at submission.
Data Schema (v0.4.1, 132 cols)
The full canonical contract lives at backend/market_data/tournament/schema.yaml. Summary:
| Group | Cols | Examples |
|---|---|---|
| Meta | 4 | era, delivery_ts, delivery_date, delivery_hour |
| Targets | 2 | target_da_price_eur_mwh, target_imbalance_actual_eur_mwh |
| ENTSO-E forecasts (lag1) | 4 | feature_fcst_load_lag1_mw, feature_fcst_wind_onshore_lag1_mw, feature_fcst_wind_offshore_lag1_mw, feature_fcst_solar_lag1_mw |
| ENTSO-E realized (lag2/lag7) | 8 | feature_actual_load_lag2_mw ... feature_actual_solar_lag7_mw |
| Neighbor DA (6 zones × 2 lags) | 12 | feature_neighbor_<fr/nl/be/dk1/at/ch>_da_lag1, _lag7 |
| Weather forecast (5 cities × 2 vars + 3 wind + 2 solar clusters) | 15 | feature_weather_temp_berlin_k, feature_weather_wind_100m_northsea_ms, feature_weather_ghi_bavaria_wm2 |
| Weather realized lag1 (ERA5 mirror) | 15 | same names + _lag1 suffix |
| Fuels | 3 | feature_fuel_ttf_eur_mwh, feature_fuel_eua_eur_t, feature_fuel_eurusd |
| Calendar | 10 | feature_cal_hour, feature_cal_dow, feature_cal_is_holiday_de, feature_cal_dst_flag, ... |
| Gas storage | 1 | feature_regime_gas_storage_pct |
| v0.4 add-ons (49) | ||
| Multimodel forecasts (4 models × 5 clusters) | 20 | feature_wind_<cluster>_<ecmwf/gfs/arpege/cma>_ms, feature_ghi_*_wm2 |
| DE DA price lags | 3 | feature_da_price_de_lag1/2/7_eur_mwh |
| reBAP lag1 | 1 | feature_rebap_lag1_eur_mwh (populated_from 2023-01-02) |
| Conventional generation (6 fuels × 2 lags) | 12 | feature_actual_lignite_lag2_mw, feature_actual_nuclear_lag7_mw, ... |
| FR nuclear lags | 2 | feature_actual_nuclear_fr_lag1/lag7_mw |
| Forecast lag2 | 4 | feature_fcst_<load/wind_*/solar>_lag2_mw |
| Astronomical | 2 | feature_astro_solar_elevation_deg, feature_astro_day_of_year |
| Neighbor holidays | 3 | feature_cal_is_holiday_<fr/nl/ch> |
| Coverage indicator flags | 2 | feature_open_meteo_available, feature_rebap_available |
Coverage caveats (canonical-facts.yaml > tournament_pipeline.v0_4_planned_features):
target_imbalance_actual_eur_mwh+ reBAP cols populated from2023-01-01(post-harmonization regime, ENTSO-E A85 publishes cleanly)- Multimodel cols populated per-model: GFS from 2021-03-23, ARPEGE Eu from 2022-11-13, CMA from 2023-12-31, ECMWF IFS from 2024-02-03
- DE nuclear lag2/lag7 populated 2021-01-01 to 2023-04-15 (DE nuclear exit), NaN after
- Tree models (XGBoost / LightGBM / CatBoost) handle NaN natively. The two
feature_*_available0/1 flags are explicit regime indicators (ml-expert leakage mitigation for tree default-direction learning).
Prediction Format (v0.4 -- 4-scalar canonical)
Your predictions DataFrame needs five columns:
| Column | Type | Values |
|---|---|---|
delivery_ts |
tz-aware Timestamp | Match delivery_ts from live.parquet (one row per delivery hour, 23-25 per day depending on DST) |
da_forecast |
float | Predicted DA clearing price, EUR/MWh |
imbalance_forecast |
float | Predicted balance/imbalance settlement price, EUR/MWh |
size_signal |
float | [0, 1]. Drives size (0.1-1.0 MWh) + bid-or-skip. size_signal = 0 = forecast-only, no bid placed this hour. |
bid_offset |
float | [0, 1]. Placement of bid inside the [imbalance_forecast, da_forecast] interval. 0 bids at imbalance_forecast (safer fill), 1 bids at da_forecast (aggressive entry). Ignored when size_signal = 0. |
Derived server-side (not submitted): bid_price = imbalance_forecast - bid_offset * (imbalance_forecast - da_forecast), size_mwh = clamp(size_signal, 0.1, 1.0), direction = long if imbalance_forecast > da_forecast else short.
Forecasts are scored (MAE + per-era Spearman + orthogonal alpha) on every submitted hour -- including size_signal = 0 forecast-only hours. P&L is computed from the position taken (direction × (imbalance_actual - da_actual) × size_mwh).
The legacy v0.2 shape (timestamp, direction, volume, confidence) is rejected by upload_predictions() with an explicit migration error pointing here.
Naming note (2026-04-25).
size_signalandbid_offsetwere originally namedconfidenceandaggressiveness. Renamed because the old names biased traders toward one specific derivation (Kelly fraction, fill-rate-vs-margin framing). The new names describe the mechanical role only -- what the field drives in the formula. Derivation is up to the trader.
How traders typically derive size_signal and bid_offset
Both fields are mechanical scalars in [0, 1], not constraints on
derivation. Ampion ships data, not analysis; the four scalars your model
produces can come from any source: predictive uncertainty, RL policy output,
regime classifier, calibrated probabilities, hand-tuned heuristics.
The recommended (default) framing for getting started:
size_signal= Kelly-style position-size signal. Proportional to your model's estimated edge(|imbalance_forecast - da_forecast|)divided by your forecast uncertainty (model quantile spread, ensemble disagreement across NWP models, or regime-rarity / out-of-distribution score). High when the spread is large AND the model is confident AND the regime is well-represented in training; low otherwise.bid_offset= inverse of spread-magnitude confidence. Wide + confident spread → LOW bid_offset (bid nearimbalance_forecast, capture more margin per MWh; only fill if the market really comes). Narrow OR uncertain spread → HIGH bid_offset (bid nearda_forecast, accept smaller margin, ensure fill).
Worked examples for two canonical hours:
| Delivery hour | DA forecast | Balance forecast | Spread | NWP agreement | Recommended size_signal |
Recommended bid_offset |
Why |
|---|---|---|---|---|---|---|---|
| Sunday h13 (sunny) | 5 EUR/MWh | -10 EUR/MWh | -15 (short) | High (solar consensus) | 0.85 | 0.4 | Already short at -4 EUR/MWh after the give-up; well-understood regime |
| Monday h19 (after low-wind Sunday) | 180 EUR/MWh | 220 EUR/MWh | +40 (long) | Low (ICON-D2 disagrees on residual wind) | 0.5 | 0.7 | Want fill confirmation; balance tail risk real but you are buying the spread, not the absolute level |
Alternative interpretations a trader's model might use instead:
size_signalfrom an out-of-distribution score, calibrated direction-probability, RL bandit policy, or rank-percentile of expected P&L across the 24 hours.bid_offsetfrom quantile-interval position, regime-conditional fixed scalars, or a function of recent fill rate.
All produce a number in [0, 1]. The server formula does not care where the
number comes from. See docs/canonical-facts.yaml > submission for the full
list of alternative interpretations the canonical contract recognizes.
Evaluation Rules
- 5 pass criteria over the 28-day window: Sharpe > 1.0, cumulative P&L > 0, orthogonal alpha > 0, ≥ 20 of 28 FILLED days (days with at least one filled hour), forecast-accuracy criterion as published on the platform.
- Drawdown caps during evaluation (flat EUR, not percentages): €500 daily + €1,000 total. Breach of either ends the evaluation immediately.
- Submission window: one payload per day before T-1 11:55 CET for delivery on day T. The DA auction is sealed-bid.
- Retake: 24-hour cooldown between evaluation attempts. Each retake is a fresh €199.
- Full terms:
website/trader-agreement.html.
Round 1 specifics (2026-07-01 → 2026-07-29)
Round 1 is the first paid evaluation of the Ampion tournament. The
rules below are specific to this round and live alongside the
general-evaluation framing above. The canonical source is
docs/round-1-rulebook.md; this section
mirrors the SDK-facing parts.
bid_offset is frozen at 0
# This still works — bid_offset stays in the v0.4 contract for SDK
# back-compat, but during Round 1 the backend overrides the value.
preds["bid_offset"] = 0.5
napi.upload_predictions(preds, model_id=model["id"])
# Response:
# {"status": "accepted", "round_number": 1,
# "bid_offset_override_applied": true, # <- Round 1 freeze applied
# ...}
The trader-submitted bid_offset is ignored; every bid is placed at
imbalance_forecast (the max-margin / lowest-fill end of the forecast
interval). Returns the v0.5.3 upload response field
bid_offset_override_applied: true. From Round 2 onward,
bid_offset is honoured again — start treating it as a working
parameter when you see round_number == 2 in the upload response.
Six passing criteria (replacing the 5-criteria sandbox model)
| # | Criterion | Threshold | SDK display via get_round_status() |
|---|---|---|---|
| 1 | Profit | profit_pct >= 5 |
criteria.profit_target |
| 2 | Max daily loss | |daily P&L %| <= 5 |
criteria.max_daily_loss |
| 3 | Max drawdown | peak-to-trough <= 10 % |
criteria.max_drawdown |
| 4 | Days submitted | >= 20 of 28 FILLED days |
criteria.days_submitted (null until settle) |
| 5 | CORR | Spearman > 0.05 (≥ 50 FILLED hours) |
criteria.corr |
| 6 | Direction accuracy | >= 52 % of FILLED hours |
criteria.direction_accuracy |
Plus the 30 % single-day concentration forfeit: the portion of any
single day's P&L that exceeds 30 % of total round P&L is forfeit when
computing criterion 1. The forfeit only kicks in at evaluation time;
your real-time profit_pct mid-round is the raw value.
Checking your progress
status = napi.get_round_status() # current round, default
# Or explicitly:
status = napi.get_round_status(round_number=1)
print(status["round_status"]) # 'in_progress' | 'qualified' | 'failed' | 'not_started'
print(status["profit_pct"], status["max_drawdown_pct"])
for name, crit in status["criteria"].items():
print(name, crit["value"], "->", crit["threshold"], crit["passing"])
While the round is in progress, end-of-round-only criteria
(days_submitted) return passing: null with a "Round in progress"
note. Final pass/fail is determined when the round settles
(round_close + 24 hours).
API Reference
napi = AmpionAPI(api_key=None, base_url=None) # env-var auth by default
# Datasets
napi.download_dataset(version, dest_path, save_file)
napi.dataset_info() # public manifest: products, splits, schema, deadlines
napi.data_mode() # 'live' | 'placeholder' | 'unknown' (last download)
napi.health_data() # public pipeline health -- last live, last train, ticks pulse
# Predictions
napi.upload_predictions(predictions_df, model_id)
napi.list_submissions(model_id=None, limit=50)
napi.submission_status(submission_id)
# Models
napi.create_model(name, description)
napi.list_models()
napi.get_model_performance(model_id)
napi.get_model_scores(model_id, limit=90)
# Leaderboard + rounds
napi.get_leaderboard(top_n=50, window_days=28) # public, no key needed
napi.get_round_info() # current round_date + 11:55 CET deadline
Data mode
Every download_dataset() response carries an X-Ampion-Data-Mode header
(live or placeholder). The SDK stamps this on the client object so trader
code can gate production submissions on real data:
df = napi.download_dataset("v1/train.parquet")
assert napi.data_mode() == "live", "Refusing to submit on placeholder data"
History offset for full-history downloads
The training parquet starts at 2021-01-01 (fixed) and ends at
(today − 2 − 91 days). The 91 days at the end is the rolling validation
slice. The upstream ingestion pulls 14 days before the first delivery
row to populate the _lag7 feature columns (7-day lag + 1-day forecast
lag + 6-day buffer). The offset is upstream of the trader; the file you
download already starts at 2021-01-01 with all lag columns populated. See
backend/market_data/tournament/orchestrator.py:52 and
backend/market_data/tournament/schema.yaml > training_window.
Convenience helpers:
import pandas as pd
from ampion import get_live_data, get_training_data
live = get_live_data(api_key)
train = get_training_data(api_key)
pred = pd.DataFrame({
"delivery_ts": live["delivery_ts"], # UTC ISO, matches live.parquet
"da_forecast": da_forecast_eur, # EUR/MWh predicted DA clear
"imbalance_forecast": imb_forecast_eur, # EUR/MWh predicted settlement
"size_signal": size_signal, # [0, 1]; 0 = forecast-only
"bid_offset": bid_offset, # [0, 1]; server derives bid_price
})
Installation
# Core (download data, submit predictions)
pip install git+https://github.com/RathishanM/Ampion.git#subdirectory=ampion-api
# With ML dependencies (numpy, scikit-learn)
pip install "git+https://github.com/RathishanM/Ampion.git#subdirectory=ampion-api[ml]"
# With Jupyter + ML for running the notebook ladder
pip install "git+https://github.com/RathishanM/Ampion.git#subdirectory=ampion-api[notebooks]"
PyPI release is pending. Once published, you'll be able to pip install ampion-api directly.
Requires Python 3.9+.
Links
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ampion-0.5.3.tar.gz.
File metadata
- Download URL: ampion-0.5.3.tar.gz
- Upload date:
- Size: 27.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6093d633b1bb5ae6c83b0a982c2886eb80034d4f62d7dce02df629f382fd796f
|
|
| MD5 |
b7546f4e5d91070f6f53517f17615e58
|
|
| BLAKE2b-256 |
1445f10eb161c7ddad388d8ae96fcea856965af9a63c87ff367f745ad3b9a3f3
|
Provenance
The following attestation bundles were made for ampion-0.5.3.tar.gz:
Publisher:
release.yml on RathishanM/Ampion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ampion-0.5.3.tar.gz -
Subject digest:
6093d633b1bb5ae6c83b0a982c2886eb80034d4f62d7dce02df629f382fd796f - Sigstore transparency entry: 1566456848
- Sigstore integration time:
-
Permalink:
RathishanM/Ampion@3b4751e61b00f9f76fc9c000ec8cb6a808041198 -
Branch / Tag:
refs/tags/sdk-v0.5.3 - Owner: https://github.com/RathishanM
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3b4751e61b00f9f76fc9c000ec8cb6a808041198 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ampion-0.5.3-py3-none-any.whl.
File metadata
- Download URL: ampion-0.5.3-py3-none-any.whl
- Upload date:
- Size: 30.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8cbd7ccccda883de8add5919a033fa6b40f494cb9b7921438b7f56fbdc9e619
|
|
| MD5 |
9a361a00f364ae77c7c73c572f1a78e1
|
|
| BLAKE2b-256 |
208e923ee158b5701827094f0ac7f60585185a9c5b81e5a2948801683c391b60
|
Provenance
The following attestation bundles were made for ampion-0.5.3-py3-none-any.whl:
Publisher:
release.yml on RathishanM/Ampion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ampion-0.5.3-py3-none-any.whl -
Subject digest:
b8cbd7ccccda883de8add5919a033fa6b40f494cb9b7921438b7f56fbdc9e619 - Sigstore transparency entry: 1566456883
- Sigstore integration time:
-
Permalink:
RathishanM/Ampion@3b4751e61b00f9f76fc9c000ec8cb6a808041198 -
Branch / Tag:
refs/tags/sdk-v0.5.3 - Owner: https://github.com/RathishanM
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3b4751e61b00f9f76fc9c000ec8cb6a808041198 -
Trigger Event:
push
-
Statement type: