UNPKG

@tvanlaerhoven/epex-client

Version:

Collect European Power Exchange (EPEX) market data

271 lines (268 loc) 11.4 kB
const today = () => { const t = new Date(); return `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, '0')}-${String(t.getDate()).padStart(2, '0')}`; }; const tomorrow = () => { const t = new Date(); t.setDate(t.getDate() + 1); return `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, '0')}-${String(t.getDate()).padStart(2, '0')}`; }; const toQuarterlyString = (period) => { const hours = Math.floor(period / 4) .toString() .padStart(2, '0'); const minutes = ((period % 4) * 15).toString().padStart(2, '0'); return `${hours}:${minutes}`; }; const toHourlyString = (period) => { return String(period).padStart(2, '0'); }; // noinspection JSUnusedGlobalSymbols var MarketArea; (function (MarketArea) { MarketArea["Austria"] = "AT"; MarketArea["Belgium"] = "BE"; MarketArea["Denmark1"] = "DK1"; MarketArea["Denmark2"] = "DK2"; MarketArea["Finland"] = "FI"; MarketArea["France"] = "FR"; MarketArea["Germany"] = "DE-LU"; MarketArea["GreatBritain"] = "GB"; MarketArea["Netherlands"] = "NL"; MarketArea["Norway1"] = "NO1"; MarketArea["Norway2"] = "NO2"; MarketArea["Norway3"] = "NO3"; MarketArea["Norway4"] = "NO4"; MarketArea["Norway5"] = "NO5"; MarketArea["Poland"] = "PL"; MarketArea["Sweden1"] = "SE1"; MarketArea["Sweden2"] = "SE2"; MarketArea["Sweden3"] = "SE3"; MarketArea["Sweden4"] = "SE4"; MarketArea["Switzerland"] = "CH"; })(MarketArea || (MarketArea = {})); const marketAreaDescriptions = { [MarketArea.Austria]: 'Austria', [MarketArea.Belgium]: 'Belgium', [MarketArea.Denmark1]: 'Denmark (Zone 1)', [MarketArea.Denmark2]: 'Denmark (Zone 2)', [MarketArea.Finland]: 'Finland', [MarketArea.France]: 'France', [MarketArea.Germany]: 'Germany', [MarketArea.GreatBritain]: 'Great Britain', [MarketArea.Netherlands]: 'Netherlands', [MarketArea.Norway1]: 'Norway (Zone 1)', [MarketArea.Norway2]: 'Norway (Zone 2)', [MarketArea.Norway3]: 'Norway (Zone 3)', [MarketArea.Norway4]: 'Norway (Zone 4)', [MarketArea.Norway5]: 'Norway (Zone 5)', [MarketArea.Poland]: 'Poland', [MarketArea.Sweden1]: 'Sweden (Zone 1)', [MarketArea.Sweden2]: 'Sweden (Zone 2)', [MarketArea.Sweden3]: 'Sweden (Zone 3)', [MarketArea.Sweden4]: 'Sweden (Zone 4)', [MarketArea.Switzerland]: 'Switzerland' }; function getMarketAreaDescription(marketArea) { return marketAreaDescriptions[marketArea]; } var TradingModality; (function (TradingModality) { TradingModality["Auction"] = "Auction"; })(TradingModality || (TradingModality = {})); var MarketSegment; (function (MarketSegment) { MarketSegment["DayAhead"] = "DayAhead"; MarketSegment["Intraday"] = "Intraday"; })(MarketSegment || (MarketSegment = {})); var DayAheadAuction; (function (DayAheadAuction) { /** * Single Day-Ahead Coupling (formerly Multi-Regional Coupling or MRC). * * A pan-European market coupling mechanism that integrates electricity markets across Europe. * Facilitates cross-border electricity trading by matching supply and demand across regions, optimizing network * usage. */ DayAheadAuction["SDAC"] = "MRC"; /** * First Day-Ahead Auction specifically for Great Britain (GB). * * Ensures price formation and market operations specific to Great Britain. */ DayAheadAuction["GB_DAA1"] = "GB"; /** * Second Day-Ahead Auction for Great Britain. */ DayAheadAuction["GB_DAA2"] = "30-call-GB"; /** * Day-Ahead Auction for Switzerland. * * A standalone auction process specific to the Swiss electricity market, which operates differently due to * Switzerland's non-EU status but maintains connections with neighboring markets. */ DayAheadAuction["CH"] = "CH"; })(DayAheadAuction || (DayAheadAuction = {})); var IntradayAuction; (function (IntradayAuction) { /** * First Intraday Auction under the Single Intraday Coupling (SIDC) framework. * * The SIDC mechanism enables cross-border intraday electricity trading between participating European countries. * IDA1 is the first auction in the intraday trading process, which allows market participants to adjust their * positions closer to real-time trading. */ IntradayAuction["SIDC_IDA1"] = "IDA1"; /** * Second Intraday Auction under the SIDC framework. * * Similar to IDA1, but it is typically scheduled later in the day to allow for additional adjustments to * electricity positions. This auction provides flexibility for market participants to buy or sell electricity * closer to the delivery time. */ IntradayAuction["SIDC_IDA2"] = "IDA2"; /** * Third Intraday Auction under the SIDC framework. * * It provides a final opportunity for market participants to make last-minute adjustments before the electricity * delivery time, optimizing cross-border trading and balancing of electricity supply and demand. */ IntradayAuction["SIDC_IDA3"] = "IDA3"; /** * First Intraday Auction for Switzerland (CH) under the intraday coupling mechanism. * * Switzerland has a separate intraday auction process, which is integrated with the European SIDC mechanism, * allowing cross-border trading between Switzerland and neighboring countries. */ IntradayAuction["CH_IDA1"] = "CH-IDA1"; /** * Second Intraday Auction for Switzerland. * * As with IDA1, but scheduled later in the day, this auction allows Swiss market participants to make additional * adjustments for electricity trading with neighboring countries. */ IntradayAuction["CH_IDA2"] = "CH-IDA2"; /** * First Intraday Auction for Great Britain (GB). * * Great Britain has its own intraday auction process, which is also integrated into the SIDC mechanism, enabling * cross-border electricity trading with neighboring countries. */ IntradayAuction["GB_IDA1"] = "GB-IDA1"; /** * Second Intraday Auction for Great Britain. * * Similar to the other auctions, GB-IDA2 allows for further adjustments to positions, allowing market participants * in Great Britain to buy or sell electricity closer to delivery time. */ IntradayAuction["GB_IDA2"] = "GB-IDA2"; })(IntradayAuction || (IntradayAuction = {})); class Client { constructor(config) { this.config = config; } async getDayAheadMarketData(area, deliveryDate = today(), tradingDate = today(), auction) { if (!auction) { auction = DayAheadAuction.SDAC; if (area === MarketArea.GreatBritain) { auction = DayAheadAuction.GB_DAA1; } if (area === MarketArea.Switzerland) { auction = DayAheadAuction.CH; } } return this.getMarketData(area, deliveryDate, tradingDate, MarketSegment.DayAhead, auction); } async getIntradayMarketData(area, deliveryDate = today(), tradingDate = today(), auction = IntradayAuction.SIDC_IDA1) { return this.getMarketData(area, deliveryDate, tradingDate, MarketSegment.Intraday, auction); } async getMarketData(area, deliveryDate = today(), tradingDate = today(), segment = MarketSegment.DayAhead, auction) { // disable certificate issues if (process === null || process === void 0 ? void 0 : process.env) { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; } const url = this.buildUrl(area, deliveryDate, tradingDate, segment, auction); this.debug('fetching url', url); const response = await fetch(url); if (!response.ok) { throw new Error(`Error reading market result: ${response.status}`); } const text = await response.text(); return { area, deliveryDate: this.extractDate(text, 'delivery_date', deliveryDate), tradingDate: this.extractDate(text, 'trading_date', tradingDate), modality: TradingModality.Auction, segment, baseloadPrice: 0, peakloadPrice: 0, entries: [], ...this.parseTable(text) }; } parseTable(data) { const table = data.match(/<table data-head(.*?)<\/table>/s); if (!table) { throw new Error('Something is wrong: could not find table data'); } const tableContent = table[1]; const baseloadMatch = tableContent.match(/<div class="flex day-1">[\s\S]*?<span>([\d.]+)<\/span>/); const peakloadMatch = tableContent.match(/<div class="flex day-2">[\s\S]*?<span>([\d.]+)<\/span>/); const baseloadPrice = baseloadMatch ? parseFloat(baseloadMatch[1]) : 0; const peakloadPrice = peakloadMatch ? parseFloat(peakloadMatch[1]) : 0; const rowMatches = [...tableContent.matchAll(/<tr class="child.*?">(.*?)<\/tr>/gs)]; const entries = rowMatches.map((match, index) => { var _a, _b, _c, _d; const cells = [...match[1].matchAll(/<td>(-?[\d.,]+)<\/td>/g)]; return { startPeriod: index, endPeriod: index + 1, buyVolume: this.parseExpectedCellFloatData((_a = cells[0]) === null || _a === void 0 ? void 0 : _a[1]), sellVolume: this.parseExpectedCellFloatData((_b = cells[1]) === null || _b === void 0 ? void 0 : _b[1]), volume: this.parseExpectedCellFloatData((_c = cells[2]) === null || _c === void 0 ? void 0 : _c[1]), price: this.parseExpectedCellFloatData((_d = cells[3]) === null || _d === void 0 ? void 0 : _d[1]) }; }); return { baseloadPrice, peakloadPrice, entries }; } buildUrl(area, deliveryDate, tradingDate, marketSegment, auction) { return (`${this.maybeUseProxy('https://www.epexspot.com/en/market-results')}` + `?market_area=${area}` + `&delivery_date=${deliveryDate}` + `&trading_date=${tradingDate}` + `&modality=${TradingModality.Auction}` + `&sub_modality=${marketSegment}` + `&auction=${auction !== null && auction !== void 0 ? auction : ''}` + `&data_mode=table`); } maybeUseProxy(url) { if (this.config.proxyServer) { const proxyUrl = `${this.config.proxyServer}/${url}`; this.debug('Using proxy url', proxyUrl); return proxyUrl; } return url; } extractDate(text, key, fallback) { const regex = new RegExp(`${key}=(\\d{4}-\\d{2}-\\d{2})`); const match = text.match(regex); return match ? match[1] : fallback; } parseExpectedCellFloatData(value) { if (!value) { throw new Error('Failed to find expected table data'); } return parseFloat(value.replace(/,/g, '')); } debug(message, ...other) { if (this.config.debug) { console.debug('EPEX', message, ...other); } } } export { Client, DayAheadAuction, IntradayAuction, MarketArea, MarketSegment, TradingModality, getMarketAreaDescription, toHourlyString, toQuarterlyString, today, tomorrow }; //# sourceMappingURL=bundle.esm.js.map