dukascopy-node-plus 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,294 @@
1
+ import {
2
+ BufferFetcher,
3
+ CacheManager,
4
+ Format,
5
+ Instrument,
6
+ Price,
7
+ Timeframe,
8
+ URL_ROOT,
9
+ defaultConfig,
10
+ formatBytes,
11
+ generateUrls,
12
+ instrumentMetaData,
13
+ normaliseDates,
14
+ processData,
15
+ schema,
16
+ validateConfig,
17
+ validateConfigNode,
18
+ version
19
+ } from "./chunk-QOULS5Y3.js";
20
+
21
+ // src/output-formatter/index.ts
22
+ var headers = ["timestamp", "open", "high", "low", "close", "volume"];
23
+ var tickHeaders = ["timestamp", "askPrice", "bidPrice", "askVolume", "bidVolume"];
24
+ function formatOutput({
25
+ processedData,
26
+ format,
27
+ timeframe
28
+ }) {
29
+ if (processedData.length === 0) {
30
+ return [];
31
+ }
32
+ const bodyHeaders = timeframe === "tick" /* tick */ ? tickHeaders : headers;
33
+ if (format === "json" /* json */) {
34
+ const data = processedData.map((arr) => {
35
+ return arr.reduce((all, item, i) => {
36
+ const name = bodyHeaders[i];
37
+ all[name] = item;
38
+ return all;
39
+ }, {});
40
+ });
41
+ return data;
42
+ }
43
+ if (format === "csv" /* csv */) {
44
+ const csvHeaders = bodyHeaders.filter((_, i) => processedData[0][i] !== void 0);
45
+ const csv = [csvHeaders, ...processedData].map((arr) => arr.join(",")).join("\n");
46
+ return csv;
47
+ }
48
+ return processedData;
49
+ }
50
+
51
+ // src/index.ts
52
+ import { RuleDate, RuleBoolean, RuleNumber, RuleString, RuleObject } from "fastest-validator";
53
+
54
+ // src/getHistoricalRates.ts
55
+ import debug from "debug";
56
+ import os from "os";
57
+ var DEBUG_NAMESPACE = "dukascopy-node";
58
+ async function getHistoricalRates(config) {
59
+ debug(`${DEBUG_NAMESPACE}:version`)(version);
60
+ debug(`${DEBUG_NAMESPACE}:nodejs`)(process.version);
61
+ debug(`${DEBUG_NAMESPACE}:os`)(`${os.type()}, ${os.release()} (${os.platform()})`);
62
+ const { input, isValid, validationErrors } = validateConfigNode(config);
63
+ debug(`${DEBUG_NAMESPACE}:config`)("%O", {
64
+ input,
65
+ isValid,
66
+ validationErrors
67
+ });
68
+ if (!isValid) {
69
+ throw { validationErrors };
70
+ }
71
+ const {
72
+ instrument,
73
+ dates: { from, to },
74
+ timeframe,
75
+ priceType,
76
+ volumes,
77
+ volumeUnits,
78
+ utcOffset,
79
+ ignoreFlats,
80
+ format,
81
+ batchSize,
82
+ pauseBetweenBatchesMs,
83
+ useCache,
84
+ cacheFolderPath,
85
+ retryCount,
86
+ pauseBetweenRetriesMs,
87
+ retryOnEmpty
88
+ } = input;
89
+ const [startDate, endDate] = normaliseDates({
90
+ instrument,
91
+ startDate: from,
92
+ endDate: to,
93
+ timeframe,
94
+ utcOffset
95
+ });
96
+ const urls = generateUrls({
97
+ instrument,
98
+ timeframe,
99
+ priceType,
100
+ startDate,
101
+ endDate
102
+ });
103
+ debug(`${DEBUG_NAMESPACE}:urls`)(`Generated ${urls.length} urls`);
104
+ debug(`${DEBUG_NAMESPACE}:urls`)(`%O`, urls);
105
+ const onItemFetch = process.env.DEBUG ? (url, buffer, isCacheHit) => {
106
+ debug(`${DEBUG_NAMESPACE}:fetcher`)(
107
+ url,
108
+ `| ${formatBytes(buffer.length)} |`,
109
+ `${isCacheHit ? "cache" : "network"}`
110
+ );
111
+ } : void 0;
112
+ const bufferFetcher = new BufferFetcher({
113
+ batchSize,
114
+ pauseBetweenBatchesMs,
115
+ cacheManager: useCache ? new CacheManager({ cacheFolderPath }) : void 0,
116
+ retryCount,
117
+ pauseBetweenRetriesMs,
118
+ onItemFetch,
119
+ retryOnEmpty
120
+ });
121
+ const bufferredData = await bufferFetcher.fetch(urls);
122
+ const processedData = processData({
123
+ instrument,
124
+ requestedTimeframe: timeframe,
125
+ bufferObjects: bufferredData,
126
+ priceType,
127
+ volumes,
128
+ volumeUnits,
129
+ ignoreFlats
130
+ });
131
+ const [startDateMs, endDateMs] = [+startDate, +endDate];
132
+ const filteredData = processedData.filter(
133
+ ([timestamp]) => timestamp && timestamp >= startDateMs && timestamp < endDateMs
134
+ );
135
+ debug(`${DEBUG_NAMESPACE}:data`)(
136
+ `Generated ${filteredData.length} ${timeframe === "tick" /* tick */ ? "ticks" : "OHLC candles"}`
137
+ );
138
+ const formattedData = formatOutput({
139
+ processedData: filteredData,
140
+ format,
141
+ timeframe
142
+ });
143
+ return formattedData;
144
+ }
145
+ var getHistoricRates = getHistoricalRates;
146
+
147
+ // src/getCurrentRates.ts
148
+ import fetch from "node-fetch";
149
+ var timeframeMap = {
150
+ tick: "TICK",
151
+ s1: "1SEC",
152
+ m1: "1MIN",
153
+ m5: "5MIN",
154
+ m15: "15MIN",
155
+ m30: "30MIN",
156
+ h1: "1HOUR",
157
+ h4: "4HOUR",
158
+ d1: "1DAY",
159
+ mn1: "1MONTH"
160
+ };
161
+ async function getCurrentRates({
162
+ instrument,
163
+ priceType = "bid",
164
+ timeframe = "d1",
165
+ volumes = true,
166
+ format = "array",
167
+ dates,
168
+ limit
169
+ }) {
170
+ const mappedTimeframe = timeframeMap[timeframe];
171
+ const instrumentName = instrumentMetaData[instrument].name;
172
+ const offerSide = priceType === "bid" ? "B" : "A";
173
+ const timeDirection = "N";
174
+ const now = new Date();
175
+ let fromDate = now;
176
+ let toDate = now;
177
+ if (dates) {
178
+ const { from, to = now } = dates;
179
+ fromDate = typeof from === "string" || typeof from === "number" ? new Date(from) : from;
180
+ toDate = typeof to === "string" || typeof to === "number" ? new Date(to) : to;
181
+ } else {
182
+ fromDate = getTimeframeLimit(timeframe, now, limit || 10);
183
+ toDate = now;
184
+ }
185
+ let targetTimestamp = +toDate;
186
+ let shouldFetch = true;
187
+ let rates = [];
188
+ while (targetTimestamp > +fromDate && shouldFetch) {
189
+ const fetchSeed = generateSeed();
190
+ const urlParams = new URLSearchParams({
191
+ path: "chart/json3",
192
+ instrument: instrumentName,
193
+ offer_side: offerSide,
194
+ interval: mappedTimeframe,
195
+ splits: "true",
196
+ stocks: "true",
197
+ init: "true",
198
+ time_direction: timeDirection,
199
+ timestamp: String(targetTimestamp),
200
+ jsonp: `_callbacks____${fetchSeed}`
201
+ });
202
+ const url = `https://freeserv.dukascopy.com/2.0/index.php?${urlParams.toString()}`;
203
+ let fetchedRates = [];
204
+ try {
205
+ const rawResponse = await fetch(url, {
206
+ headers: {
207
+ Referer: "https://freeserv.dukascopy.com/2.0"
208
+ }
209
+ });
210
+ const rawResponseText = await rawResponse.text();
211
+ const responseClean = rawResponseText.replace(`_callbacks____${fetchSeed}(`, "").replace(");", "");
212
+ fetchedRates = JSON.parse(responseClean);
213
+ if (fetchedRates.length > 0) {
214
+ const start = +new Date(fetchedRates[0][0]);
215
+ targetTimestamp = start;
216
+ rates.unshift(...fetchedRates);
217
+ } else {
218
+ shouldFetch = false;
219
+ }
220
+ } catch (err) {
221
+ shouldFetch = false;
222
+ }
223
+ }
224
+ const shouldSlice = !dates && (typeof limit === "undefined" || typeof limit === "number");
225
+ let filteredRates = shouldSlice ? rates.slice((limit || 10) * -1) : rates.filter(function(item) {
226
+ let key = item[0];
227
+ const isWithinBounds = item[0] >= +fromDate && item[0] < +toDate;
228
+ const isUnique = !this.has(key);
229
+ if (isWithinBounds && isUnique) {
230
+ this.add(key);
231
+ return true;
232
+ }
233
+ return false;
234
+ }, /* @__PURE__ */ new Set());
235
+ if (!volumes) {
236
+ if (timeframe === "tick") {
237
+ filteredRates = filteredRates.map((item) => [item[0], item[1], item[2]]);
238
+ } else {
239
+ filteredRates = filteredRates.map((item) => [item[0], item[1], item[2], item[3], item[4]]);
240
+ }
241
+ }
242
+ const output = formatOutput({ processedData: filteredRates, format, timeframe });
243
+ return output;
244
+ }
245
+ function generateSeed() {
246
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
247
+ let result = "";
248
+ for (let i = 10; i > 0; --i)
249
+ result += chars[Math.floor(Math.random() * chars.length)];
250
+ return result;
251
+ }
252
+ function getTimeframeLimit(timeframe, now, limit) {
253
+ const nowTimestamp = +now;
254
+ const bufferMultiplier = 5;
255
+ const timeframeLimits = {
256
+ tick: 1e3,
257
+ s1: 1e3,
258
+ m1: 60 * 1e3,
259
+ m5: 5 * 60 * 1e3,
260
+ m15: 15 * 60 * 1e3,
261
+ m30: 30 * 60 * 1e3,
262
+ h1: 60 * 60 * 1e3,
263
+ h4: 4 * 60 * 60 * 1e3,
264
+ d1: 24 * 60 * 60 * 1e3,
265
+ mn1: 30 * 24 * 60 * 60 * 1e3
266
+ };
267
+ return new Date(nowTimestamp - limit * bufferMultiplier * timeframeLimits[timeframe]);
268
+ }
269
+ export {
270
+ BufferFetcher,
271
+ CacheManager,
272
+ Format,
273
+ Instrument,
274
+ instrumentMetaData as InstrumentMetaData,
275
+ Price,
276
+ RuleBoolean,
277
+ RuleDate,
278
+ RuleNumber,
279
+ RuleObject,
280
+ RuleString,
281
+ Timeframe,
282
+ URL_ROOT,
283
+ defaultConfig,
284
+ formatOutput,
285
+ generateUrls,
286
+ getCurrentRates,
287
+ getHistoricRates,
288
+ getHistoricalRates,
289
+ normaliseDates,
290
+ processData,
291
+ schema,
292
+ validateConfig,
293
+ validateConfigNode
294
+ };