Metadata-Version: 2.4
Name: ampion
Version: 0.5.2
Summary: Python client for the Ampion Trading Platform — Numerai-style tournament for European day-ahead power prices.
Project-URL: Homepage, https://ampiontrading.com
Project-URL: Repository, https://github.com/RathishanM/Ampion
Project-URL: Documentation, https://github.com/RathishanM/Ampion/tree/main/ampion-api
Project-URL: Issues, https://github.com/RathishanM/Ampion/issues
Author-email: Ampion Trading <dev@ampiontrading.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: EPEX,algorithmic,day-ahead,energy,power,tournament,trading
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Requires-Dist: pandas<2.3,>=2.1
Requires-Dist: pyarrow>=14
Requires-Dist: requests<3,>=2.28
Requires-Dist: scipy>=1.11
Provides-Extra: ml
Requires-Dist: numpy>=1.23; extra == 'ml'
Requires-Dist: scikit-learn>=1.1; extra == 'ml'
Provides-Extra: notebooks
Requires-Dist: jupyter>=1.0; extra == 'notebooks'
Requires-Dist: matplotlib>=3.6; extra == 'notebooks'
Requires-Dist: numpy>=1.23; extra == 'notebooks'
Requires-Dist: scikit-learn>=1.1; extra == 'notebooks'
Description-Content-Type: text/markdown

# 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.0.** 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`), 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.

---

## Quick Start

```bash
pip install ampion>=0.5.0
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`.

```python
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"])
```

---

## 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`](notebooks/01_hello_ampion.ipynb) | Auth, download data, submit a baseline prediction |
| [`02_wind_signal.ipynb`](notebooks/02_wind_signal.ipynb) | Multi-signal wind/solar/load strategy with seasonal calibration |
| [`03_ml_model.ipynb`](notebooks/03_ml_model.ipynb) | Gradient-boosted model with time-series CV and confidence gating |
| [`04_ensemble.ipynb`](notebooks/04_ensemble.ipynb) | Ensemble three models, show the composite beats any single one |

```bash
pip install "ampion-api[notebooks]"
jupyter notebook notebooks/
```

---

## Full Example

```python
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

```python
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](https://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`](../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`](../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`.

---

## API Reference

```python
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:

```python
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:

```python
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

```bash
# 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

- [Ampion Platform](https://app.ampiontrading.com)
- [Leaderboard](https://app.ampiontrading.com/leaderboard)
- [GitHub repo](https://github.com/RathishanM/Ampion/tree/main/ampion-api)
- [Report an issue](https://github.com/RathishanM/Ampion/issues)

---

## License

MIT
