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.
- package/LICENSE +21 -0
- package/README.md +1523 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +14005 -0
- package/dist/esm/chunk-QOULS5Y3.js +13570 -0
- package/dist/esm/cli/index.js +462 -0
- package/dist/esm/index.js +294 -0
- package/dist/index.d.ts +6725 -0
- package/dist/index.js +13834 -0
- package/package.json +86 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BufferFetcher,
|
|
4
|
+
CacheManager,
|
|
5
|
+
VolumeUnit,
|
|
6
|
+
__require,
|
|
7
|
+
formatBytes,
|
|
8
|
+
generateUrls,
|
|
9
|
+
getDateTimeFormatOptions,
|
|
10
|
+
getFormattedDate,
|
|
11
|
+
instrumentMetaData,
|
|
12
|
+
normaliseDates,
|
|
13
|
+
processData,
|
|
14
|
+
schema,
|
|
15
|
+
validateConfig,
|
|
16
|
+
version
|
|
17
|
+
} from "../chunk-QOULS5Y3.js";
|
|
18
|
+
|
|
19
|
+
// src/cli/cli.ts
|
|
20
|
+
import { resolve, join } from "path";
|
|
21
|
+
import os from "os";
|
|
22
|
+
|
|
23
|
+
// src/cli/progress.ts
|
|
24
|
+
import { Bar } from "cli-progress";
|
|
25
|
+
var chalk = __require("chalk");
|
|
26
|
+
var progressBar = new Bar({
|
|
27
|
+
format: "|" + chalk.green("{bar}") + "| {percentage}%",
|
|
28
|
+
barCompleteChar: "\u2588",
|
|
29
|
+
barIncompleteChar: "\u2591",
|
|
30
|
+
hideCursor: true,
|
|
31
|
+
fps: 24,
|
|
32
|
+
barsize: 45
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// src/cli/cli.ts
|
|
36
|
+
import { createWriteStream } from "fs";
|
|
37
|
+
import { ensureDir, ensureFile, stat } from "fs-extra";
|
|
38
|
+
|
|
39
|
+
// src/cli/config.ts
|
|
40
|
+
import { program } from "commander";
|
|
41
|
+
var now = "now";
|
|
42
|
+
var commanderSchema = program.option("-d, --debug", "Output extra debugging", false).option("-s, --silent", "Hides the search config in the CLI output", false).requiredOption("-i, --instrument <value>", "Trading instrument").requiredOption("-from, --date-from <value>", "From date (yyyy-mm-dd)").option("-to, --date-to <value>", `To date (yyyy-mm-dd or '${now}')`, now).option(
|
|
43
|
+
"-t, --timeframe <value>",
|
|
44
|
+
"Timeframe aggregation (tick, s1, m1, m5, m15, m30, h1, h4, d1, mn1)",
|
|
45
|
+
"d1" /* d1 */
|
|
46
|
+
).option("-p, --price-type <value>", "Price type: (bid, ask)", "bid" /* bid */).option("-utc, --utc-offset <value>", "UTC offset in minutes", Number, 0).option("-v, --volumes", "Include volumes", false).option(
|
|
47
|
+
"-vu, --volume-units <value>",
|
|
48
|
+
"Volume units (millions, thousands, units)",
|
|
49
|
+
VolumeUnit.millions
|
|
50
|
+
).option("-fl, --flats", "Include flats (0 volumes)", false).option("-f, --format <value>", "Output format (csv, json, array)", "json" /* json */).option("-dir, --directory <value>", "Download directory", "./download").option("-bs, --batch-size <value>", "Batch size of downloaded artifacts", Number, 10).option("-bp, --batch-pause <value>", "Pause between batches in ms", Number, 1e3).option("-ch, --cache", "Use cache", false).option("-chpath, --cache-path <value>", "Folder path for cache data", "./.dukascopy-cache").option("-df, --date-format <value>", "Date format", "").option("-tz, --time-zone <value>", "Timezone", "").option("-r, --retries <value>", "Number of retries for a failed artifact download", Number, 0).option("-rp, --retry-pause <value>", "Pause between retries in milliseconds", Number, 500).option(
|
|
51
|
+
"-re, --retry-on-empty",
|
|
52
|
+
"A flag indicating whether requests with successful but empty (0 Bytes) responses should be retried. If `retries` is `0` this parameter will be ignored",
|
|
53
|
+
false
|
|
54
|
+
).option(
|
|
55
|
+
"-fr, --no-fail-after-retries",
|
|
56
|
+
"A flag indicating whether the process should fail after all retries have been exhausted. If `retries` is `0` this parameter will be ignored"
|
|
57
|
+
).option("-fn, --file-name <value>", "Custom file name for the generated file", "").option(
|
|
58
|
+
"-in, --inline",
|
|
59
|
+
"Makes files smaller in size by removing new lines in the output (works only with json and array formats)",
|
|
60
|
+
false
|
|
61
|
+
);
|
|
62
|
+
function getConfigFromCliArgs(argv) {
|
|
63
|
+
const options = commanderSchema.parse(argv).opts();
|
|
64
|
+
if (options.dateTo === now) {
|
|
65
|
+
options.dateTo = new Date();
|
|
66
|
+
}
|
|
67
|
+
const cliConfig = {
|
|
68
|
+
instrument: options.instrument,
|
|
69
|
+
dates: {
|
|
70
|
+
from: options.dateFrom,
|
|
71
|
+
to: options.dateTo
|
|
72
|
+
},
|
|
73
|
+
timeframe: options.timeframe,
|
|
74
|
+
priceType: options.priceType,
|
|
75
|
+
utcOffset: options.utcOffset,
|
|
76
|
+
volumes: options.volumes,
|
|
77
|
+
volumeUnits: options.volumeUnits,
|
|
78
|
+
ignoreFlats: !options.flats,
|
|
79
|
+
dir: options.directory,
|
|
80
|
+
silent: options.silent,
|
|
81
|
+
format: options.format,
|
|
82
|
+
batchSize: options.batchSize,
|
|
83
|
+
pauseBetweenBatchesMs: options.batchPause,
|
|
84
|
+
useCache: options.cache,
|
|
85
|
+
cacheFolderPath: options.cachePath,
|
|
86
|
+
retryCount: options.retries,
|
|
87
|
+
failAfterRetryCount: options.failAfterRetries,
|
|
88
|
+
retryOnEmpty: options.retryOnEmpty,
|
|
89
|
+
pauseBetweenRetriesMs: options.retryPause,
|
|
90
|
+
debug: options.debug,
|
|
91
|
+
inline: options.inline,
|
|
92
|
+
fileName: options.fileName,
|
|
93
|
+
dateFormat: options.dateFormat,
|
|
94
|
+
timeZone: options.timeZone
|
|
95
|
+
};
|
|
96
|
+
const cliSchema = {
|
|
97
|
+
...schema,
|
|
98
|
+
...{
|
|
99
|
+
dir: { type: "string", required: true },
|
|
100
|
+
silent: { type: "boolean", required: false },
|
|
101
|
+
debug: { type: "boolean", required: false },
|
|
102
|
+
inline: { type: "boolean", required: false },
|
|
103
|
+
fileName: { type: "string", required: false },
|
|
104
|
+
dateFormat: { type: "string", required: false },
|
|
105
|
+
timeZone: { type: "string", required: false }
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
return validateConfig(cliConfig, cliSchema);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/cli/printer.ts
|
|
112
|
+
var chalk2 = __require("chalk");
|
|
113
|
+
var log = console.log;
|
|
114
|
+
function printSpacer() {
|
|
115
|
+
log();
|
|
116
|
+
}
|
|
117
|
+
function printDivider() {
|
|
118
|
+
log(chalk2.gray("----------------------------------------------------"));
|
|
119
|
+
}
|
|
120
|
+
function printHeader(searchConfig, adjustedStartDate, adjustedEndDate) {
|
|
121
|
+
const { instrument, timeframe, priceType, utcOffset, volumes, ignoreFlats, format } = searchConfig;
|
|
122
|
+
const dateTimeFormatOptions = getDateTimeFormatOptions(timeframe);
|
|
123
|
+
printDivider();
|
|
124
|
+
log(chalk2.whiteBright("Downloading historical price data for:"));
|
|
125
|
+
printDivider();
|
|
126
|
+
log("Instrument: ", chalk2.bold(chalk2.yellow(instrumentMetaData[instrument].description)));
|
|
127
|
+
log("Timeframe: ", chalk2.bold(chalk2.yellow(timeframe)));
|
|
128
|
+
log(
|
|
129
|
+
"From date: ",
|
|
130
|
+
chalk2.bold(chalk2.yellow(getFormattedDate(adjustedStartDate, dateTimeFormatOptions)))
|
|
131
|
+
);
|
|
132
|
+
log(
|
|
133
|
+
"To date: ",
|
|
134
|
+
chalk2.bold(chalk2.yellow(getFormattedDate(adjustedEndDate, dateTimeFormatOptions)))
|
|
135
|
+
);
|
|
136
|
+
if (timeframe !== "tick") {
|
|
137
|
+
log("Price type: ", chalk2.bold(chalk2.yellow(priceType)));
|
|
138
|
+
}
|
|
139
|
+
log("Volumes: ", chalk2.bold(chalk2.yellow(volumes)));
|
|
140
|
+
log("UTC Offset: ", chalk2.bold(chalk2.yellow(utcOffset)));
|
|
141
|
+
log("Include flats: ", chalk2.bold(chalk2.yellow(!ignoreFlats)));
|
|
142
|
+
log("Format: ", chalk2.bold(chalk2.yellow(format)));
|
|
143
|
+
printDivider();
|
|
144
|
+
}
|
|
145
|
+
function printErrors(header, errorMessage) {
|
|
146
|
+
log(chalk2.redBright(header));
|
|
147
|
+
[].concat(errorMessage).forEach((error) => log(chalk2.red(` > ${error}`)));
|
|
148
|
+
printSpacer();
|
|
149
|
+
}
|
|
150
|
+
function printSuccess(text) {
|
|
151
|
+
printDivider();
|
|
152
|
+
log(chalk2.greenBright(text));
|
|
153
|
+
printSpacer();
|
|
154
|
+
}
|
|
155
|
+
function printGeneral(text) {
|
|
156
|
+
log(text);
|
|
157
|
+
printSpacer();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/cli/cli.ts
|
|
161
|
+
import chalk3 from "chalk";
|
|
162
|
+
import debug from "debug";
|
|
163
|
+
import dayjs from "dayjs";
|
|
164
|
+
import utc from "dayjs/plugin/utc";
|
|
165
|
+
import tz from "dayjs/plugin/timezone";
|
|
166
|
+
|
|
167
|
+
// src/stream-writer/index.ts
|
|
168
|
+
import fs from "fs";
|
|
169
|
+
var BatchStreamWriter = class {
|
|
170
|
+
constructor(options) {
|
|
171
|
+
this.isFileEmpty = true;
|
|
172
|
+
this.fileWriteStream = options.fileWriteStream;
|
|
173
|
+
this.timeframe = options.timeframe;
|
|
174
|
+
this.format = options.format;
|
|
175
|
+
this.isInline = options.isInline;
|
|
176
|
+
this.volumes = Boolean(options.volumes);
|
|
177
|
+
this.startDateTs = options.startDateTs;
|
|
178
|
+
this.endDateTs = options.endDateTs;
|
|
179
|
+
this.bodyHeaders = this.initHeaders();
|
|
180
|
+
}
|
|
181
|
+
initHeaders() {
|
|
182
|
+
const bodyHeaders = this.timeframe === "tick" /* tick */ ? ["timestamp", "askPrice", "bidPrice", "askVolume", "bidVolume"] : ["timestamp", "open", "high", "low", "close", "volume"];
|
|
183
|
+
if (!this.volumes) {
|
|
184
|
+
bodyHeaders.pop();
|
|
185
|
+
if (this.timeframe === "tick" /* tick */) {
|
|
186
|
+
bodyHeaders.pop();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return bodyHeaders;
|
|
190
|
+
}
|
|
191
|
+
async writeBatch(batch, dateFormatter) {
|
|
192
|
+
const batchWithinRange = [];
|
|
193
|
+
for (let j = 0; j < batch.length; j++) {
|
|
194
|
+
const item = batch[j];
|
|
195
|
+
const isItemInRange = item.length > 0 && item[0] >= this.startDateTs && item[0] < this.endDateTs;
|
|
196
|
+
if (isItemInRange) {
|
|
197
|
+
if (dateFormatter) {
|
|
198
|
+
item[0] = dateFormatter(item[0]);
|
|
199
|
+
}
|
|
200
|
+
batchWithinRange.push(item);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (let i = 0; i < batchWithinRange.length; i++) {
|
|
204
|
+
let item = batchWithinRange[i];
|
|
205
|
+
const isFirstItem = i === 0;
|
|
206
|
+
const isLastItem = i === batchWithinRange.length - 1;
|
|
207
|
+
const shouldOpen = isFirstItem && this.isFileEmpty;
|
|
208
|
+
let body = "";
|
|
209
|
+
if (this.format === "csv" /* csv */) {
|
|
210
|
+
if (shouldOpen) {
|
|
211
|
+
const csvHeaders = this.bodyHeaders.join(",");
|
|
212
|
+
body += `${csvHeaders}
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
body += item.join(",") + "\n";
|
|
216
|
+
} else {
|
|
217
|
+
if (shouldOpen) {
|
|
218
|
+
body += "[" + (!this.isInline ? "\n" : "");
|
|
219
|
+
}
|
|
220
|
+
if (isFirstItem && !this.isFileEmpty) {
|
|
221
|
+
body += "," + (!this.isInline ? "\n" : "");
|
|
222
|
+
}
|
|
223
|
+
if (dateFormatter && (this.format === "json" /* json */ || this.format === "array" /* array */)) {
|
|
224
|
+
item[0] = `"${item[0]}"`;
|
|
225
|
+
}
|
|
226
|
+
if (this.format === "json" /* json */) {
|
|
227
|
+
const jsonObjectBody = item.map((val, i2) => `"${this.bodyHeaders[i2]}":${val}`).join(",");
|
|
228
|
+
body += `{${jsonObjectBody}}`;
|
|
229
|
+
} else {
|
|
230
|
+
const arrayBody = item.join(",");
|
|
231
|
+
body += `[${arrayBody}]`;
|
|
232
|
+
}
|
|
233
|
+
body += (!isLastItem ? "," : "") + (!isLastItem && !this.isInline ? "\n" : "");
|
|
234
|
+
}
|
|
235
|
+
if (!body) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const ableToWrite = this.fileWriteStream.write(body);
|
|
239
|
+
if (ableToWrite) {
|
|
240
|
+
if (this.isFileEmpty) {
|
|
241
|
+
this.isFileEmpty = false;
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
await new Promise((resolve2) => {
|
|
245
|
+
this.fileWriteStream.once("drain", resolve2);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
async closeBatchFile() {
|
|
252
|
+
if ((this.format === "json" /* json */ || this.format === "array" /* array */) && !this.isFileEmpty) {
|
|
253
|
+
const body = this.isInline ? "]" : "\n]";
|
|
254
|
+
const ableToWrite = this.fileWriteStream.write(body);
|
|
255
|
+
if (!ableToWrite) {
|
|
256
|
+
await new Promise((resolve2) => {
|
|
257
|
+
this.fileWriteStream.once("drain", resolve2);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
this.fileWriteStream.end();
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// src/utils/formatTimeDuration.ts
|
|
267
|
+
function formatTimeDuration(durationMs) {
|
|
268
|
+
if (durationMs < 1e3) {
|
|
269
|
+
return `${durationMs}ms`;
|
|
270
|
+
} else if (durationMs < 6e4) {
|
|
271
|
+
return `${(durationMs / 1e3).toFixed(1)}s`;
|
|
272
|
+
} else if (durationMs < 36e5) {
|
|
273
|
+
const min = Math.floor(durationMs / 6e4);
|
|
274
|
+
const sec = Math.floor((durationMs - min * 6e4) / 1e3);
|
|
275
|
+
return `${min}m ${sec}s`;
|
|
276
|
+
} else {
|
|
277
|
+
const hours = Math.floor(durationMs / 36e5);
|
|
278
|
+
const min = Math.floor((durationMs - hours * 36e5) / 6e4);
|
|
279
|
+
const sec = Math.floor((durationMs - hours * 36e5 - min * 6e4) / 1e3);
|
|
280
|
+
return `${hours}h ${min}m ${sec}s`;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/cli/cli.ts
|
|
285
|
+
dayjs.extend(utc);
|
|
286
|
+
dayjs.extend(tz);
|
|
287
|
+
var DEBUG_NAMESPACE = "dukascopy-node:cli";
|
|
288
|
+
async function run(argv) {
|
|
289
|
+
const { input, isValid, validationErrors } = getConfigFromCliArgs(argv);
|
|
290
|
+
let {
|
|
291
|
+
instrument,
|
|
292
|
+
dates: { from: fromDate, to: toDate },
|
|
293
|
+
timeframe,
|
|
294
|
+
priceType,
|
|
295
|
+
utcOffset,
|
|
296
|
+
volumes,
|
|
297
|
+
volumeUnits,
|
|
298
|
+
ignoreFlats,
|
|
299
|
+
format,
|
|
300
|
+
batchSize,
|
|
301
|
+
pauseBetweenBatchesMs,
|
|
302
|
+
useCache,
|
|
303
|
+
cacheFolderPath,
|
|
304
|
+
dir,
|
|
305
|
+
silent,
|
|
306
|
+
debug: isDebugActive,
|
|
307
|
+
inline,
|
|
308
|
+
retryCount,
|
|
309
|
+
failAfterRetryCount,
|
|
310
|
+
retryOnEmpty,
|
|
311
|
+
pauseBetweenRetriesMs,
|
|
312
|
+
fileName: customFileName,
|
|
313
|
+
dateFormat,
|
|
314
|
+
timeZone
|
|
315
|
+
} = input;
|
|
316
|
+
if (isDebugActive) {
|
|
317
|
+
debug.enable(`${DEBUG_NAMESPACE}:*`);
|
|
318
|
+
} else {
|
|
319
|
+
if (process.env.DEBUG) {
|
|
320
|
+
isDebugActive = true;
|
|
321
|
+
debug.enable(process.env.DEBUG);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const downloadStartTs = Date.now();
|
|
325
|
+
try {
|
|
326
|
+
debug(`${DEBUG_NAMESPACE}:version`)(version);
|
|
327
|
+
debug(`${DEBUG_NAMESPACE}:nodejs`)(process.version);
|
|
328
|
+
debug(`${DEBUG_NAMESPACE}:os`)(`${os.type()}, ${os.release()} (${os.platform()})`);
|
|
329
|
+
debug(`${DEBUG_NAMESPACE}:config`)("%O", {
|
|
330
|
+
input,
|
|
331
|
+
isValid,
|
|
332
|
+
validationErrors
|
|
333
|
+
});
|
|
334
|
+
if (isValid) {
|
|
335
|
+
const [startDate, endDate] = normaliseDates({
|
|
336
|
+
instrument,
|
|
337
|
+
startDate: fromDate,
|
|
338
|
+
endDate: toDate,
|
|
339
|
+
timeframe,
|
|
340
|
+
utcOffset
|
|
341
|
+
});
|
|
342
|
+
const fileExtension = format === "csv" /* csv */ ? "csv" /* csv */ : "json" /* json */;
|
|
343
|
+
const dateRangeStr = [startDate, endDate].map((date) => {
|
|
344
|
+
let cutoff = 10;
|
|
345
|
+
const hasHours = date.getUTCHours() !== 0;
|
|
346
|
+
const hasMinutes = date.getUTCMinutes() !== 0;
|
|
347
|
+
if (hasHours) {
|
|
348
|
+
cutoff = 13;
|
|
349
|
+
}
|
|
350
|
+
if (hasMinutes) {
|
|
351
|
+
cutoff = 16;
|
|
352
|
+
}
|
|
353
|
+
return date.toISOString().slice(0, cutoff);
|
|
354
|
+
}).join("-");
|
|
355
|
+
const fileName = customFileName ? `${customFileName}.${fileExtension}` : `${instrument}-${timeframe}${timeframe === "tick" ? "" : "-" + priceType}-${dateRangeStr}.${fileExtension}`;
|
|
356
|
+
const folderPath = resolve(process.cwd(), dir);
|
|
357
|
+
const filePath = resolve(folderPath, fileName);
|
|
358
|
+
if (!isDebugActive) {
|
|
359
|
+
silent ? printDivider() : printHeader(input, startDate, endDate);
|
|
360
|
+
}
|
|
361
|
+
const urls = generateUrls({ instrument, timeframe, priceType, startDate, endDate });
|
|
362
|
+
debug(`${DEBUG_NAMESPACE}:urls`)(`Generated ${urls.length} urls`);
|
|
363
|
+
debug(`${DEBUG_NAMESPACE}:urls`)(`%O`, urls);
|
|
364
|
+
let step = 0;
|
|
365
|
+
if (!isDebugActive) {
|
|
366
|
+
progressBar.start(urls.length, step);
|
|
367
|
+
}
|
|
368
|
+
await ensureDir(folderPath);
|
|
369
|
+
const fileWriteStream = createWriteStream(filePath, { flags: "w+" });
|
|
370
|
+
fileWriteStream.on("finish", async () => {
|
|
371
|
+
const downloadEndTs = Date.now();
|
|
372
|
+
if (!isDebugActive) {
|
|
373
|
+
progressBar.stop();
|
|
374
|
+
}
|
|
375
|
+
const relativeFilePath = join(dir, fileName);
|
|
376
|
+
await ensureFile(filePath);
|
|
377
|
+
const { size } = await stat(filePath);
|
|
378
|
+
printSuccess(`\u221A File saved: ${chalk3.bold(relativeFilePath)} (${formatBytes(size)})`);
|
|
379
|
+
printGeneral(`Download time: ${formatTimeDuration(downloadEndTs - downloadStartTs)}`);
|
|
380
|
+
});
|
|
381
|
+
const batchStreamWriter = new BatchStreamWriter({
|
|
382
|
+
fileWriteStream,
|
|
383
|
+
timeframe,
|
|
384
|
+
format,
|
|
385
|
+
isInline: inline,
|
|
386
|
+
volumes,
|
|
387
|
+
startDateTs: +startDate,
|
|
388
|
+
endDateTs: +endDate
|
|
389
|
+
});
|
|
390
|
+
const bufferFetcher = new BufferFetcher({
|
|
391
|
+
batchSize,
|
|
392
|
+
pauseBetweenBatchesMs,
|
|
393
|
+
cacheManager: useCache ? new CacheManager({ cacheFolderPath }) : void 0,
|
|
394
|
+
retryCount,
|
|
395
|
+
retryOnEmpty,
|
|
396
|
+
failAfterRetryCount,
|
|
397
|
+
pauseBetweenRetriesMs,
|
|
398
|
+
onItemFetch: (url, buffer, isCacheHit) => {
|
|
399
|
+
debug(`${DEBUG_NAMESPACE}:fetcher`)(
|
|
400
|
+
url,
|
|
401
|
+
`| ${formatBytes(buffer.length)} |`,
|
|
402
|
+
`${isCacheHit ? "cache" : "network"}`
|
|
403
|
+
);
|
|
404
|
+
if (!isDebugActive) {
|
|
405
|
+
step += 1;
|
|
406
|
+
progressBar.update(step);
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
onBatchFetch: async (bufferObjects, isLastBatch) => {
|
|
410
|
+
const filteredBatchData = [];
|
|
411
|
+
for (let j = 0, m = bufferObjects.length; j < m; j++) {
|
|
412
|
+
if (bufferObjects[j].buffer.length > 0) {
|
|
413
|
+
filteredBatchData.push(bufferObjects[j]);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (filteredBatchData.length) {
|
|
417
|
+
const processedBatch = processData({
|
|
418
|
+
instrument,
|
|
419
|
+
requestedTimeframe: timeframe,
|
|
420
|
+
bufferObjects: filteredBatchData,
|
|
421
|
+
priceType,
|
|
422
|
+
volumes,
|
|
423
|
+
volumeUnits,
|
|
424
|
+
ignoreFlats
|
|
425
|
+
});
|
|
426
|
+
await batchStreamWriter.writeBatch(
|
|
427
|
+
processedBatch,
|
|
428
|
+
dateFormat ? (timeStamp) => {
|
|
429
|
+
if (dateFormat === "iso") {
|
|
430
|
+
return new Date(timeStamp).toISOString();
|
|
431
|
+
}
|
|
432
|
+
if (timeZone) {
|
|
433
|
+
return dayjs(timeStamp).tz(timeZone).format(dateFormat);
|
|
434
|
+
}
|
|
435
|
+
return dayjs(timeStamp).utc().format(dateFormat);
|
|
436
|
+
} : void 0
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
if (isLastBatch) {
|
|
440
|
+
await batchStreamWriter.closeBatchFile();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
await bufferFetcher.fetch_optimized(urls);
|
|
445
|
+
} else {
|
|
446
|
+
printErrors(
|
|
447
|
+
"Search config invalid:",
|
|
448
|
+
validationErrors.map((err) => (err == null ? void 0 : err.message) || "")
|
|
449
|
+
);
|
|
450
|
+
process.exit(0);
|
|
451
|
+
}
|
|
452
|
+
} catch (err) {
|
|
453
|
+
const errorMsg = err instanceof Error ? err.message : JSON.stringify(err);
|
|
454
|
+
printErrors("\nSomething went wrong:", errorMsg);
|
|
455
|
+
process.exit(0);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/cli/index.ts
|
|
460
|
+
(async () => {
|
|
461
|
+
await run(process.argv);
|
|
462
|
+
})();
|