-- ============================================================= -- Hrubý odhad „kolik by přineslo přesunout ~30 kWh/den“ z levných -- slotů (OTE sell < práh) do večerních/ranních špiček. -- -- Zjednodušení: -- • Kalendářní den v Europe/Prague. -- • Bereme jen dny, kde existuje aspoň jeden 15min slot s sell < :cheap_thr. -- • „Levná“ strana: průměrná OTE sell ve všech slotech toho dne s sell < :cheap_thr. -- • „Drahá“ strana: okno večer+ráno (18:00–24:00 a 0:00–8:00), seřadíme sloty -- podle ceny DESC, vyhodíme :drop_top nejvyšších (malá baterie je už „sežere“), -- vezmeme dalších :take_slot 15min intervalů → :take_slot × 0,25 h × 12 kW = 30 kWh -- při 12 kW a take_slot = 10. -- • Hrubý přínos (Kč/den) ≈ :shift_kwh * (avg_peak - avg_cheap); bez účinnosti baterie. -- -- Uprav parametry v nejníže (date_trunc rozsah, práhy, počty slotů). -- Spuštění: psql -v ON_ERROR_STOP=1 -f scripts/analysis/ote_arbitrage_proxy.sql -- ============================================================= WITH params AS ( SELECT 0.3::numeric AS cheap_thr, 0::int AS drop_top, -- vynechat N nejdražších 15min ve večer+ráno okně 12::int AS take_slot, -- dalších N slotů = 2,5 h při 15 min 30::numeric AS shift_kwh -- objem energie pro hrubý spread (volitelně = take_slot * 0.25 * 12) ), slots AS ( SELECT interval_start, (interval_start AT TIME ZONE 'Europe/Prague')::date AS d, (interval_start AT TIME ZONE 'Europe/Prague')::time AS t, sell_raw_price_czk_kwh::numeric AS sell FROM ems.market_interval_price WHERE market_source = 'OTE_CZ' AND interval_start >= TIMESTAMPTZ '2025-04-01 Europe/Prague' AND interval_start < TIMESTAMPTZ '2026-04-01 Europe/Prague' ), days_cheap AS ( SELECT s.d FROM slots s GROUP BY s.d HAVING MIN(s.sell) < (SELECT cheap_thr FROM params) ), cheap_side AS ( SELECT s.d, AVG(s.sell) AS avg_cheap FROM slots s INNER JOIN days_cheap dc ON dc.d = s.d WHERE s.sell < (SELECT cheap_thr FROM params) GROUP BY s.d ), evening_morning AS ( SELECT s.d, s.sell, s.t FROM slots s INNER JOIN days_cheap dc ON dc.d = s.d WHERE s.t >= TIME '18:00' OR s.t < TIME '08:00' ), ranked AS ( SELECT em.d, em.sell, ROW_NUMBER() OVER (PARTITION BY em.d ORDER BY em.sell DESC, em.t) AS rn FROM evening_morning em ), peak_pick AS ( SELECT r.d, r.sell FROM ranked r WHERE r.rn > (SELECT drop_top FROM params) AND r.rn <= (SELECT drop_top + take_slot FROM params) ), peak_side AS ( SELECT d, AVG(sell) AS avg_peak FROM peak_pick GROUP BY d ), per_day AS ( SELECT c.d, c.avg_cheap, p.avg_peak, (SELECT shift_kwh FROM params) * (p.avg_peak - c.avg_cheap) AS rough_kc_day FROM cheap_side c INNER JOIN peak_side p ON p.d = c.d ), period AS ( SELECT (TIMESTAMPTZ '2026-04-01 Europe/Prague' - TIMESTAMPTZ '2024-04-01 Europe/Prague') AS len ) SELECT COUNT(*)::int AS days_qualifying, ROUND(SUM(pd.rough_kc_day)::numeric, 2) AS spread_kc_sum_period, ROUND(AVG(pd.rough_kc_day)::numeric, 4) AS spread_kc_avg_per_qualifying_day, ROUND( (SUM(pd.rough_kc_day) / NULLIF(EXTRACT(EPOCH FROM (SELECT len FROM period)) / 86400.0, 0) * 365.0)::numeric, 2 ) AS spread_kc_naive_per_solar_year FROM per_day pd;