Implements the package from PLAN.md: config/data loading, the LP-first MILP-fallback rolling-horizon optimiser, battery degradation tracking, independent schedule validation, metrics, plots and the CLI, plus the matching test suite. Adds a Gitea Actions workflow (lint + tests) that posts a pass/fail notification to ntfy on every run, and a .gitignore for build/cache artefacts that had been tracked by mistake. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
"""Market definitions.
|
||||
|
||||
Both markets are expressed on the common half-hourly grid. A market whose
|
||||
trading block is longer than a half-hour (Market 2, hourly) is described by
|
||||
``block_half_hours``; the optimiser creates one decision variable per *block*
|
||||
and references it from every half-hour inside that block, so the commitment
|
||||
rule cannot be violated by construction.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
|
||||
MARKET_1 = "market_1"
|
||||
MARKET_2 = "market_2"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Market:
|
||||
"""A market the battery can trade in.
|
||||
|
||||
Attributes:
|
||||
name: Identifier used in output column names.
|
||||
block_half_hours: Number of half-hours a single commitment must span.
|
||||
prices: One price per half-hour, broadcast from the block price.
|
||||
"""
|
||||
|
||||
name: str
|
||||
block_half_hours: int
|
||||
prices: np.ndarray
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.block_half_hours < 1:
|
||||
raise ValueError("block_half_hours must be >= 1")
|
||||
if self.prices.ndim != 1:
|
||||
raise ValueError("prices must be one-dimensional")
|
||||
|
||||
def block_of(self, half_hour: int) -> int:
|
||||
"""Index of the trading block containing ``half_hour``."""
|
||||
return half_hour // self.block_half_hours
|
||||
|
||||
def slice(self, start: int, stop: int) -> "Market":
|
||||
"""A view of this market over half-hours ``[start, stop)``.
|
||||
|
||||
``start`` must fall on a block boundary, otherwise the window would cut
|
||||
a trading block in half and the commitment rule would be meaningless.
|
||||
"""
|
||||
if start % self.block_half_hours:
|
||||
raise ValueError(
|
||||
f"{self.name}: window start {start} is not aligned to a "
|
||||
f"{self.block_half_hours}-half-hour block boundary"
|
||||
)
|
||||
return Market(self.name, self.block_half_hours, self.prices[start:stop])
|
||||
Reference in New Issue
Block a user