Initial commit
Made-with: Cursor
This commit is contained in:
128
docs/04-modules/market-prices.md
Normal file
128
docs/04-modules/market-prices.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Modul: Market Prices (Spotové ceny OTE CZ)
|
||||
|
||||
## Co modul dělá
|
||||
|
||||
- Stahuje spotové ceny elektřiny z OTE CZ
|
||||
- Ukládá raw data bez vazby na lokalitu (sdílená tabulka)
|
||||
- Efektivní ceny (s marží) se dopočítávají per site přes view
|
||||
- Granularita: **15 minut** nativně (OTE CZ publikuje po hodinách → konvertujeme na 15min replikací)
|
||||
|
||||
---
|
||||
|
||||
## Zdroj dat: OTE CZ
|
||||
|
||||
**URL:** `https://www.ote-cr.cz/cs/kratkodobe-trhy/elektrina/denni-trh`
|
||||
|
||||
OTE CZ publikuje denní ceny zpravidla **den předem (D-1)** okolo 13:00–14:00 středoevropského času.
|
||||
|
||||
### Formát dat OTE CZ
|
||||
|
||||
OTE publikuje hodinové ceny v EUR/MWh. Konverzní kroky:
|
||||
1. Stáhnout XML/JSON feed nebo scrape HTML tabulky
|
||||
2. Převést EUR/MWh → CZK/kWh (kurz ČNB nebo fixní koeficient dle konfigurace)
|
||||
3. Rozložit hodinový interval na 4× 15min sloty (stejná hodnota)
|
||||
4. Uložit do `market_interval_price`
|
||||
|
||||
### Alternativní API
|
||||
|
||||
- **OTE XML feed:** `https://www.ote-cr.cz/pubapi/v1/market-data/dam?date=YYYY-MM-DD&market=DAM&type=FIN`
|
||||
- Autentikace: nepotřebná pro veřejná data
|
||||
|
||||
---
|
||||
|
||||
## Kdo stahuje data
|
||||
|
||||
**Python service: `price_importer`**
|
||||
|
||||
Samostatný modul (ne součást FastAPI, ale může být volán z ní jako task).
|
||||
|
||||
### Kdy se spouští
|
||||
|
||||
| Trigger | Čas | Popis |
|
||||
|---|---|---|
|
||||
| Scheduled (cron) | každý den 14:00 CET | Stažení cen na zítřek (D+1) |
|
||||
| Scheduled (cron) | každý den 00:05 CET | Kontrola – ověření že dnešní data jsou v DB |
|
||||
| Manual trigger | na vyžádání | API endpoint `POST /admin/import-prices?date=YYYY-MM-DD` |
|
||||
| Retry | při chybě, 3× s backoffem | Automatický opakovaný pokus |
|
||||
|
||||
### Logika importu
|
||||
|
||||
```python
|
||||
# Pseudologika importu (implementace v price_importer.py)
|
||||
|
||||
def import_prices_for_date(date: date, source: str = "OTE_CZ"):
|
||||
# 1. Zkontrolovat jestli data pro daný den už existují
|
||||
existing = db.query("SELECT COUNT(*) FROM market_interval_price WHERE interval_start::date = %s AND market_source = %s", date, source)
|
||||
if existing > 0 and not force_reimport:
|
||||
log.info("Data already exist, skipping")
|
||||
return
|
||||
|
||||
# 2. Stáhnout z OTE API
|
||||
raw_data = ote_client.fetch_dam_prices(date) # vrátí list hodinových cen v EUR/MWh
|
||||
|
||||
# 3. Konvertovat EUR/MWh → CZK/kWh
|
||||
eur_czk_rate = get_exchange_rate() # z konfigurace nebo ČNB API
|
||||
czk_per_kwh = [(price_eur_mwh * eur_czk_rate) / 1000 for price in raw_data]
|
||||
|
||||
# 4. Rozložit na 15min intervaly (1 hodina = 4 sloty se stejnou cenou)
|
||||
intervals = expand_hourly_to_15min(czk_per_kwh, date)
|
||||
|
||||
# 5. Upsert do DB (idempotentní)
|
||||
db.upsert_many("market_interval_price", intervals, conflict_keys=["market_source", "interval_start"])
|
||||
log.info(f"Imported {len(intervals)} intervals for {date}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Struktura DB záznamu
|
||||
|
||||
Viz `03-data-model.md` → tabulka `market_interval_price`.
|
||||
|
||||
Klíčové body:
|
||||
- `buy_raw_price_czk_kwh` a `sell_raw_price_czk_kwh` jsou **oddělené**
|
||||
- Pro OTE CZ je v první verzi `sell_raw_price = buy_raw_price` (reference cena)
|
||||
- `imported_at` slouží pro audit importů
|
||||
|
||||
---
|
||||
|
||||
## Efektivní ceny per site
|
||||
|
||||
Viz view `market_vw_site_effective_price` v `03-data-model.md`.
|
||||
|
||||
Marže se konfigurují v `site_market_config`:
|
||||
|
||||
| Parametr | Typ | Příklad |
|
||||
|---|---|---|
|
||||
| `buy_margin_fixed_czk` | Kč/kWh | 0.05 (5 haléřů/kWh) |
|
||||
| `buy_margin_percent` | % | 2.5 |
|
||||
| `sell_margin_fixed_czk` | Kč/kWh | -0.02 (srážka) |
|
||||
| `sell_margin_percent` | % | 0 |
|
||||
|
||||
---
|
||||
|
||||
## Konfigurace (env proměnné)
|
||||
|
||||
```env
|
||||
OTE_API_URL=https://www.ote-cr.cz/pubapi/v1/market-data/dam
|
||||
OTE_IMPORT_HOUR=14 # hodina kdy se spouští denní import
|
||||
EUR_CZK_RATE=25.0 # fallback kurz pokud ČNB API nedostupné
|
||||
CNB_API_URL=https://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.xml
|
||||
PRICE_IMPORT_RETRY_COUNT=3
|
||||
PRICE_IMPORT_RETRY_BACKOFF_SEC=300
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring a alerting
|
||||
|
||||
- Alert pokud do 16:00 nejsou v DB ceny na zítřek
|
||||
- Log každého importu (datum, počet intervalů, zdroj, trvání)
|
||||
- Endpoint `GET /health/prices?date=YYYY-MM-DD` → vrátí počet importovaných intervalů
|
||||
|
||||
---
|
||||
|
||||
## Otevřené body
|
||||
|
||||
- [ ] Kurz EUR/CZK: fixní hodnota vs denní stahování z ČNB – rozhodnout před implementací
|
||||
- [ ] OTE nabízí i intraday ceny – zatím neimplementujeme
|
||||
- [ ] Sell price: OTE nemá oddělenou nákupní a prodejní raw cenu, obě = DAM cena; může se lišit u jiných zdrojů
|
||||
Reference in New Issue
Block a user