UNPKG

dukascopy-node

Version:

Node.js library for downloading historical market tick data for for Crypto, Stocks, ETFs, CFDs, Forex

286 lines (281 loc) 7.84 kB
import { BufferFetcher, CacheManager, Format, Instrument, Price, Timeframe, URL_ROOT, defaultConfig, formatBytes, generateUrls, instrumentMetaData, normaliseDates, processData, schema, validateConfig, validateConfigNode, version } from "./chunk-J7QH5GFA.js"; // src/output-formatter/index.ts var headers = ["timestamp", "open", "high", "low", "close", "volume"]; var tickHeaders = ["timestamp", "askPrice", "bidPrice", "askVolume", "bidVolume"]; function formatOutput({ processedData, format, timeframe }) { if (processedData.length === 0) { return []; } const bodyHeaders = timeframe === "tick" /* tick */ ? tickHeaders : headers; if (format === "json" /* json */) { const data = processedData.map((arr) => { return arr.reduce((all, item, i) => { const name = bodyHeaders[i]; all[name] = item; return all; }, {}); }); return data; } if (format === "csv" /* csv */) { const csvHeaders = bodyHeaders.filter((_, i) => processedData[0][i] !== void 0); const csv = [csvHeaders, ...processedData].map((arr) => arr.join(",")).join("\n"); return csv; } return processedData; } // src/index.ts import { RuleDate, RuleBoolean, RuleNumber, RuleString, RuleObject } from "fastest-validator"; // src/getHistoricalRates.ts import debug from "debug"; import os from "os"; var DEBUG_NAMESPACE = "dukascopy-node"; async function getHistoricalRates(config) { debug(`${DEBUG_NAMESPACE}:version`)(version); debug(`${DEBUG_NAMESPACE}:nodejs`)(process.version); debug(`${DEBUG_NAMESPACE}:os`)(`${os.type()}, ${os.release()} (${os.platform()})`); const { input, isValid, validationErrors } = validateConfigNode(config); debug(`${DEBUG_NAMESPACE}:config`)("%O", { input, isValid, validationErrors }); if (!isValid) { throw { validationErrors }; } const { instrument, dates: { from, to }, timeframe, priceType, volumes, volumeUnits, utcOffset, ignoreFlats, format, batchSize, pauseBetweenBatchesMs, useCache, cacheFolderPath, retryCount, pauseBetweenRetriesMs, retryOnEmpty } = input; const [startDate, endDate] = normaliseDates({ instrument, startDate: from, endDate: to, timeframe, utcOffset }); const urls = generateUrls({ instrument, timeframe, priceType, startDate, endDate }); debug(`${DEBUG_NAMESPACE}:urls`)(`Generated ${urls.length} urls`); debug(`${DEBUG_NAMESPACE}:urls`)(`%O`, urls); const onItemFetch = process.env.DEBUG ? (url, buffer, isCacheHit) => { debug(`${DEBUG_NAMESPACE}:fetcher`)(url, `| ${formatBytes(buffer.length)} |`, `${isCacheHit ? "cache" : "network"}`); } : void 0; const bufferFetcher = new BufferFetcher({ batchSize, pauseBetweenBatchesMs, cacheManager: useCache ? new CacheManager({ cacheFolderPath }) : void 0, retryCount, pauseBetweenRetriesMs, onItemFetch, retryOnEmpty }); const bufferredData = await bufferFetcher.fetch(urls); const processedData = processData({ instrument, requestedTimeframe: timeframe, bufferObjects: bufferredData, priceType, volumes, volumeUnits, ignoreFlats }); const [startDateMs, endDateMs] = [+startDate, +endDate]; const filteredData = processedData.filter(([timestamp]) => timestamp && timestamp >= startDateMs && timestamp < endDateMs); debug(`${DEBUG_NAMESPACE}:data`)(`Generated ${filteredData.length} ${timeframe === "tick" /* tick */ ? "ticks" : "OHLC candles"}`); const formattedData = formatOutput({ processedData: filteredData, format, timeframe }); return formattedData; } var getHistoricRates = getHistoricalRates; // src/getCurrentRates.ts import fetch from "node-fetch"; var timeframeMap = { tick: "TICK", s1: "1SEC", m1: "1MIN", m5: "5MIN", m15: "15MIN", m30: "30MIN", h1: "1HOUR", h4: "4HOUR", d1: "1DAY", mn1: "1MONTH" }; async function getCurrentRates({ instrument, priceType = "bid", timeframe = "d1", volumes = true, format = "array", dates, limit }) { const mappedTimeframe = timeframeMap[timeframe]; const instrumentName = instrumentMetaData[instrument].name; const offerSide = priceType === "bid" ? "B" : "A"; const timeDirection = "N"; const now = new Date(); let fromDate = now; let toDate = now; if (dates) { const { from, to = now } = dates; fromDate = typeof from === "string" || typeof from === "number" ? new Date(from) : from; toDate = typeof to === "string" || typeof to === "number" ? new Date(to) : to; } else { fromDate = getTimeframeLimit(timeframe, now, limit || 10); toDate = now; } let targetTimestamp = +toDate; let shouldFetch = true; let rates = []; while (targetTimestamp > +fromDate && shouldFetch) { const fetchSeed = generateSeed(); const urlParams = new URLSearchParams({ path: "chart/json3", instrument: instrumentName, offer_side: offerSide, interval: mappedTimeframe, splits: "true", stocks: "true", init: "true", time_direction: timeDirection, timestamp: String(targetTimestamp), jsonp: `_callbacks____${fetchSeed}` }); const url = `https://freeserv.dukascopy.com/2.0/index.php?${urlParams.toString()}`; let fetchedRates = []; try { const rawResponse = await fetch(url, { headers: { Referer: "https://freeserv.dukascopy.com/2.0" } }); const rawResponseText = await rawResponse.text(); const responseClean = rawResponseText.replace(`_callbacks____${fetchSeed}(`, "").replace(");", ""); fetchedRates = JSON.parse(responseClean); if (fetchedRates.length > 0) { const start = +new Date(fetchedRates[0][0]); targetTimestamp = start; rates.unshift(...fetchedRates); } else { shouldFetch = false; } } catch (err) { shouldFetch = false; } } const shouldSlice = !dates && (typeof limit === "undefined" || typeof limit === "number"); let filteredRates = shouldSlice ? rates.slice((limit || 10) * -1) : rates.filter(function(item) { let key = item[0]; const isWithinBounds = item[0] >= +fromDate && item[0] < +toDate; const isUnique = !this.has(key); if (isWithinBounds && isUnique) { this.add(key); return true; } return false; }, /* @__PURE__ */ new Set()); if (!volumes) { if (timeframe === "tick") { filteredRates = filteredRates.map((item) => [item[0], item[1], item[2]]); } else { filteredRates = filteredRates.map((item) => [item[0], item[1], item[2], item[3], item[4]]); } } const output = formatOutput({ processedData: filteredRates, format, timeframe }); return output; } function generateSeed() { const chars = "abcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; for (let i = 10; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]; return result; } function getTimeframeLimit(timeframe, now, limit) { const nowTimestamp = +now; const bufferMultiplier = 5; const timeframeLimits = { tick: 1e3, s1: 1e3, m1: 60 * 1e3, m5: 5 * 60 * 1e3, m15: 15 * 60 * 1e3, m30: 30 * 60 * 1e3, h1: 60 * 60 * 1e3, h4: 4 * 60 * 60 * 1e3, d1: 24 * 60 * 60 * 1e3, mn1: 30 * 24 * 60 * 60 * 1e3 }; return new Date(nowTimestamp - limit * bufferMultiplier * timeframeLimits[timeframe]); } export { BufferFetcher, CacheManager, Format, Instrument, Price, RuleBoolean, RuleDate, RuleNumber, RuleObject, RuleString, Timeframe, URL_ROOT, defaultConfig, formatOutput, generateUrls, getCurrentRates, getHistoricRates, getHistoricalRates, normaliseDates, processData, schema, validateConfig, validateConfigNode };