tune microcycling
This commit is contained in:
104
scripts/analysis/ote_arbitrage_proxy.sql
Normal file
104
scripts/analysis/ote_arbitrage_proxy.sql
Normal file
@@ -0,0 +1,104 @@
|
||||
-- =============================================================
|
||||
-- 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;
|
||||
Reference in New Issue
Block a user