# # Obtain energy prices from Entsoe (transparancy platform) # See https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html # require 'open-uri' require 'nokogiri' class Entsoe URL = 'https://transparency.entsoe.eu' attr_reader :storage_cost def initialize(api_key = "c2287e07-0c26-4950-b430-22b7f75a8f2e") @api_key = api_key @kwh_prices = {} @storage_cost = 0.05 # how much does it cost to store 1 kwh in battery end def price_at(formatted_hour) unless @kwh_prices.key?(formatted_hour) p "Fetching Entsoe tariffs for %s" % formatted_hour @kwh_prices.merge!(query_day_ahead_prices(Date.parse(formatted_hour))) end @kwh_prices[formatted_hour] end def prices_at(date) hour_start = date.beginning_of_day day_end = hour_start.advance(days: 1) result = {} while(hour_start < day_end) do formatted_hour = hour_start.strftime("%F-%H") result.merge!({ formatted_hour => price_at(formatted_hour)}) hour_start = hour_start.advance(:hours => 1) end result end def high_low_hours(date) sorted_prices = prices_at(date).to_a.sort_by(&:last) # sort according to price highest_hour = sorted_prices.last[0] # From Apr-Oct: do not charge; every hour that has cost > charge_rate is a high_hour if [4,5,6,7,8,9,10].include?(date.month) lowest_hour = "" # effectively no charging from grid high_hours = sorted_prices.select{|p| p[1] > storage_cost} else lowest_hour = sorted_prices.first[0] # calculate hours where rate > charge_rate + storage_cost (typically 0.05) charge_rate = sorted_prices.first[1] # assume we charge at lowest hour high_hours = sorted_prices.select{|p| p[1] > charge_rate + storage_cost}.to_h.keys # reset lowest_hour (effectively not charging), when price difference smaller than storage_cost highest_price = sorted_prices.last[1] lowest_hour = "" if (highest_price-charge_rate) < storage_cost end return lowest_hour,highest_hour,high_hours end def query_day_ahead_prices(date) start_date = date.beginning_of_day end_date = date.end_of_day # A44 - Document type => Price Document # NL = '10YNL----------L' domain = '10YNL----------L' url = URL + "/api?securityToken=%s&documentType=A44&in_Domain=%s&out_Domain=%s&periodStart=%s&periodEnd=%s" % [@api_key, domain, domain, start_date.strftime("%Y%m%d%H%M"), end_date.strftime("%Y%m%d%H00")] #p url base_request(date, url) end private # get position and amount from XML snippet # # 1 # 196.23 # # # convert price to EUR per kwh, including VAT (21%) # def parse_point(xml) return xml.children[1].text.to_i, ((xml.children[3].text.to_f/1000)*1.21).round(5) end def base_request(date, url) formatted_date = date.strftime("%F") doc = Nokogiri::XML(URI.open(url)) prices = doc.xpath('.//xmlns:Point').map{|p| parse_point(p)} #returns a hash with keys formatted "yyyy-mm-dd-hr" and values price (per kwh) prices.map{|p| ["%s-%02d" % [formatted_date,(p[0]-1)], p[1]]}.to_h end end