Download code/hour23.py from Elpriser/gb-price-estimate: direct link, hf CLI and curl.
- Browser
- Download file 2.7 kB
-
https://huggingface.co/Elpriser/gb-price-estimate/resolve/main/code/hour23.py
- Command line
-
hf download hf://Elpriser/gb-price-estimate/code/hour23.py
-
curl -L -o hour23.py https://huggingface.co/Elpriser/gb-price-estimate/resolve/main/code/hour23.py
2.7 kB
| """The missing last hour of Portugal's and Ireland's newest day. | |
| Both zones run one hour behind CET, so the day-ahead auction (CET day) leaves the last local hour (23:00-24:00 = 00:00-01:00 CET of the next market day) of the | |
| newest settled day without a price until the next auction is published (~11:45-12:45 UTC). The estimate of that ONE hour uses what is known when it is missing: | |
| the same day's hours 0..22 and every earlier day. | |
| persist the price of hour 22 of the same day the naive method | |
| med28 hour 22 + the median of (hour 23 - hour 22) over the last 28 complete days "the last settled hour and the typical same-hour step" | |
| Which of the two a zone publishes is decided by backtest_hour23.py (hour23_gate.json): the one with the lower error out of sample, and only if it is not worse than | |
| the best naive method (persistence of hour 22, yesterday's hour 23, last week's hour 23) on the whole backtest AND on the hold-out. The band is the | |
| rolling residual band of the EU model (calibrate.py: 1.2 x the 10th / 90th percentile of the method's own error over the last 180 days). | |
| Pure functions on a day x hour matrix (rows = days in order, 24 columns, NaN where there is no price). | |
| """ | |
| import numpy as np | |
| METHODS = ("persist", "med28") | |
| STEP_DAYS = 28 | |
| BAND_DAYS = 180 | |
| BAND_SCALE = 1.2 | |
| MIN_STEPS = 10 | |
| def step_median(P, i, n=STEP_DAYS): | |
| """Median of (hour 23 - hour 22) over the n complete days before day index i, or NaN with fewer than MIN_STEPS.""" | |
| s = (P[max(0, i - n):i, 23] - P[max(0, i - n):i, 22]) | |
| s = s[np.isfinite(s)] | |
| return float(np.median(s)) if len(s) >= MIN_STEPS else np.nan | |
| def estimate(P, i, method): | |
| """The estimate of P[i, 23] from P[i, :23] and the days before i, or NaN when hour 22 is missing (or the history is too short for med28).""" | |
| p22 = P[i, 22] | |
| if not np.isfinite(p22): | |
| return np.nan | |
| if method == "persist": | |
| return float(p22) | |
| if method == "med28": | |
| st = step_median(P, i) | |
| return float(p22 + st) if np.isfinite(st) else np.nan | |
| raise ValueError(method) | |
| def naive(P, i): | |
| """The naive candidates for P[i, 23]: persistence of hour 22, yesterday's hour 23, last week's hour 23.""" | |
| return {"persist": P[i, 22], "yday": P[i - 1, 23] if i >= 1 else np.nan, "lag7": P[i - 7, 23] if i >= 7 else np.nan} | |
| def band_offsets(res, scale=BAND_SCALE): | |
| """(lo, hi) from the residuals (actual - estimate) of the window: scale x P10 and scale x P90.""" | |
| r = np.asarray(res, float) | |
| r = r[np.isfinite(r)] | |
| if len(r) < 60: | |
| return None | |
| return round(float(np.percentile(r, 10)) * scale, 2), round(float(np.percentile(r, 90)) * scale, 2) | |