Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Electricity prices in Finland

sahkonhinnat.livemd

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(&amp;Map.fetch!(&amp;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(&amp;Map.fetch!(&amp;1, "price"))
|> Enum.with_index(1)
|> Enum.sort_by(&amp;Map.fetch!(elem(&amp;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(&amp;Map.fetch!(&amp;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(&amp;Map.fetch!(&amp;1, "avg_next"))

value of avg_next means average price of next N hours