Files
ems/docs/04-modules/market-prices.md
Dusan Vojacek 8b4af663d8 Initial commit
Made-with: Cursor
2026-03-20 13:27:44 +01:00

4.3 KiB
Raw Blame History

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:0014: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

# 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é)

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ů