Energy Optimization

Iberian BESS Policy Challenge

A deterministic BESS dispatch policy is challenged against frozen Iberian day-ahead scenarios, commercial baselines, and a perfect-foresight upper bound with explicit risk and constraint readouts.

Problem

This result evaluates a single 1 MW / 4 MWh battery trading frozen OMIE day-ahead price scenarios. The policy decides hourly charge and discharge while respecting power, state-of-charge, efficiency, and terminal SOC constraints.

Evaluation Contract

The public benchmark is the Iberian BESS Policy Challenge v0.1. It compares the accepted policy against commercial baselines and a perfect-foresight LP upper bound. The oracle is not a sales baseline; it is the remaining headroom under the same simplified physical model.

Result

The seed quantile baseline scored 72.544043. The accepted policy portfolio scored 48.086813, a reduction of 24.45723 score units (33.71363%).

The accepted policy produced 20.197299 EUR/day average uplift against the quantile comparison baseline, with 0.0 constraint breaches and a downside rate of 0.0.

What Changed

The accepted policy is a small deterministic portfolio. It evaluates several valid dispatch heuristics on the same daily price horizon and selects the highest-margin valid plan. This is intentionally simple: the pre-sell point is not to replace a client optimizer, but to show that an offline policy challenger can find auditable improvement candidates under a fixed contract.

Tail Behavior

The comparison bundle keeps every scenario visible, including stress-tail days and weaker cases. The public claim uses aggregate uplift together with p5/p95 uplift, regret, cycle-adjusted margin, and constraint health.

Limitations

This is an offline day-ahead benchmark over frozen scenarios. It is not production trading, not a live bidding system, not an official market benchmark, and does not model intraday, reserves, imbalance settlement, taxes, grid constraints, or portfolio effects.

Reproducibility

The bundle includes the accepted candidate, evaluation contract, comparison rows, dispatch trace, replay data, forecast-error smoke output, metrics, score trace, and provenance. Public-claim guardrails validate that required artifacts exist, constraints are clean, limitations are stated, and replay evidence is present.

Oracle capture ratio 87.470%

Share of oracle value captured under the frozen dispatch scenarios.

Objective reduction 33.714%
Mean regret €27.57
Acceptance objective 48.087
Seed, checked-in, and accepted policy scores under the v0.1 contract. Lower is better.
Accepted policy readout across uplift, regret, cycle-adjusted margin, downside, and constraint breaches.
Scenario-level dispatch trace: price, charge/discharge action, and state of charge.

Evidence

Acceptance score and observable behavior.

The score is a weighted euro-like benchmark objective over regret, baseline shortfall, degradation proxy, downside rate, and constraint penalties. Public sales claims should use it with the comparison and dispatch artifacts.

Oracle capture ratio 87.470%
Mean regret €27.57
Score reduction 24.457
Objective reduction 33.714%

Dispatch readout

Oracle capture ratio 87.470%
Mean regret €27.57
Dispatch trace Open JSON artifact

Accepted implementation

Replayable candidate code.

"""Accepted storage_arbitrage_es policy used for the public pre-sell proof."""

from __future__ import annotations

from domains.storage_arbitrage_es.evaluator import (
    build_conservative_cycle_baseline,
    build_quantile_baseline,
    build_spread_tb4_baseline,
    simulate_dispatch,
)
from domains.storage_arbitrage_es.program import DispatchContext, DispatchPlan
from domains.storage_arbitrage_es.program import dispatch_policy as checked_in_policy


def dispatch_policy(ctx: DispatchContext) -> DispatchPlan:
    """Choose the highest-margin valid plan from a small deterministic policy portfolio."""
    candidates: list[tuple[float, str, DispatchPlan]] = []
    for label, builder in (
        ("quantile", build_quantile_baseline),
        ("spread_tb4", build_spread_tb4_baseline),
        ("conservative", build_conservative_cycle_baseline),
    ):
        plan = builder(ctx.prices_eur_per_mwh, ctx.spec)
        simulation = simulate_dispatch(ctx.prices_eur_per_mwh, plan, ctx.spec)
        if simulation.valid:
            candidates.append((float(simulation.profit_eur), label, plan))

    checked_plan = checked_in_policy(ctx)
    checked_simulation = simulate_dispatch(ctx.prices_eur_per_mwh, checked_plan, ctx.spec)
    if checked_simulation.valid:
        candidates.append((float(checked_simulation.profit_eur), "checked_in", checked_plan))

    if not candidates:
        return build_quantile_baseline(ctx.prices_eur_per_mwh, ctx.spec)

    candidates.sort(key=lambda item: (item[0], item[1]), reverse=True)
    return candidates[0][2]