Skip to main content

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 a ampion selftest console 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_offset is frozen at 0 for Round 1 — the backend ignores any trader-submitted value and the upload response surfaces bid_offset_override_applied: true so 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:

  1. api_key=... passed to AmpionAPI()
  2. AMPION_API_KEY environment 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 eras
  • ampion.spearman_per_era(preds, labels, eras) — era-aware scoring
  • ampion.xy(df, target) — split a DataFrame into X / y for a given target
  • ampion.feature_cols(df) / ampion.target_cols(df) — column accessors
  • AmpionAPI.get_current_round() / get_submission_slot() / list_rounds() — round + slot discovery
  • AmpionAPI.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 from 2023-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_*_available 0/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_signal and bid_offset were originally named confidence and aggressiveness. 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 near imbalance_forecast, capture more margin per MWh; only fill if the market really comes). Narrow OR uncertain spread → HIGH bid_offset (bid near da_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_signal from an out-of-distribution score, calibrated direction-probability, RL bandit policy, or rank-percentile of expected P&L across the 24 hours.
  • bid_offset from 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 Filled days >= 20 of 28 FILLED days criteria.days_filled (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_filled) 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


Download files

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

Source Distribution

ampion-0.5.4.tar.gz (28.1 kB view details)

Uploaded Source

Built Distribution

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

ampion-0.5.4-py3-none-any.whl (30.6 kB view details)

Uploaded Python 3

File details

Details for the file ampion-0.5.4.tar.gz.

File metadata

  • Download URL: ampion-0.5.4.tar.gz
  • Upload date:
  • Size: 28.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ampion-0.5.4.tar.gz
Algorithm Hash digest
SHA256 48533b26cdbab2b07d001eeda24fdc4ca2d50986b91d7c2a3224304067e83243
MD5 0ff6302d0a6a08a08ea6c7c580f0a748
BLAKE2b-256 3ede6c8ad42c8094d1518c5bf2f5397482d1d8d030351bdea42d356d60ad1dce

See more details on using hashes here.

Provenance

The following attestation bundles were made for ampion-0.5.4.tar.gz:

Publisher: release.yml on RathishanM/Ampion

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ampion-0.5.4-py3-none-any.whl.

File metadata

  • Download URL: ampion-0.5.4-py3-none-any.whl
  • Upload date:
  • Size: 30.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ampion-0.5.4-py3-none-any.whl
Algorithm Hash digest
SHA256 169c9b198c2fe4cb8d63746eba2462fffbed4ed3832bcf28aec713832f1b3b7d
MD5 d63132a2a9131dcb3b889858e09c427e
BLAKE2b-256 00194921bba41e0291955faa4e1c7a31cb4bf125f0c53e1cdfa51f7ea86499d6

See more details on using hashes here.

Provenance

The following attestation bundles were made for ampion-0.5.4-py3-none-any.whl:

Publisher: release.yml on RathishanM/Ampion

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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