Electricity prices in Finland
Mix.install([
{:kino, "~> 0.7.0"},
{:vega_lite, "~> 0.1.4"},
{:kino_vega_lite, "~> 0.1.1"},
{:jason, "~> 1.3"},
{:httpoison, "~> 1.8"},
{:plug_cowboy, "~> 2.0"}
])
alias VegaLite, as: Vl
Data fetching
Spot prices are fetched from my own service, built with Elixir Phoenix.
API provides following endpoints
-
/api/price/today
todays prices -
/api/price/now
current hour price -
/api/price/next_24hrs
prices on following 24hrs
{:ok, %HTTPoison.Response{status_code: 200, body: body}} =
HTTPoison.get("https://spotti.fly.dev/api/price/next_24hrs")
price_data = Jason.decode!(body)
Graph
Vl.new(width: 800, height: 500, title: "Electricity prices today")
|> Vl.data_from_values(price_data)
|> Vl.layers([
Vl.new()
|> Vl.mark(:bar, width: 20, tooltip: true, corner_radius_end: 4)
|> Vl.encode_field(:x, "date", type: :temporal, axis: [format: "%H"], title: "klo")
|> Vl.encode_field(:y, "price", type: :quantitative, title: "c/kWh")
|> Vl.encode_field(:theta, "price", type: :quantitative),
Vl.new()
|> Vl.mark(:rule)
|> Vl.encode_field(
:y,
"price",
type: :quantitative,
aggregate: :mean
)
|> Vl.encode(:color, value: :orange)
|> Vl.encode(:size, value: 3)
|> Vl.encode(:stroke_dash, value: [6, 4])
|> Vl.encode(:tooltip, value: "")
])
Cheapest hours
price_data
|> Enum.sort_by(&Map.fetch!(&1, "price"))
|> Enum.take(5)
# Initialize a list of the 3 cheapest hours seen so far
cheapest_hours = []
# Initialize the lowest average price seen so far
lowest_avg_price = :infinity
# Iterate over the prices
for hour <- price_data do
# Add the current hour to the list of cheapest hours
cheapest_hours = [hour | cheapest_hours] |> Enum.take(3)
IO.inspect(cheapest_hours)
# Use `Enum.sum/1` to calculate the sum of the numbers
sum = Enum.sum(cheapest_hours |> Enum.map(&Map.fetch!(&1, "price")))
# Calculate the average by dividing the sum by the number of items
avg_price = sum / 3
# If the average price is lower than the lowest seen so far,
# update the list of cheapest hours and the lowest average price
if avg_price < lowest_avg_price do
cheapest_hours = cheapest_hours
lowest_avg_price = avg_price
end
end
# Iterate over the cheapest hours
for hour <- cheapest_hours do
# Get the price and date information
price_in_euro_cents = Map.fetch!(hour, "price")
date_string = Map.fetch!(hour, "date")
# Do something with the price and date information
IO.puts("#{date_string}: #{price_in_euro_cents}")
end
Rank
Calculate rank based on price, chepeast hour has lowest rank
price_data
|> Enum.sort_by(&Map.fetch!(&1, "price"))
|> Enum.with_index(1)
|> Enum.sort_by(&Map.fetch!(elem(&1, 0), "date"))
|> Enum.map(fn {data, rank} -> Map.put_new(data, "rank", rank) end)
Period of cheapest hours
hours = Kino.Input.number("Period (hrs)", default: 3)
period = Kino.Input.read(hours)
price_data
|> Enum.with_index()
|> Enum.map(fn {data, index} ->
next_items = Enum.slice(price_data, index..(index + period - 1))
sum = next_items |> Enum.map(&Map.fetch!(&1, "price")) |> Enum.sum()
avg_next_hrs = (sum / min(period, length(next_items))) |> Float.round(2)
Map.put_new(data, "avg_next", avg_next_hrs)
end)
|> Enum.slice(0..(-period - 1))
|> Enum.sort_by(&Map.fetch!(&1, "avg_next"))
value of avg_next means average price of next N hours