tradeblocks-mcp 3.0.2 → 3.0.3
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 +82 -68
- package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
- package/dist/chunk-27S67XW3.js.map +1 -0
- package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
- package/dist/chunk-2E63THNI.js.map +1 -0
- package/dist/{chunk-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
- package/dist/chunk-6S37CXUA.js.map +1 -0
- package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
- package/dist/chunk-AP7IHUUR.js.map +1 -0
- package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
- package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
- package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
- package/dist/chunk-SEUZYQGQ.js.map +1 -0
- package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
- package/dist/chunk-UBUC5A66.js.map +1 -0
- package/dist/{chunk-XXYOUIZY.js → chunk-VDU25Z6X.js} +63 -80
- package/dist/chunk-VDU25Z6X.js.map +1 -0
- package/dist/daily-log-processor-BY3ISY6K.js +9 -0
- package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
- package/dist/iv-solver-worker.js +9 -1
- package/dist/market-provider-H3ARIVZ4.js +16 -0
- package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
- package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
- package/dist/test-exports.js +358 -254
- package/dist/test-exports.js.map +1 -1
- package/dist/trade-processor-EYA3I3XB.js +9 -0
- package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
- package/manifest.json +3 -11
- package/package.json +1 -1
- package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
- package/server/chunk-4P7D7YZP.js.map +1 -0
- package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
- package/server/chunk-BOPHW5M6.js.map +1 -0
- package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
- package/server/chunk-GH2552SE.js.map +1 -0
- package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
- package/server/chunk-OBYKFW2B.js.map +1 -0
- package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
- package/server/chunk-T66KH2XH.js.map +1 -0
- package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
- package/server/chunk-W5E7FHC4.js.map +1 -0
- package/server/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
- package/server/chunk-YUCOAJ4Z.js.map +1 -0
- package/server/{chunk-PNKG7RY7.js → chunk-ZBJCF4ZG.js} +63 -80
- package/server/chunk-ZBJCF4ZG.js.map +1 -0
- package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
- package/server/config-DK7KOMNL.js.map +1 -0
- package/server/daily-log-processor-ENEUT22A.js +10 -0
- package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
- package/server/http-server.js +2 -5
- package/server/index.js +1771 -1538
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +9 -1
- package/server/market-provider-B437HKLW.js +17 -0
- package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
- package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
- package/server/trade-processor-L3PIQ5TG.js +10 -0
- package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
- package/src/auth/clients-store.ts +4 -4
- package/src/auth/code-store.ts +1 -1
- package/src/auth/config.ts +11 -12
- package/src/auth/login-page.ts +8 -10
- package/src/auth/provider.ts +35 -30
- package/src/auth/token.ts +17 -15
- package/src/db/backtest-schemas.ts +12 -4
- package/src/db/connection.ts +107 -40
- package/src/db/index.ts +37 -5
- package/src/db/json-adapters.ts +22 -31
- package/src/db/json-migration.ts +11 -15
- package/src/db/json-store.ts +5 -3
- package/src/db/market-datasets.ts +48 -15
- package/src/db/market-schemas.ts +11 -5
- package/src/db/market-views.ts +119 -32
- package/src/db/parquet-writer.ts +7 -7
- package/src/db/profile-schemas.ts +39 -22
- package/src/db/schemas.ts +2 -2
- package/src/http-server.ts +7 -12
- package/src/index.ts +8 -9
- package/src/market/ingestor/index.ts +1 -4
- package/src/market/ingestor/market-ingestor.ts +140 -67
- package/src/market/ingestor/types.ts +1 -3
- package/src/market/stores/chain-sql.ts +2 -8
- package/src/market/stores/chain-store.ts +1 -4
- package/src/market/stores/duckdb-chain-store.ts +3 -14
- package/src/market/stores/duckdb-enriched-store.ts +3 -13
- package/src/market/stores/duckdb-quote-store.ts +10 -27
- package/src/market/stores/duckdb-spot-store.ts +4 -20
- package/src/market/stores/enriched-sql.ts +3 -9
- package/src/market/stores/enriched-store.ts +2 -2
- package/src/market/stores/parquet-chain-store.ts +5 -22
- package/src/market/stores/parquet-enriched-store.ts +4 -16
- package/src/market/stores/parquet-oi-daily-store.ts +3 -14
- package/src/market/stores/parquet-quote-store.ts +9 -29
- package/src/market/stores/parquet-spot-store.ts +14 -33
- package/src/market/stores/quote-store.ts +2 -10
- package/src/market/stores/spot-sql.ts +3 -15
- package/src/market/stores/spot-store.ts +1 -1
- package/src/market/tickers/defaults.json +6 -6
- package/src/market/tickers/loader.ts +2 -7
- package/src/market/tickers/registry.ts +5 -15
- package/src/market/tickers/schemas.ts +2 -6
- package/src/models/strategy-profile.ts +39 -39
- package/src/sync/block-sync.ts +44 -65
- package/src/sync/index.ts +1 -4
- package/src/sync/metadata.ts +14 -20
- package/src/test-exports.ts +127 -123
- package/src/tools/analysis.ts +180 -159
- package/src/tools/batch-exit-analysis.ts +100 -59
- package/src/tools/blocks/analysis.ts +116 -101
- package/src/tools/blocks/comparison.ts +281 -364
- package/src/tools/blocks/core.ts +208 -253
- package/src/tools/blocks/health.ts +571 -563
- package/src/tools/blocks/similarity.ts +316 -283
- package/src/tools/edge-decay.ts +124 -197
- package/src/tools/exit-analysis.ts +130 -77
- package/src/tools/greeks-attribution.ts +84 -35
- package/src/tools/guides.ts +4 -6
- package/src/tools/imports.ts +9 -12
- package/src/tools/market-data.ts +1067 -757
- package/src/tools/market-enrichment.ts +3 -3
- package/src/tools/market-fetch.ts +148 -67
- package/src/tools/market-imports.ts +12 -12
- package/src/tools/middleware/sync-middleware.ts +5 -6
- package/src/tools/performance.ts +185 -302
- package/src/tools/profile-analysis.ts +52 -66
- package/src/tools/profiles.ts +106 -69
- package/src/tools/regime-advisor.ts +20 -45
- package/src/tools/replay.ts +81 -77
- package/src/tools/reports/discrepancies.ts +298 -328
- package/src/tools/reports/fields.ts +7 -25
- package/src/tools/reports/helpers.ts +18 -49
- package/src/tools/reports/predictive.ts +27 -70
- package/src/tools/reports/slippage-trends.ts +315 -345
- package/src/tools/reports/slippage.ts +1 -4
- package/src/tools/reports/strategy-matches.ts +399 -441
- package/src/tools/schema.ts +43 -40
- package/src/tools/shared/filters.ts +3 -9
- package/src/tools/snapshot.ts +9 -30
- package/src/tools/sql.ts +15 -14
- package/src/tools/tickers.ts +1 -4
- package/src/utils/batch-exit-analysis.ts +31 -42
- package/src/utils/black-scholes.ts +39 -29
- package/src/utils/block-loader.ts +68 -82
- package/src/utils/calibration-probe.ts +1 -2
- package/src/utils/chain-loader.ts +3 -3
- package/src/utils/csv-discovery.ts +16 -22
- package/src/utils/data-quality.ts +24 -36
- package/src/utils/exit-triggers.ts +91 -96
- package/src/utils/field-timing.ts +94 -79
- package/src/utils/filter-predicates.ts +13 -9
- package/src/utils/flatfile-importer.ts +94 -64
- package/src/utils/greeks-decomposition.ts +152 -100
- package/src/utils/iv-solver-pool.ts +55 -25
- package/src/utils/iv-solver-worker.ts +5 -5
- package/src/utils/market-enricher.ts +528 -497
- package/src/utils/market-importer.ts +31 -12
- package/src/utils/market-provider.ts +21 -23
- package/src/utils/massive-tier.ts +5 -7
- package/src/utils/migrate-option-data-helpers.ts +2 -8
- package/src/utils/option-quote-greeks.ts +25 -31
- package/src/utils/option-time.ts +4 -8
- package/src/utils/output-formatter.ts +1 -4
- package/src/utils/provider-capabilities.ts +1 -4
- package/src/utils/providers/massive.ts +59 -93
- package/src/utils/providers/thetadata/backfill.ts +14 -23
- package/src/utils/providers/thetadata/client.ts +12 -8
- package/src/utils/providers/thetadata/decode.ts +2 -20
- package/src/utils/providers/thetadata/endpoints.ts +32 -40
- package/src/utils/providers/thetadata/join.ts +11 -10
- package/src/utils/providers/thetadata/proto.ts +12 -10
- package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
- package/src/utils/providers/thetadata.ts +11 -10
- package/src/utils/quote-enricher.ts +4 -4
- package/src/utils/quote-parquet-projection.ts +3 -11
- package/src/utils/sample-date-selector.ts +3 -5
- package/src/utils/schema-metadata.ts +102 -70
- package/src/utils/ticker.ts +5 -9
- package/src/utils/trade-replay.ts +77 -68
- package/dist/chunk-4BLCXNQ6.js.map +0 -1
- package/dist/chunk-BKQ4PM4Y.js.map +0 -1
- package/dist/chunk-FGZH632F.js.map +0 -1
- package/dist/chunk-LDKTV7GW.js.map +0 -1
- package/dist/chunk-PRAYH3RT.js.map +0 -1
- package/dist/chunk-W2PP3LEH.js.map +0 -1
- package/dist/chunk-XXYOUIZY.js.map +0 -1
- package/dist/daily-log-processor-6MWJ23JK.js +0 -9
- package/dist/market-provider-VDRJUEF2.js +0 -16
- package/dist/trade-processor-NHU4VWRX.js +0 -9
- package/server/chunk-5EBXHT6C.js.map +0 -1
- package/server/chunk-72GKJE2U.js.map +0 -1
- package/server/chunk-CSDVJPBB.js.map +0 -1
- package/server/chunk-FBNDMCT5.js.map +0 -1
- package/server/chunk-NRFXAJF7.js.map +0 -1
- package/server/chunk-NZO6PT64.js.map +0 -1
- package/server/chunk-PNKG7RY7.js.map +0 -1
- package/server/chunk-WA5AAPCH.js.map +0 -1
- package/server/config-6IZXEFEX.js.map +0 -1
- package/server/daily-log-processor-Y3PVSVBM.js +0 -10
- package/server/market-provider-VOYYVYWT.js +0 -17
- package/server/trade-processor-JWVS37KM.js +0 -10
- /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-BY3ISY6K.js.map} +0 -0
- /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
- /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-H3ARIVZ4.js.map} +0 -0
- /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
- /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
- /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.js.map} +0 -0
- /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
- /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-ENEUT22A.js.map} +0 -0
- /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
- /package/server/{market-provider-VOYYVYWT.js.map → market-provider-B437HKLW.js.map} +0 -0
- /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
- /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
- /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.js.map} +0 -0
- /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
|
@@ -37,8 +37,8 @@ const TMP_DIR = "/tmp/massive-flat";
|
|
|
37
37
|
|
|
38
38
|
export interface ParsedBar {
|
|
39
39
|
ticker: string; // O: prefix stripped
|
|
40
|
-
date: string;
|
|
41
|
-
time: string;
|
|
40
|
+
date: string; // YYYY-MM-DD in ET
|
|
41
|
+
time: string; // HH:MM in ET
|
|
42
42
|
open: number;
|
|
43
43
|
high: number;
|
|
44
44
|
low: number;
|
|
@@ -70,10 +70,10 @@ export interface ImportFlatFilesResult {
|
|
|
70
70
|
* We use a fast approximation: March 8-31 through November 1 = EDT.
|
|
71
71
|
*/
|
|
72
72
|
function isEDT(utcMonth: number, utcDay: number): boolean {
|
|
73
|
-
if (utcMonth > 3 && utcMonth < 11) return true;
|
|
74
|
-
if (utcMonth === 3) return utcDay >= 8;
|
|
75
|
-
if (utcMonth === 11) return utcDay < 7;
|
|
76
|
-
return false;
|
|
73
|
+
if (utcMonth > 3 && utcMonth < 11) return true; // Apr-Oct always EDT
|
|
74
|
+
if (utcMonth === 3) return utcDay >= 8; // March: after ~2nd Sunday
|
|
75
|
+
if (utcMonth === 11) return utcDay < 7; // Nov: before ~1st Sunday
|
|
76
|
+
return false; // Dec-Feb always EST
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
@@ -115,7 +115,7 @@ export function parseFlatFileLine(line: string, underlyingPrefix: string): Parse
|
|
|
115
115
|
|
|
116
116
|
const rawTicker = parts[0];
|
|
117
117
|
// Index tickers: "I:VIX" → "VIX", option tickers: "O:SPXW..." → "SPXW..."
|
|
118
|
-
const ticker = rawTicker.includes(
|
|
118
|
+
const ticker = rawTicker.includes(":") ? rawTicker.slice(rawTicker.indexOf(":") + 1) : rawTicker;
|
|
119
119
|
|
|
120
120
|
let open: number, close: number, high: number, low: number, windowStart: string, volume: number;
|
|
121
121
|
|
|
@@ -178,10 +178,7 @@ export function tradingDays(from: string, to: string): string[] {
|
|
|
178
178
|
* one writeBars call per (ticker, date) so we group first, then write
|
|
179
179
|
* serially — DuckDB is single-writer (Pitfall 9).
|
|
180
180
|
*/
|
|
181
|
-
async function writeRowsThroughStore(
|
|
182
|
-
stores: MarketStores,
|
|
183
|
-
rows: ParsedBar[],
|
|
184
|
-
): Promise<void> {
|
|
181
|
+
async function writeRowsThroughStore(stores: MarketStores, rows: ParsedBar[]): Promise<void> {
|
|
185
182
|
if (rows.length === 0) return;
|
|
186
183
|
|
|
187
184
|
const byTickerDate = new Map<string, Map<string, MarketStoreBarRow[]>>();
|
|
@@ -236,17 +233,17 @@ export async function importFlatFileDay(
|
|
|
236
233
|
dateStr: string,
|
|
237
234
|
underlying: string,
|
|
238
235
|
stores: MarketStores,
|
|
239
|
-
assetClass:
|
|
236
|
+
assetClass: "option" | "index" = "option",
|
|
240
237
|
): Promise<ImportDayResult> {
|
|
241
238
|
// Index flat files go to a separate tmp path to avoid colliding with option files
|
|
242
|
-
const tmpSubdir = assetClass ===
|
|
239
|
+
const tmpSubdir = assetClass === "index" ? "/tmp/massive-flat-index" : TMP_DIR;
|
|
243
240
|
const localPath = resolve(tmpSubdir, `${dateStr}.csv.gz`);
|
|
244
241
|
|
|
245
242
|
// Skip-check via the spot store. For options the underlying itself rarely
|
|
246
243
|
// has spot bars (those go through a separate index import), so we use
|
|
247
244
|
// store coverage as a best-effort signal — duplicate ingest is idempotent
|
|
248
245
|
// at the store layer (writeBars overwrites the partition).
|
|
249
|
-
if (assetClass ===
|
|
246
|
+
if (assetClass === "index") {
|
|
250
247
|
const cov = await stores.spot.getCoverage(underlying, dateStr, dateStr);
|
|
251
248
|
if (cov.totalDates > 0) {
|
|
252
249
|
return { date: dateStr, imported: 0, skipped: true };
|
|
@@ -256,15 +253,15 @@ export async function importFlatFileDay(
|
|
|
256
253
|
// Download via provider (provider-agnostic)
|
|
257
254
|
if (!existsSync(localPath)) {
|
|
258
255
|
mkdirSync(tmpSubdir, { recursive: true });
|
|
259
|
-
const { getProvider } = await import(
|
|
256
|
+
const { getProvider } = await import("./market-provider.ts");
|
|
260
257
|
const provider = getProvider();
|
|
261
258
|
if (provider.downloadFlatFile) {
|
|
262
259
|
const downloaded = await provider.downloadFlatFile(dateStr, assetClass);
|
|
263
260
|
if (!downloaded) {
|
|
264
|
-
return { date: dateStr, imported: 0, skipped: false, error:
|
|
261
|
+
return { date: dateStr, imported: 0, skipped: false, error: "download_failed" };
|
|
265
262
|
}
|
|
266
263
|
} else {
|
|
267
|
-
return { date: dateStr, imported: 0, skipped: false, error:
|
|
264
|
+
return { date: dateStr, imported: 0, skipped: false, error: "provider_no_flat_files" };
|
|
268
265
|
}
|
|
269
266
|
}
|
|
270
267
|
|
|
@@ -274,7 +271,8 @@ export async function importFlatFileDay(
|
|
|
274
271
|
|
|
275
272
|
// Stream-parse: filter to underlying tickers, convert timestamps, collect rows
|
|
276
273
|
// Index tickers use "I:" prefix, option tickers use "O:" prefix
|
|
277
|
-
const tickerPrefix =
|
|
274
|
+
const tickerPrefix =
|
|
275
|
+
assetClass === "index" ? `I:${underlying}` : underlying === "SPX" ? "O:SPX" : `O:${underlying}`;
|
|
278
276
|
|
|
279
277
|
const rows: ParsedBar[] = [];
|
|
280
278
|
await new Promise<void>((resolveP, reject) => {
|
|
@@ -318,39 +316,43 @@ export async function importFlatFileDay(
|
|
|
318
316
|
async function downloadAndParse(
|
|
319
317
|
dateStr: string,
|
|
320
318
|
underlying: string,
|
|
321
|
-
assetClass:
|
|
319
|
+
assetClass: "option" | "index",
|
|
322
320
|
): Promise<{ date: string; rows: ParsedBar[]; skipped?: boolean; error?: string }> {
|
|
323
|
-
const tmpSubdir = assetClass ===
|
|
321
|
+
const tmpSubdir = assetClass === "index" ? "/tmp/massive-flat-index" : TMP_DIR;
|
|
324
322
|
const localPath = resolve(tmpSubdir, `${dateStr}.csv.gz`);
|
|
325
323
|
|
|
326
324
|
// Download via provider
|
|
327
325
|
if (!existsSync(localPath)) {
|
|
328
326
|
mkdirSync(tmpSubdir, { recursive: true });
|
|
329
|
-
const { getProvider } = await import(
|
|
327
|
+
const { getProvider } = await import("./market-provider.ts");
|
|
330
328
|
const provider = getProvider();
|
|
331
329
|
if (provider.downloadFlatFile) {
|
|
332
330
|
const downloaded = await provider.downloadFlatFile(dateStr, assetClass);
|
|
333
331
|
if (!downloaded) {
|
|
334
|
-
return { date: dateStr, rows: [], error:
|
|
332
|
+
return { date: dateStr, rows: [], error: "download_failed" };
|
|
335
333
|
}
|
|
336
334
|
} else {
|
|
337
|
-
return { date: dateStr, rows: [], error:
|
|
335
|
+
return { date: dateStr, rows: [], error: "provider_no_flat_files" };
|
|
338
336
|
}
|
|
339
337
|
}
|
|
340
338
|
|
|
341
339
|
if (!existsSync(localPath)) {
|
|
342
|
-
return { date: dateStr, rows: [], error:
|
|
340
|
+
return { date: dateStr, rows: [], error: "not_found" };
|
|
343
341
|
}
|
|
344
342
|
|
|
345
343
|
// Stream-parse: filter to underlying tickers, convert timestamps
|
|
346
|
-
const tickerPrefix =
|
|
344
|
+
const tickerPrefix =
|
|
345
|
+
assetClass === "index" ? `I:${underlying}` : underlying === "SPX" ? "O:SPX" : `O:${underlying}`;
|
|
347
346
|
const rows: ParsedBar[] = [];
|
|
348
347
|
await new Promise<void>((resolveP, reject) => {
|
|
349
348
|
const gunzip = createGunzip();
|
|
350
349
|
const rl = createInterface({ input: createReadStream(localPath).pipe(gunzip) });
|
|
351
350
|
let isHeader = true;
|
|
352
351
|
rl.on("line", (line: string) => {
|
|
353
|
-
if (isHeader) {
|
|
352
|
+
if (isHeader) {
|
|
353
|
+
isHeader = false;
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
354
356
|
const parsed = parseFlatFileLine(line, tickerPrefix);
|
|
355
357
|
if (parsed) rows.push(parsed);
|
|
356
358
|
});
|
|
@@ -360,7 +362,11 @@ async function downloadAndParse(
|
|
|
360
362
|
});
|
|
361
363
|
|
|
362
364
|
// Clean up downloaded file
|
|
363
|
-
try {
|
|
365
|
+
try {
|
|
366
|
+
unlinkSync(localPath);
|
|
367
|
+
} catch {
|
|
368
|
+
/* best-effort */
|
|
369
|
+
}
|
|
364
370
|
|
|
365
371
|
return { date: dateStr, rows };
|
|
366
372
|
}
|
|
@@ -383,9 +389,9 @@ export async function importFlatFiles(
|
|
|
383
389
|
underlying: string,
|
|
384
390
|
stores: MarketStores,
|
|
385
391
|
dataDir: string,
|
|
386
|
-
assetClass:
|
|
392
|
+
assetClass: "option" | "index" = "option",
|
|
387
393
|
): Promise<ImportFlatFilesResult> {
|
|
388
|
-
const tmpDir = assetClass ===
|
|
394
|
+
const tmpDir = assetClass === "index" ? "/tmp/massive-flat-index" : TMP_DIR;
|
|
389
395
|
mkdirSync(tmpDir, { recursive: true });
|
|
390
396
|
|
|
391
397
|
const days = tradingDays(from, to);
|
|
@@ -399,8 +405,8 @@ export async function importFlatFiles(
|
|
|
399
405
|
// from per-ticker API fetches.
|
|
400
406
|
const importedDates = await getFlatImportLogJson(assetClass, underlying, from, to, dataDir);
|
|
401
407
|
|
|
402
|
-
const daysToImport = days.filter(d => !importedDates.has(d));
|
|
403
|
-
const skippedDays = days.filter(d => importedDates.has(d));
|
|
408
|
+
const daysToImport = days.filter((d) => !importedDates.has(d));
|
|
409
|
+
const skippedDays = days.filter((d) => importedDates.has(d));
|
|
404
410
|
for (const d of skippedDays) {
|
|
405
411
|
results.push({ date: d, imported: 0, skipped: true });
|
|
406
412
|
totalSkipped++;
|
|
@@ -409,7 +415,9 @@ export async function importFlatFiles(
|
|
|
409
415
|
if (daysToImport.length === 0) {
|
|
410
416
|
console.log(` [importFlatFiles] all ${days.length} days already imported — nothing to do`);
|
|
411
417
|
} else {
|
|
412
|
-
console.log(
|
|
418
|
+
console.log(
|
|
419
|
+
` [importFlatFiles] ${daysToImport.length} days to import, ${skippedDays.length} already imported (${assetClass} ${underlying})`,
|
|
420
|
+
);
|
|
413
421
|
}
|
|
414
422
|
|
|
415
423
|
// Process in batches: parallel download+parse, serial spot-store writes
|
|
@@ -417,11 +425,13 @@ export async function importFlatFiles(
|
|
|
417
425
|
const batch = daysToImport.slice(i, i + IMPORT_CONCURRENCY);
|
|
418
426
|
const batchNum = Math.floor(i / IMPORT_CONCURRENCY) + 1;
|
|
419
427
|
const totalBatches = Math.ceil(daysToImport.length / IMPORT_CONCURRENCY);
|
|
420
|
-
console.log(
|
|
428
|
+
console.log(
|
|
429
|
+
` [importFlatFiles] batch ${batchNum}/${totalBatches}: ${batch[0]}..${batch[batch.length - 1]} (${Math.round((Date.now() - t0) / 1000)}s)`,
|
|
430
|
+
);
|
|
421
431
|
|
|
422
432
|
// Parallel download + parse
|
|
423
433
|
const parsed = await Promise.all(
|
|
424
|
-
batch.map(day => downloadAndParse(day, underlying, assetClass))
|
|
434
|
+
batch.map((day) => downloadAndParse(day, underlying, assetClass)),
|
|
425
435
|
);
|
|
426
436
|
|
|
427
437
|
// Serial writes through the spot store (DuckDB single-writer)
|
|
@@ -439,17 +449,24 @@ export async function importFlatFiles(
|
|
|
439
449
|
totalImported += p.rows.length;
|
|
440
450
|
// Record successful import in JSON metadata log
|
|
441
451
|
try {
|
|
442
|
-
await upsertFlatImportLogJson(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
452
|
+
await upsertFlatImportLogJson(
|
|
453
|
+
{
|
|
454
|
+
date: p.date,
|
|
455
|
+
asset_class: assetClass,
|
|
456
|
+
underlying,
|
|
457
|
+
imported_at: new Date().toISOString(),
|
|
458
|
+
bar_count: p.rows.length,
|
|
459
|
+
},
|
|
460
|
+
dataDir,
|
|
461
|
+
);
|
|
462
|
+
} catch {
|
|
463
|
+
/* best-effort metadata tracking */
|
|
464
|
+
}
|
|
450
465
|
}
|
|
451
466
|
}
|
|
452
|
-
console.log(
|
|
467
|
+
console.log(
|
|
468
|
+
` [importFlatFiles] done: ${totalImported} bars imported, ${totalSkipped} days skipped (${Math.round((Date.now() - t0) / 1000)}s)`,
|
|
469
|
+
);
|
|
453
470
|
|
|
454
471
|
return {
|
|
455
472
|
totalImported,
|
|
@@ -471,25 +488,25 @@ async function downloadAndParseMulti(
|
|
|
471
488
|
dateStr: string,
|
|
472
489
|
tickers: string[],
|
|
473
490
|
): Promise<{ date: string; rows: ParsedBar[]; error?: string }> {
|
|
474
|
-
const tmpDir =
|
|
491
|
+
const tmpDir = "/tmp/massive-flat-index";
|
|
475
492
|
const localPath = resolve(tmpDir, `${dateStr}.csv.gz`);
|
|
476
493
|
|
|
477
494
|
if (!existsSync(localPath)) {
|
|
478
495
|
mkdirSync(tmpDir, { recursive: true });
|
|
479
|
-
const { getProvider } = await import(
|
|
496
|
+
const { getProvider } = await import("./market-provider.ts");
|
|
480
497
|
const provider = getProvider();
|
|
481
498
|
if (provider.downloadFlatFile) {
|
|
482
|
-
const downloaded = await provider.downloadFlatFile(dateStr,
|
|
483
|
-
if (!downloaded) return { date: dateStr, rows: [], error:
|
|
499
|
+
const downloaded = await provider.downloadFlatFile(dateStr, "index");
|
|
500
|
+
if (!downloaded) return { date: dateStr, rows: [], error: "download_failed" };
|
|
484
501
|
} else {
|
|
485
|
-
return { date: dateStr, rows: [], error:
|
|
502
|
+
return { date: dateStr, rows: [], error: "provider_no_flat_files" };
|
|
486
503
|
}
|
|
487
504
|
}
|
|
488
505
|
|
|
489
|
-
if (!existsSync(localPath)) return { date: dateStr, rows: [], error:
|
|
506
|
+
if (!existsSync(localPath)) return { date: dateStr, rows: [], error: "not_found" };
|
|
490
507
|
|
|
491
508
|
// Build prefix set for fast matching: "I:VIX,", "I:VIX9D,", "I:SPX,"
|
|
492
|
-
const prefixes = tickers.map(t => `I:${t},`);
|
|
509
|
+
const prefixes = tickers.map((t) => `I:${t},`);
|
|
493
510
|
|
|
494
511
|
const rows: ParsedBar[] = [];
|
|
495
512
|
await new Promise<void>((resolveP, reject) => {
|
|
@@ -497,7 +514,10 @@ async function downloadAndParseMulti(
|
|
|
497
514
|
const rl = createInterface({ input: createReadStream(localPath).pipe(gunzip) });
|
|
498
515
|
let isHeader = true;
|
|
499
516
|
rl.on("line", (line: string) => {
|
|
500
|
-
if (isHeader) {
|
|
517
|
+
if (isHeader) {
|
|
518
|
+
isHeader = false;
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
501
521
|
for (const prefix of prefixes) {
|
|
502
522
|
if (line.startsWith(prefix)) {
|
|
503
523
|
const parsed = parseFlatFileLine(line, prefix.slice(0, -1)); // strip trailing comma
|
|
@@ -511,7 +531,11 @@ async function downloadAndParseMulti(
|
|
|
511
531
|
gunzip.on("error", reject);
|
|
512
532
|
});
|
|
513
533
|
|
|
514
|
-
try {
|
|
534
|
+
try {
|
|
535
|
+
unlinkSync(localPath);
|
|
536
|
+
} catch {
|
|
537
|
+
/* best-effort */
|
|
538
|
+
}
|
|
515
539
|
return { date: dateStr, rows };
|
|
516
540
|
}
|
|
517
541
|
|
|
@@ -529,7 +553,7 @@ export async function importIndexBars(
|
|
|
529
553
|
tickers: string[],
|
|
530
554
|
stores: MarketStores,
|
|
531
555
|
): Promise<ImportFlatFilesResult> {
|
|
532
|
-
mkdirSync(
|
|
556
|
+
mkdirSync("/tmp/massive-flat-index", { recursive: true });
|
|
533
557
|
|
|
534
558
|
const days = tradingDays(from, to);
|
|
535
559
|
const results: ImportDayResult[] = [];
|
|
@@ -554,8 +578,8 @@ export async function importIndexBars(
|
|
|
554
578
|
// For per-day skip we only need a contains check, not a precise
|
|
555
579
|
// missing-date list.
|
|
556
580
|
const dates = new Set<string>();
|
|
557
|
-
const start = new Date(cov.earliest +
|
|
558
|
-
const end = new Date(cov.latest +
|
|
581
|
+
const start = new Date(cov.earliest + "T00:00:00Z");
|
|
582
|
+
const end = new Date(cov.latest + "T00:00:00Z");
|
|
559
583
|
const cur = new Date(start);
|
|
560
584
|
while (cur <= end) {
|
|
561
585
|
dates.add(cur.toISOString().slice(0, 10));
|
|
@@ -580,16 +604,20 @@ export async function importIndexBars(
|
|
|
580
604
|
}
|
|
581
605
|
}
|
|
582
606
|
|
|
583
|
-
const daysToImport = days.filter(d => !skipDays.has(d));
|
|
584
|
-
for (const d of days.filter(d => skipDays.has(d))) {
|
|
607
|
+
const daysToImport = days.filter((d) => !skipDays.has(d));
|
|
608
|
+
for (const d of days.filter((d) => skipDays.has(d))) {
|
|
585
609
|
results.push({ date: d, imported: 0, skipped: true });
|
|
586
610
|
totalSkipped++;
|
|
587
611
|
}
|
|
588
612
|
|
|
589
613
|
if (daysToImport.length === 0) {
|
|
590
|
-
console.log(
|
|
614
|
+
console.log(
|
|
615
|
+
` [importIndexBars] all ${days.length} days have data for ${tickers.join(",")} — nothing to import`,
|
|
616
|
+
);
|
|
591
617
|
} else {
|
|
592
|
-
console.log(
|
|
618
|
+
console.log(
|
|
619
|
+
` [importIndexBars] ${daysToImport.length} days to import for ${tickers.join(",")}, ${skipDays.size} skipped`,
|
|
620
|
+
);
|
|
593
621
|
}
|
|
594
622
|
|
|
595
623
|
// Parallel download+parse, serial store writes
|
|
@@ -597,12 +625,12 @@ export async function importIndexBars(
|
|
|
597
625
|
const batch = daysToImport.slice(i, i + INDEX_CONCURRENCY);
|
|
598
626
|
const batchNum = Math.floor(i / INDEX_CONCURRENCY) + 1;
|
|
599
627
|
const totalBatches = Math.ceil(daysToImport.length / INDEX_CONCURRENCY);
|
|
600
|
-
console.log(
|
|
601
|
-
|
|
602
|
-
const parsed = await Promise.all(
|
|
603
|
-
batch.map(day => downloadAndParseMulti(day, tickers))
|
|
628
|
+
console.log(
|
|
629
|
+
` [importIndexBars] batch ${batchNum}/${totalBatches}: ${batch[0]}..${batch[batch.length - 1]} (${Math.round((Date.now() - t0) / 1000)}s)`,
|
|
604
630
|
);
|
|
605
631
|
|
|
632
|
+
const parsed = await Promise.all(batch.map((day) => downloadAndParseMulti(day, tickers)));
|
|
633
|
+
|
|
606
634
|
for (const p of parsed) {
|
|
607
635
|
if (p.error) {
|
|
608
636
|
results.push({ date: p.date, imported: 0, skipped: false, error: p.error });
|
|
@@ -617,7 +645,9 @@ export async function importIndexBars(
|
|
|
617
645
|
totalImported += p.rows.length;
|
|
618
646
|
}
|
|
619
647
|
}
|
|
620
|
-
console.log(
|
|
648
|
+
console.log(
|
|
649
|
+
` [importIndexBars] done: ${totalImported} bars imported, ${totalSkipped} days skipped (${Math.round((Date.now() - t0) / 1000)}s)`,
|
|
650
|
+
);
|
|
621
651
|
|
|
622
652
|
return {
|
|
623
653
|
totalImported,
|