@insightsentry/mcp 1.4.43 → 1.4.45
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/README.md +44 -3
- package/dist/api-client.d.ts +17 -5
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +125 -70
- package/dist/api-client.js.map +1 -1
- package/dist/arg-coercion.js +10 -5
- package/dist/arg-coercion.js.map +1 -1
- package/dist/cancellation.d.ts +7 -0
- package/dist/cancellation.d.ts.map +1 -0
- package/dist/cancellation.js +48 -0
- package/dist/cancellation.js.map +1 -0
- package/dist/chart.d.ts +1 -1
- package/dist/chart.d.ts.map +1 -1
- package/dist/chart.js +96 -23
- package/dist/chart.js.map +1 -1
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +66 -15
- package/dist/cli.js.map +1 -1
- package/dist/csv.d.ts +14 -0
- package/dist/csv.d.ts.map +1 -0
- package/dist/csv.js +88 -0
- package/dist/csv.js.map +1 -0
- package/dist/history-merge.d.ts +2 -0
- package/dist/history-merge.d.ts.map +1 -0
- package/dist/history-merge.js +238 -0
- package/dist/history-merge.js.map +1 -0
- package/dist/history.d.ts +3 -1
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js +59 -100
- package/dist/history.js.map +1 -1
- package/dist/index.js +22 -11
- package/dist/index.js.map +1 -1
- package/dist/response-storage.d.ts +7 -0
- package/dist/response-storage.d.ts.map +1 -1
- package/dist/response-storage.js +15 -2
- package/dist/response-storage.js.map +1 -1
- package/dist/tool-definitions.d.ts.map +1 -1
- package/dist/tool-definitions.js +19 -4
- package/dist/tool-definitions.js.map +1 -1
- package/dist/tool-runner.d.ts +3 -1
- package/dist/tool-runner.d.ts.map +1 -1
- package/dist/tool-runner.js +23 -3
- package/dist/tool-runner.js.map +1 -1
- package/dist/version-status.d.ts +4 -2
- package/dist/version-status.d.ts.map +1 -1
- package/dist/version-status.js +83 -33
- package/dist/version-status.js.map +1 -1
- package/dist/websocket-stream.js +2 -2
- package/dist/websocket-stream.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import { mkdir, mkdtemp, open, rename, rm, unlink } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { escapeCsvCell, readCsvRows } from "./csv.js";
|
|
6
|
+
const RUN_ROWS = 4096;
|
|
7
|
+
const RUN_BYTES = 1024 * 1024;
|
|
8
|
+
const MERGE_FAN_IN = 8;
|
|
9
|
+
class BufferedWriter {
|
|
10
|
+
file;
|
|
11
|
+
parts = [];
|
|
12
|
+
bytes = 0;
|
|
13
|
+
constructor(file) {
|
|
14
|
+
this.file = file;
|
|
15
|
+
}
|
|
16
|
+
async write(value) {
|
|
17
|
+
this.parts.push(value);
|
|
18
|
+
this.bytes += Buffer.byteLength(value);
|
|
19
|
+
if (this.bytes >= 64 * 1024)
|
|
20
|
+
await this.flush();
|
|
21
|
+
}
|
|
22
|
+
async flush() {
|
|
23
|
+
if (!this.parts.length)
|
|
24
|
+
return;
|
|
25
|
+
await this.file.writeFile(this.parts.join(""), "utf8");
|
|
26
|
+
this.parts = [];
|
|
27
|
+
this.bytes = 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function writeRows(file, rows, signal) {
|
|
31
|
+
const handle = await open(file, "wx");
|
|
32
|
+
try {
|
|
33
|
+
const writer = new BufferedWriter(handle);
|
|
34
|
+
for await (const row of rows) {
|
|
35
|
+
signal?.throwIfAborted();
|
|
36
|
+
await writer.write(`${JSON.stringify(row)}\n`);
|
|
37
|
+
}
|
|
38
|
+
await writer.flush();
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await handle.close();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function* readRun(file, signal) {
|
|
45
|
+
const stream = createReadStream(file, { encoding: "utf8", highWaterMark: 16 * 1024, signal });
|
|
46
|
+
const lines = createInterface({ input: stream, crlfDelay: Infinity });
|
|
47
|
+
try {
|
|
48
|
+
for await (const line of lines) {
|
|
49
|
+
signal?.throwIfAborted();
|
|
50
|
+
yield JSON.parse(line);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
lines.close();
|
|
55
|
+
stream.destroy();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function* mergeRuns(files, compare, signal) {
|
|
59
|
+
const readers = files.map((file) => readRun(file, signal));
|
|
60
|
+
const heap = [];
|
|
61
|
+
const push = (entry) => {
|
|
62
|
+
let index = heap.length;
|
|
63
|
+
heap.push(entry);
|
|
64
|
+
while (index > 0) {
|
|
65
|
+
const parent = (index - 1) >>> 1;
|
|
66
|
+
if (compare(heap[parent].row, entry.row) <= 0)
|
|
67
|
+
break;
|
|
68
|
+
heap[index] = heap[parent];
|
|
69
|
+
index = parent;
|
|
70
|
+
}
|
|
71
|
+
heap[index] = entry;
|
|
72
|
+
};
|
|
73
|
+
const pop = () => {
|
|
74
|
+
const first = heap[0];
|
|
75
|
+
const last = heap.pop();
|
|
76
|
+
if (heap.length && last) {
|
|
77
|
+
let index = 0;
|
|
78
|
+
while (index * 2 + 1 < heap.length) {
|
|
79
|
+
let child = index * 2 + 1;
|
|
80
|
+
if (child + 1 < heap.length && compare(heap[child + 1].row, heap[child].row) < 0)
|
|
81
|
+
child++;
|
|
82
|
+
if (compare(last.row, heap[child].row) <= 0)
|
|
83
|
+
break;
|
|
84
|
+
heap[index] = heap[child];
|
|
85
|
+
index = child;
|
|
86
|
+
}
|
|
87
|
+
heap[index] = last;
|
|
88
|
+
}
|
|
89
|
+
return first;
|
|
90
|
+
};
|
|
91
|
+
try {
|
|
92
|
+
for (let index = 0; index < readers.length; index++) {
|
|
93
|
+
const item = await readers[index].next();
|
|
94
|
+
if (!item.done)
|
|
95
|
+
push({ row: item.value, reader: index });
|
|
96
|
+
}
|
|
97
|
+
while (heap.length) {
|
|
98
|
+
signal?.throwIfAborted();
|
|
99
|
+
const item = pop();
|
|
100
|
+
yield item.row;
|
|
101
|
+
const next = await readers[item.reader].next();
|
|
102
|
+
if (!next.done)
|
|
103
|
+
push({ row: next.value, reader: item.reader });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
await Promise.all(readers.map((reader) => reader.return(undefined)));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function* externalSort(source, compare, directory, prefix, signal) {
|
|
111
|
+
let rows = [];
|
|
112
|
+
let bytes = 0;
|
|
113
|
+
let sequence = 0;
|
|
114
|
+
let files = [];
|
|
115
|
+
const nextFile = () => path.join(directory, `${prefix}-${sequence++}.ndjson`);
|
|
116
|
+
const spill = async () => {
|
|
117
|
+
if (!rows.length)
|
|
118
|
+
return;
|
|
119
|
+
rows.sort(compare);
|
|
120
|
+
const file = nextFile();
|
|
121
|
+
await writeRows(file, rows, signal);
|
|
122
|
+
files.push(file);
|
|
123
|
+
rows = [];
|
|
124
|
+
bytes = 0;
|
|
125
|
+
};
|
|
126
|
+
for await (const row of source) {
|
|
127
|
+
rows.push(row);
|
|
128
|
+
bytes += Buffer.byteLength(JSON.stringify(row));
|
|
129
|
+
if (rows.length >= RUN_ROWS || bytes >= RUN_BYTES)
|
|
130
|
+
await spill();
|
|
131
|
+
}
|
|
132
|
+
await spill();
|
|
133
|
+
while (files.length > MERGE_FAN_IN) {
|
|
134
|
+
const next = [];
|
|
135
|
+
for (let index = 0; index < files.length; index += MERGE_FAN_IN) {
|
|
136
|
+
const group = files.slice(index, index + MERGE_FAN_IN);
|
|
137
|
+
if (group.length === 1) {
|
|
138
|
+
next.push(group[0]);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const file = nextFile();
|
|
142
|
+
await writeRows(file, mergeRuns(group, compare, signal), signal);
|
|
143
|
+
for (const old of group)
|
|
144
|
+
await unlink(old);
|
|
145
|
+
next.push(file);
|
|
146
|
+
}
|
|
147
|
+
files = next;
|
|
148
|
+
}
|
|
149
|
+
yield* mergeRuns(files, compare, signal);
|
|
150
|
+
for (const file of files)
|
|
151
|
+
await unlink(file);
|
|
152
|
+
}
|
|
153
|
+
export async function mergeCsvFiles(files, outputPath, signal) {
|
|
154
|
+
signal?.throwIfAborted();
|
|
155
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
156
|
+
const workspace = await mkdtemp(path.join(path.dirname(outputPath), ".insight-merge-"));
|
|
157
|
+
const headers = [];
|
|
158
|
+
const positions = new Map();
|
|
159
|
+
let ordinal = 0;
|
|
160
|
+
async function* inputRows() {
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
let columns;
|
|
163
|
+
let mapping = [];
|
|
164
|
+
let keyIndices = [];
|
|
165
|
+
for await (const values of readCsvRows(file, signal)) {
|
|
166
|
+
if (!columns) {
|
|
167
|
+
columns = values;
|
|
168
|
+
keyIndices = ["code", "bar_type", "time"].map((key) => values.indexOf(key));
|
|
169
|
+
if (keyIndices.includes(-1))
|
|
170
|
+
throw new Error("Merged CSV requires code, bar_type, and time columns for deduplication");
|
|
171
|
+
mapping = columns.map((column) => {
|
|
172
|
+
let index = positions.get(column);
|
|
173
|
+
if (index === undefined) {
|
|
174
|
+
index = headers.length;
|
|
175
|
+
positions.set(column, index);
|
|
176
|
+
headers.push(column);
|
|
177
|
+
}
|
|
178
|
+
return index;
|
|
179
|
+
});
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const projected = [];
|
|
183
|
+
for (let index = 0; index < mapping.length; index++)
|
|
184
|
+
projected[mapping[index]] = values[index] ?? "";
|
|
185
|
+
yield {
|
|
186
|
+
key: JSON.stringify(keyIndices.map((index) => values[index] ?? "")),
|
|
187
|
+
first: ordinal,
|
|
188
|
+
last: ordinal++,
|
|
189
|
+
values: projected,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const byKey = (a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : a.last - b.last);
|
|
195
|
+
async function* deduplicated() {
|
|
196
|
+
let pending;
|
|
197
|
+
for await (const row of externalSort(inputRows(), byKey, workspace, "key", signal)) {
|
|
198
|
+
if (pending?.key === row.key) {
|
|
199
|
+
pending.last = row.last;
|
|
200
|
+
pending.values = row.values;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
if (pending)
|
|
204
|
+
yield pending;
|
|
205
|
+
pending = row;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (pending)
|
|
209
|
+
yield pending;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const output = path.join(workspace, "merged.csv");
|
|
213
|
+
const handle = await open(output, "wx");
|
|
214
|
+
try {
|
|
215
|
+
const writer = new BufferedWriter(handle);
|
|
216
|
+
let wroteHeader = false;
|
|
217
|
+
for await (const row of externalSort(deduplicated(), (a, b) => a.first - b.first, workspace, "order", signal)) {
|
|
218
|
+
if (!wroteHeader) {
|
|
219
|
+
await writer.write(`${headers.map(escapeCsvCell).join(",")}\n`);
|
|
220
|
+
wroteHeader = true;
|
|
221
|
+
}
|
|
222
|
+
await writer.write(`${headers.map((_header, index) => escapeCsvCell(row.values[index])).join(",")}\n`);
|
|
223
|
+
}
|
|
224
|
+
if (!wroteHeader && headers.length)
|
|
225
|
+
await writer.write(`${headers.map(escapeCsvCell).join(",")}\n`);
|
|
226
|
+
await writer.flush();
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
await handle.close();
|
|
230
|
+
}
|
|
231
|
+
signal?.throwIfAborted();
|
|
232
|
+
await rename(output, outputPath);
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
await rm(workspace, { recursive: true, force: true });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=history-merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-merge.js","sourceRoot":"","sources":["../src/history-merge.ts"],"names":[],"mappings":"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"}
|
package/dist/history.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { type ApiRequestOptions } from "./api-client.js";
|
|
1
2
|
export type HistoryBarType = "second" | "minute" | "hour" | "day" | "week" | "month";
|
|
2
3
|
export type HistoryOutputFormat = "json" | "csv" | "both";
|
|
3
|
-
export type RequestFn = (method: string, pathTemplate: string, params: Record<string, any
|
|
4
|
+
export type RequestFn = (method: string, pathTemplate: string, params: Record<string, any>, options?: ApiRequestOptions) => Promise<any>;
|
|
4
5
|
export interface DownloadHistoryOptions {
|
|
5
6
|
symbol: string;
|
|
6
7
|
from: string;
|
|
@@ -30,6 +31,7 @@ export interface HistoryProgressEvent {
|
|
|
30
31
|
error?: string;
|
|
31
32
|
}
|
|
32
33
|
export interface DownloadHistoryDeps {
|
|
34
|
+
signal?: AbortSignal;
|
|
33
35
|
request: RequestFn;
|
|
34
36
|
onProgress?: (event: HistoryProgressEvent) => void;
|
|
35
37
|
}
|
package/dist/history.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"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"}
|
package/dist/history.js
CHANGED
|
@@ -2,6 +2,8 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { mkdir, readdir, readFile, rename, rmdir, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { isTerminalApiError } from "./api-client.js";
|
|
5
|
+
import { CsvParser, escapeCsvCell } from "./csv.js";
|
|
6
|
+
import { mergeCsvFiles } from "./history-merge.js";
|
|
5
7
|
import { validateHistoryBarInterval } from "./history-validation.js";
|
|
6
8
|
const HISTORY_PATH = "/v3/symbols/{symbol}/history";
|
|
7
9
|
const SERIES_PATH = "/v3/symbols/{symbol}/series";
|
|
@@ -24,11 +26,26 @@ export async function planHistoryRequests(options, deps) {
|
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
export async function downloadHistory(options, deps) {
|
|
29
|
+
deps.signal?.throwIfAborted();
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const abort = () => controller.abort(deps.signal?.reason);
|
|
32
|
+
deps.signal?.addEventListener("abort", abort, { once: true });
|
|
33
|
+
try {
|
|
34
|
+
return await downloadHistoryRun(options, { ...deps, signal: controller.signal }, controller);
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
deps.signal?.removeEventListener("abort", abort);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function downloadHistoryRun(options, deps, controller) {
|
|
27
41
|
const format = options.format ?? "csv";
|
|
28
42
|
const concurrency = normalizeConcurrency(options.concurrency);
|
|
29
43
|
const shouldMergeCsv = options.merge ?? true;
|
|
30
44
|
await validateOutputDirectory(options.output_dir);
|
|
31
|
-
const plan = await planHistoryRequests(options,
|
|
45
|
+
const plan = await planHistoryRequests(options, {
|
|
46
|
+
request: (method, route, params) => deps.request(method, route, params, { signal: deps.signal }),
|
|
47
|
+
});
|
|
48
|
+
deps.signal?.throwIfAborted();
|
|
32
49
|
const mergeCsvFilesByRequestIndex = new Map();
|
|
33
50
|
const createdCsvFiles = new Set();
|
|
34
51
|
const result = {
|
|
@@ -45,7 +62,7 @@ export async function downloadHistory(options, deps) {
|
|
|
45
62
|
let nextIndex = 0;
|
|
46
63
|
let terminalError = null;
|
|
47
64
|
async function worker() {
|
|
48
|
-
while (!
|
|
65
|
+
while (!controller.signal.aborted && nextIndex < plan.requests.length) {
|
|
49
66
|
const requestIndex = nextIndex;
|
|
50
67
|
const request = plan.requests[requestIndex];
|
|
51
68
|
nextIndex += 1;
|
|
@@ -53,6 +70,7 @@ export async function downloadHistory(options, deps) {
|
|
|
53
70
|
const startDate = String(request.params.start_date ?? `${options.from}_${options.to}`);
|
|
54
71
|
const symbol = String(request.params.symbol);
|
|
55
72
|
try {
|
|
73
|
+
controller.signal.throwIfAborted();
|
|
56
74
|
const existingTargetFiles = !options.overwrite
|
|
57
75
|
? await findExistingTargetFiles(request.outputBasePath, format, request)
|
|
58
76
|
: null;
|
|
@@ -70,7 +88,8 @@ export async function downloadHistory(options, deps) {
|
|
|
70
88
|
});
|
|
71
89
|
continue;
|
|
72
90
|
}
|
|
73
|
-
const response = filterResponseForRequest(await deps.request("GET", request.method === "series" ? SERIES_PATH : HISTORY_PATH, request.params), options, request);
|
|
91
|
+
const response = filterResponseForRequest(await deps.request("GET", request.method === "series" ? SERIES_PATH : HISTORY_PATH, request.params, { signal: controller.signal }), options, request);
|
|
92
|
+
controller.signal.throwIfAborted();
|
|
74
93
|
const message = response?.message ?? response?.error;
|
|
75
94
|
if (message && !Array.isArray(response?.series)) {
|
|
76
95
|
result.skipped += 1;
|
|
@@ -103,6 +122,7 @@ export async function downloadHistory(options, deps) {
|
|
|
103
122
|
continue;
|
|
104
123
|
}
|
|
105
124
|
for (const filePath of targetFiles) {
|
|
125
|
+
controller.signal.throwIfAborted();
|
|
106
126
|
await removeOutputMarker(filePath);
|
|
107
127
|
let content;
|
|
108
128
|
if (filePath.endsWith(".csv")) {
|
|
@@ -133,8 +153,11 @@ export async function downloadHistory(options, deps) {
|
|
|
133
153
|
});
|
|
134
154
|
}
|
|
135
155
|
catch (error) {
|
|
156
|
+
if (controller.signal.aborted)
|
|
157
|
+
return;
|
|
136
158
|
if (isTerminalApiError(error)) {
|
|
137
159
|
terminalError = error;
|
|
160
|
+
controller.abort(error);
|
|
138
161
|
nextIndex = plan.requests.length;
|
|
139
162
|
throw error;
|
|
140
163
|
}
|
|
@@ -154,19 +177,26 @@ export async function downloadHistory(options, deps) {
|
|
|
154
177
|
}
|
|
155
178
|
}
|
|
156
179
|
}
|
|
157
|
-
await Promise.
|
|
158
|
-
if (
|
|
180
|
+
const workers = await Promise.allSettled(Array.from({ length: Math.min(concurrency, plan.requests.length) }, () => worker()));
|
|
181
|
+
if (terminalError)
|
|
182
|
+
throw terminalError;
|
|
183
|
+
for (const worker of workers)
|
|
184
|
+
if (worker.status === "rejected")
|
|
185
|
+
throw worker.reason;
|
|
186
|
+
controller.signal.throwIfAborted();
|
|
187
|
+
if (result.failed === 0 && shouldMergeCsv && (format === "csv" || format === "both")) {
|
|
159
188
|
const orderedCsvFiles = plan.requests.flatMap((_request, index) => mergeCsvFilesByRequestIndex.get(index) ?? []);
|
|
160
189
|
if (orderedCsvFiles.length > 0) {
|
|
161
190
|
const mergedFile = mergedCsvPath(options, path.basename(path.dirname(orderedCsvFiles[0])));
|
|
162
|
-
await mergeCsvFiles(orderedCsvFiles, mergedFile);
|
|
191
|
+
await mergeCsvFiles(orderedCsvFiles, mergedFile, controller.signal);
|
|
163
192
|
await removeLegacyMergedMarker(mergedFile);
|
|
164
193
|
result.merged_file = mergedFile;
|
|
165
194
|
result.files.push(mergedFile);
|
|
166
195
|
if (!options.keep_chunks) {
|
|
167
196
|
const removableCsvFiles = await filterRemovableChunkFiles(orderedCsvFiles, createdCsvFiles);
|
|
168
197
|
await removeChunkFilesAndMarkers(removableCsvFiles, options.output_dir);
|
|
169
|
-
|
|
198
|
+
const removedFiles = new Set(removableCsvFiles);
|
|
199
|
+
result.files = result.files.filter((filePath) => !removedFiles.has(filePath));
|
|
170
200
|
}
|
|
171
201
|
}
|
|
172
202
|
}
|
|
@@ -251,6 +281,8 @@ function planRequestsForSymbol(options, symbol, outputSymbol = symbol) {
|
|
|
251
281
|
async function planFuturesHistoryRequests(options, request) {
|
|
252
282
|
const schedule = await fetchContractSchedule(options.symbol, request);
|
|
253
283
|
const range = monthRangeFor(options.from, options.to);
|
|
284
|
+
const firstDay = formatDay(parseDay(options.from, "start"));
|
|
285
|
+
const lastDay = formatDay(parseDay(options.to, "end"));
|
|
254
286
|
const lookback = options.contract_lookback_months ?? DEFAULT_CONTRACT_LOOKBACK_MONTHS;
|
|
255
287
|
const requests = [];
|
|
256
288
|
for (let year = range.start.year; year <= range.end.year + 1; year += 1) {
|
|
@@ -267,6 +299,8 @@ async function planFuturesHistoryRequests(options, request) {
|
|
|
267
299
|
const symbol = `${schedule.fullBaseCode}${monthCode}${year}`;
|
|
268
300
|
const startDate = formatMonth(month);
|
|
269
301
|
for (const archiveDate of expandArchiveStartDates(startDate, options.bar_type)) {
|
|
302
|
+
if (options.bar_type === "second" && (archiveDate < firstDay || archiveDate > lastDay))
|
|
303
|
+
continue;
|
|
270
304
|
requests.push({
|
|
271
305
|
method: "history",
|
|
272
306
|
params: {
|
|
@@ -336,6 +370,9 @@ function validateOptions(options) {
|
|
|
336
370
|
if (![...HISTORY_BAR_TYPES, ...SERIES_BAR_TYPES].includes(options.bar_type)) {
|
|
337
371
|
throw new Error("bar_type must be second, minute, hour, day, week, or month");
|
|
338
372
|
}
|
|
373
|
+
if (compareDays(parseDay(options.from, "start"), parseDay(options.to, "end")) > 0) {
|
|
374
|
+
throw new Error("from must be before or equal to to");
|
|
375
|
+
}
|
|
339
376
|
normalizeConcurrency(options.concurrency);
|
|
340
377
|
if (options.format && !["json", "csv", "both"].includes(options.format)) {
|
|
341
378
|
throw new Error("format must be json, csv, or both");
|
|
@@ -508,47 +545,6 @@ function outputBasePathForResponse(request, response) {
|
|
|
508
545
|
function mergedCsvPath(options, barTypeLabel) {
|
|
509
546
|
return path.join(path.resolve(options.output_dir), sanitizePathPart(options.symbol), sanitizePathPart(barTypeLabel), "merged.csv");
|
|
510
547
|
}
|
|
511
|
-
async function mergeCsvFiles(files, outputPath) {
|
|
512
|
-
const headers = [];
|
|
513
|
-
const headerSet = new Set();
|
|
514
|
-
const rowsByKey = new Map();
|
|
515
|
-
for (const file of files) {
|
|
516
|
-
const content = await readFile(file, "utf8");
|
|
517
|
-
if (!content.trim())
|
|
518
|
-
continue;
|
|
519
|
-
const lines = content.split(/\r?\n/).filter((line) => line.length > 0);
|
|
520
|
-
if (lines.length === 0)
|
|
521
|
-
continue;
|
|
522
|
-
const columns = parseCsvLine(lines[0]);
|
|
523
|
-
const codeIndex = columns.indexOf("code");
|
|
524
|
-
const barTypeIndex = columns.indexOf("bar_type");
|
|
525
|
-
const timeIndex = columns.indexOf("time");
|
|
526
|
-
if (codeIndex === -1 || barTypeIndex === -1 || timeIndex === -1) {
|
|
527
|
-
throw new Error("Merged CSV requires code, bar_type, and time columns for deduplication");
|
|
528
|
-
}
|
|
529
|
-
for (const column of columns) {
|
|
530
|
-
if (!headerSet.has(column)) {
|
|
531
|
-
headerSet.add(column);
|
|
532
|
-
headers.push(column);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
for (const line of lines.slice(1)) {
|
|
536
|
-
const values = parseCsvLine(line);
|
|
537
|
-
const key = [codeIndex, barTypeIndex, timeIndex]
|
|
538
|
-
.map((index) => values[index] ?? "")
|
|
539
|
-
.join("\u0000");
|
|
540
|
-
const row = {};
|
|
541
|
-
for (let index = 0; index < columns.length; index += 1) {
|
|
542
|
-
row[columns[index]] = values[index] ?? "";
|
|
543
|
-
}
|
|
544
|
-
rowsByKey.set(key, row);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
const rows = Array.from(rowsByKey.values()).map((row) => headers.map((header) => row[header] ?? ""));
|
|
548
|
-
const content = headers.length > 0 ? serializeCsvRows(headers, rows) : "";
|
|
549
|
-
await writeFileAtomic(outputPath, content);
|
|
550
|
-
return content;
|
|
551
|
-
}
|
|
552
548
|
async function allFilesAreResumable(files, request) {
|
|
553
549
|
for (const file of files) {
|
|
554
550
|
if (!(await isExistingOutputFileResumable(file, request))) {
|
|
@@ -576,21 +572,22 @@ async function isExistingOutputFileResumable(file, request) {
|
|
|
576
572
|
}
|
|
577
573
|
}
|
|
578
574
|
function validateCsvContent(content) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
if (!columns.includes(column)) {
|
|
588
|
-
throw new Error(`CSV output is missing ${column} column`);
|
|
575
|
+
const parser = new CsvParser();
|
|
576
|
+
let rows = 0;
|
|
577
|
+
const validate = (row) => {
|
|
578
|
+
if (rows++ === 0) {
|
|
579
|
+
for (const column of ["code", "bar_type", "time"]) {
|
|
580
|
+
if (!row.includes(column))
|
|
581
|
+
throw new Error(`CSV output is missing ${column} column`);
|
|
582
|
+
}
|
|
589
583
|
}
|
|
590
|
-
}
|
|
591
|
-
for (const
|
|
592
|
-
|
|
593
|
-
|
|
584
|
+
};
|
|
585
|
+
for (const row of parser.push(content))
|
|
586
|
+
validate(row);
|
|
587
|
+
for (const row of parser.finish())
|
|
588
|
+
validate(row);
|
|
589
|
+
if (rows < 2)
|
|
590
|
+
throw new Error("CSV output has no data rows");
|
|
594
591
|
}
|
|
595
592
|
function validateJsonContent(content) {
|
|
596
593
|
if (!content.trim())
|
|
@@ -772,44 +769,6 @@ function serializeCsvRows(headers, rows) {
|
|
|
772
769
|
}
|
|
773
770
|
return `${lines.join("\n")}\n`;
|
|
774
771
|
}
|
|
775
|
-
function escapeCsvCell(value) {
|
|
776
|
-
if (value === undefined || value === null)
|
|
777
|
-
return "";
|
|
778
|
-
const text = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
779
|
-
if (/[",\n\r]/.test(text)) {
|
|
780
|
-
return `"${text.replace(/"/g, '""')}"`;
|
|
781
|
-
}
|
|
782
|
-
return text;
|
|
783
|
-
}
|
|
784
|
-
function parseCsvLine(line) {
|
|
785
|
-
const cells = [];
|
|
786
|
-
let current = "";
|
|
787
|
-
let inQuotes = false;
|
|
788
|
-
for (let index = 0; index < line.length; index += 1) {
|
|
789
|
-
const char = line[index];
|
|
790
|
-
if (char === '"') {
|
|
791
|
-
if (inQuotes && line[index + 1] === '"') {
|
|
792
|
-
current += '"';
|
|
793
|
-
index += 1;
|
|
794
|
-
}
|
|
795
|
-
else {
|
|
796
|
-
inQuotes = !inQuotes;
|
|
797
|
-
}
|
|
798
|
-
continue;
|
|
799
|
-
}
|
|
800
|
-
if (char === "," && !inQuotes) {
|
|
801
|
-
cells.push(current);
|
|
802
|
-
current = "";
|
|
803
|
-
continue;
|
|
804
|
-
}
|
|
805
|
-
current += char;
|
|
806
|
-
}
|
|
807
|
-
if (inQuotes) {
|
|
808
|
-
throw new Error("CSV line has an unterminated quoted field");
|
|
809
|
-
}
|
|
810
|
-
cells.push(current);
|
|
811
|
-
return cells;
|
|
812
|
-
}
|
|
813
772
|
function parseSettlementMonth(value) {
|
|
814
773
|
const match = /^\d{4}(?<month>\d{2})\d{2}$/.exec(value);
|
|
815
774
|
if (!match?.groups)
|