@tvanlaerhoven/epex-client
Version:
Collect European Power Exchange (EPEX) market data
284 lines (279 loc) • 13.3 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Epex = {}));
})(this, (function (exports) { 'use strict';
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
exports.MarketArea = void 0;
(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";
})(exports.MarketArea || (exports.MarketArea = {}));
const marketAreaDescriptions = {
[exports.MarketArea.Austria]: 'Austria',
[exports.MarketArea.Belgium]: 'Belgium',
[exports.MarketArea.Denmark1]: 'Denmark (Zone 1)',
[exports.MarketArea.Denmark2]: 'Denmark (Zone 2)',
[exports.MarketArea.Finland]: 'Finland',
[exports.MarketArea.France]: 'France',
[exports.MarketArea.Germany]: 'Germany',
[exports.MarketArea.GreatBritain]: 'Great Britain',
[exports.MarketArea.Netherlands]: 'Netherlands',
[exports.MarketArea.Norway1]: 'Norway (Zone 1)',
[exports.MarketArea.Norway2]: 'Norway (Zone 2)',
[exports.MarketArea.Norway3]: 'Norway (Zone 3)',
[exports.MarketArea.Norway4]: 'Norway (Zone 4)',
[exports.MarketArea.Norway5]: 'Norway (Zone 5)',
[exports.MarketArea.Poland]: 'Poland',
[exports.MarketArea.Sweden1]: 'Sweden (Zone 1)',
[exports.MarketArea.Sweden2]: 'Sweden (Zone 2)',
[exports.MarketArea.Sweden3]: 'Sweden (Zone 3)',
[exports.MarketArea.Sweden4]: 'Sweden (Zone 4)',
[exports.MarketArea.Switzerland]: 'Switzerland'
};
function getMarketAreaDescription(marketArea) {
return marketAreaDescriptions[marketArea];
}
exports.TradingModality = void 0;
(function (TradingModality) {
TradingModality["Auction"] = "Auction";
})(exports.TradingModality || (exports.TradingModality = {}));
exports.MarketSegment = void 0;
(function (MarketSegment) {
MarketSegment["DayAhead"] = "DayAhead";
MarketSegment["Intraday"] = "Intraday";
})(exports.MarketSegment || (exports.MarketSegment = {}));
exports.DayAheadAuction = void 0;
(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";
})(exports.DayAheadAuction || (exports.DayAheadAuction = {}));
exports.IntradayAuction = void 0;
(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";
})(exports.IntradayAuction || (exports.IntradayAuction = {}));
class Client {
constructor(config) {
this.config = config;
}
async getDayAheadMarketData(area, deliveryDate = today(), tradingDate = today(), auction) {
if (!auction) {
auction = exports.DayAheadAuction.SDAC;
if (area === exports.MarketArea.GreatBritain) {
auction = exports.DayAheadAuction.GB_DAA1;
}
if (area === exports.MarketArea.Switzerland) {
auction = exports.DayAheadAuction.CH;
}
}
return this.getMarketData(area, deliveryDate, tradingDate, exports.MarketSegment.DayAhead, auction);
}
async getIntradayMarketData(area, deliveryDate = today(), tradingDate = today(), auction = exports.IntradayAuction.SIDC_IDA1) {
return this.getMarketData(area, deliveryDate, tradingDate, exports.MarketSegment.Intraday, auction);
}
async getMarketData(area, deliveryDate = today(), tradingDate = today(), segment = exports.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: exports.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=${exports.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);
}
}
}
exports.Client = Client;
exports.getMarketAreaDescription = getMarketAreaDescription;
exports.toHourlyString = toHourlyString;
exports.toQuarterlyString = toQuarterlyString;
exports.today = today;
exports.tomorrow = tomorrow;
}));
//# sourceMappingURL=bundle.cjs.js.map