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.
Files changed (214) hide show
  1. package/README.md +82 -68
  2. package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
  3. package/dist/chunk-27S67XW3.js.map +1 -0
  4. package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
  5. package/dist/chunk-2E63THNI.js.map +1 -0
  6. package/dist/{chunk-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
  7. package/dist/chunk-6S37CXUA.js.map +1 -0
  8. package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
  9. package/dist/chunk-AP7IHUUR.js.map +1 -0
  10. package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
  11. package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
  12. package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
  13. package/dist/chunk-SEUZYQGQ.js.map +1 -0
  14. package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
  15. package/dist/chunk-UBUC5A66.js.map +1 -0
  16. package/dist/{chunk-XXYOUIZY.js → chunk-VDU25Z6X.js} +63 -80
  17. package/dist/chunk-VDU25Z6X.js.map +1 -0
  18. package/dist/daily-log-processor-BY3ISY6K.js +9 -0
  19. package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
  20. package/dist/iv-solver-worker.js +9 -1
  21. package/dist/market-provider-H3ARIVZ4.js +16 -0
  22. package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
  23. package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
  24. package/dist/test-exports.js +358 -254
  25. package/dist/test-exports.js.map +1 -1
  26. package/dist/trade-processor-EYA3I3XB.js +9 -0
  27. package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
  28. package/manifest.json +3 -11
  29. package/package.json +1 -1
  30. package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
  31. package/server/chunk-4P7D7YZP.js.map +1 -0
  32. package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
  33. package/server/chunk-BOPHW5M6.js.map +1 -0
  34. package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
  35. package/server/chunk-GH2552SE.js.map +1 -0
  36. package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
  37. package/server/chunk-OBYKFW2B.js.map +1 -0
  38. package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
  39. package/server/chunk-T66KH2XH.js.map +1 -0
  40. package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
  41. package/server/chunk-W5E7FHC4.js.map +1 -0
  42. package/server/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
  43. package/server/chunk-YUCOAJ4Z.js.map +1 -0
  44. package/server/{chunk-PNKG7RY7.js → chunk-ZBJCF4ZG.js} +63 -80
  45. package/server/chunk-ZBJCF4ZG.js.map +1 -0
  46. package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
  47. package/server/config-DK7KOMNL.js.map +1 -0
  48. package/server/daily-log-processor-ENEUT22A.js +10 -0
  49. package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
  50. package/server/http-server.js +2 -5
  51. package/server/index.js +1771 -1538
  52. package/server/index.js.map +1 -1
  53. package/server/iv-solver-worker.js +9 -1
  54. package/server/market-provider-B437HKLW.js +17 -0
  55. package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
  56. package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
  57. package/server/trade-processor-L3PIQ5TG.js +10 -0
  58. package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
  59. package/src/auth/clients-store.ts +4 -4
  60. package/src/auth/code-store.ts +1 -1
  61. package/src/auth/config.ts +11 -12
  62. package/src/auth/login-page.ts +8 -10
  63. package/src/auth/provider.ts +35 -30
  64. package/src/auth/token.ts +17 -15
  65. package/src/db/backtest-schemas.ts +12 -4
  66. package/src/db/connection.ts +107 -40
  67. package/src/db/index.ts +37 -5
  68. package/src/db/json-adapters.ts +22 -31
  69. package/src/db/json-migration.ts +11 -15
  70. package/src/db/json-store.ts +5 -3
  71. package/src/db/market-datasets.ts +48 -15
  72. package/src/db/market-schemas.ts +11 -5
  73. package/src/db/market-views.ts +119 -32
  74. package/src/db/parquet-writer.ts +7 -7
  75. package/src/db/profile-schemas.ts +39 -22
  76. package/src/db/schemas.ts +2 -2
  77. package/src/http-server.ts +7 -12
  78. package/src/index.ts +8 -9
  79. package/src/market/ingestor/index.ts +1 -4
  80. package/src/market/ingestor/market-ingestor.ts +140 -67
  81. package/src/market/ingestor/types.ts +1 -3
  82. package/src/market/stores/chain-sql.ts +2 -8
  83. package/src/market/stores/chain-store.ts +1 -4
  84. package/src/market/stores/duckdb-chain-store.ts +3 -14
  85. package/src/market/stores/duckdb-enriched-store.ts +3 -13
  86. package/src/market/stores/duckdb-quote-store.ts +10 -27
  87. package/src/market/stores/duckdb-spot-store.ts +4 -20
  88. package/src/market/stores/enriched-sql.ts +3 -9
  89. package/src/market/stores/enriched-store.ts +2 -2
  90. package/src/market/stores/parquet-chain-store.ts +5 -22
  91. package/src/market/stores/parquet-enriched-store.ts +4 -16
  92. package/src/market/stores/parquet-oi-daily-store.ts +3 -14
  93. package/src/market/stores/parquet-quote-store.ts +9 -29
  94. package/src/market/stores/parquet-spot-store.ts +14 -33
  95. package/src/market/stores/quote-store.ts +2 -10
  96. package/src/market/stores/spot-sql.ts +3 -15
  97. package/src/market/stores/spot-store.ts +1 -1
  98. package/src/market/tickers/defaults.json +6 -6
  99. package/src/market/tickers/loader.ts +2 -7
  100. package/src/market/tickers/registry.ts +5 -15
  101. package/src/market/tickers/schemas.ts +2 -6
  102. package/src/models/strategy-profile.ts +39 -39
  103. package/src/sync/block-sync.ts +44 -65
  104. package/src/sync/index.ts +1 -4
  105. package/src/sync/metadata.ts +14 -20
  106. package/src/test-exports.ts +127 -123
  107. package/src/tools/analysis.ts +180 -159
  108. package/src/tools/batch-exit-analysis.ts +100 -59
  109. package/src/tools/blocks/analysis.ts +116 -101
  110. package/src/tools/blocks/comparison.ts +281 -364
  111. package/src/tools/blocks/core.ts +208 -253
  112. package/src/tools/blocks/health.ts +571 -563
  113. package/src/tools/blocks/similarity.ts +316 -283
  114. package/src/tools/edge-decay.ts +124 -197
  115. package/src/tools/exit-analysis.ts +130 -77
  116. package/src/tools/greeks-attribution.ts +84 -35
  117. package/src/tools/guides.ts +4 -6
  118. package/src/tools/imports.ts +9 -12
  119. package/src/tools/market-data.ts +1067 -757
  120. package/src/tools/market-enrichment.ts +3 -3
  121. package/src/tools/market-fetch.ts +148 -67
  122. package/src/tools/market-imports.ts +12 -12
  123. package/src/tools/middleware/sync-middleware.ts +5 -6
  124. package/src/tools/performance.ts +185 -302
  125. package/src/tools/profile-analysis.ts +52 -66
  126. package/src/tools/profiles.ts +106 -69
  127. package/src/tools/regime-advisor.ts +20 -45
  128. package/src/tools/replay.ts +81 -77
  129. package/src/tools/reports/discrepancies.ts +298 -328
  130. package/src/tools/reports/fields.ts +7 -25
  131. package/src/tools/reports/helpers.ts +18 -49
  132. package/src/tools/reports/predictive.ts +27 -70
  133. package/src/tools/reports/slippage-trends.ts +315 -345
  134. package/src/tools/reports/slippage.ts +1 -4
  135. package/src/tools/reports/strategy-matches.ts +399 -441
  136. package/src/tools/schema.ts +43 -40
  137. package/src/tools/shared/filters.ts +3 -9
  138. package/src/tools/snapshot.ts +9 -30
  139. package/src/tools/sql.ts +15 -14
  140. package/src/tools/tickers.ts +1 -4
  141. package/src/utils/batch-exit-analysis.ts +31 -42
  142. package/src/utils/black-scholes.ts +39 -29
  143. package/src/utils/block-loader.ts +68 -82
  144. package/src/utils/calibration-probe.ts +1 -2
  145. package/src/utils/chain-loader.ts +3 -3
  146. package/src/utils/csv-discovery.ts +16 -22
  147. package/src/utils/data-quality.ts +24 -36
  148. package/src/utils/exit-triggers.ts +91 -96
  149. package/src/utils/field-timing.ts +94 -79
  150. package/src/utils/filter-predicates.ts +13 -9
  151. package/src/utils/flatfile-importer.ts +94 -64
  152. package/src/utils/greeks-decomposition.ts +152 -100
  153. package/src/utils/iv-solver-pool.ts +55 -25
  154. package/src/utils/iv-solver-worker.ts +5 -5
  155. package/src/utils/market-enricher.ts +528 -497
  156. package/src/utils/market-importer.ts +31 -12
  157. package/src/utils/market-provider.ts +21 -23
  158. package/src/utils/massive-tier.ts +5 -7
  159. package/src/utils/migrate-option-data-helpers.ts +2 -8
  160. package/src/utils/option-quote-greeks.ts +25 -31
  161. package/src/utils/option-time.ts +4 -8
  162. package/src/utils/output-formatter.ts +1 -4
  163. package/src/utils/provider-capabilities.ts +1 -4
  164. package/src/utils/providers/massive.ts +59 -93
  165. package/src/utils/providers/thetadata/backfill.ts +14 -23
  166. package/src/utils/providers/thetadata/client.ts +12 -8
  167. package/src/utils/providers/thetadata/decode.ts +2 -20
  168. package/src/utils/providers/thetadata/endpoints.ts +32 -40
  169. package/src/utils/providers/thetadata/join.ts +11 -10
  170. package/src/utils/providers/thetadata/proto.ts +12 -10
  171. package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
  172. package/src/utils/providers/thetadata.ts +11 -10
  173. package/src/utils/quote-enricher.ts +4 -4
  174. package/src/utils/quote-parquet-projection.ts +3 -11
  175. package/src/utils/sample-date-selector.ts +3 -5
  176. package/src/utils/schema-metadata.ts +102 -70
  177. package/src/utils/ticker.ts +5 -9
  178. package/src/utils/trade-replay.ts +77 -68
  179. package/dist/chunk-4BLCXNQ6.js.map +0 -1
  180. package/dist/chunk-BKQ4PM4Y.js.map +0 -1
  181. package/dist/chunk-FGZH632F.js.map +0 -1
  182. package/dist/chunk-LDKTV7GW.js.map +0 -1
  183. package/dist/chunk-PRAYH3RT.js.map +0 -1
  184. package/dist/chunk-W2PP3LEH.js.map +0 -1
  185. package/dist/chunk-XXYOUIZY.js.map +0 -1
  186. package/dist/daily-log-processor-6MWJ23JK.js +0 -9
  187. package/dist/market-provider-VDRJUEF2.js +0 -16
  188. package/dist/trade-processor-NHU4VWRX.js +0 -9
  189. package/server/chunk-5EBXHT6C.js.map +0 -1
  190. package/server/chunk-72GKJE2U.js.map +0 -1
  191. package/server/chunk-CSDVJPBB.js.map +0 -1
  192. package/server/chunk-FBNDMCT5.js.map +0 -1
  193. package/server/chunk-NRFXAJF7.js.map +0 -1
  194. package/server/chunk-NZO6PT64.js.map +0 -1
  195. package/server/chunk-PNKG7RY7.js.map +0 -1
  196. package/server/chunk-WA5AAPCH.js.map +0 -1
  197. package/server/config-6IZXEFEX.js.map +0 -1
  198. package/server/daily-log-processor-Y3PVSVBM.js +0 -10
  199. package/server/market-provider-VOYYVYWT.js +0 -17
  200. package/server/trade-processor-JWVS37KM.js +0 -10
  201. /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-BY3ISY6K.js.map} +0 -0
  202. /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
  203. /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-H3ARIVZ4.js.map} +0 -0
  204. /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
  205. /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
  206. /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.js.map} +0 -0
  207. /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
  208. /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-ENEUT22A.js.map} +0 -0
  209. /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
  210. /package/server/{market-provider-VOYYVYWT.js.map → market-provider-B437HKLW.js.map} +0 -0
  211. /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
  212. /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
  213. /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.js.map} +0 -0
  214. /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; // YYYY-MM-DD in ET
41
- time: string; // HH:MM in ET
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; // 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
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(':') ? rawTicker.slice(rawTicker.indexOf(':') + 1) : rawTicker;
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: 'option' | 'index' = 'option',
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 === 'index' ? '/tmp/massive-flat-index' : TMP_DIR;
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 === 'index') {
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('./market-provider.ts');
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: 'download_failed' };
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: 'provider_no_flat_files' };
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 = assetClass === 'index' ? `I:${underlying}` : (underlying === "SPX" ? "O:SPX" : `O:${underlying}`);
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: 'option' | 'index',
319
+ assetClass: "option" | "index",
322
320
  ): Promise<{ date: string; rows: ParsedBar[]; skipped?: boolean; error?: string }> {
323
- const tmpSubdir = assetClass === 'index' ? '/tmp/massive-flat-index' : TMP_DIR;
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('./market-provider.ts');
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: 'download_failed' };
332
+ return { date: dateStr, rows: [], error: "download_failed" };
335
333
  }
336
334
  } else {
337
- return { date: dateStr, rows: [], error: 'provider_no_flat_files' };
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: 'not_found' };
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 = assetClass === 'index' ? `I:${underlying}` : (underlying === "SPX" ? "O:SPX" : `O:${underlying}`);
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) { isHeader = false; return; }
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 { unlinkSync(localPath); } catch { /* best-effort */ }
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: 'option' | 'index' = 'option',
392
+ assetClass: "option" | "index" = "option",
387
393
  ): Promise<ImportFlatFilesResult> {
388
- const tmpDir = assetClass === 'index' ? '/tmp/massive-flat-index' : TMP_DIR;
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(` [importFlatFiles] ${daysToImport.length} days to import, ${skippedDays.length} already imported (${assetClass} ${underlying})`);
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(` [importFlatFiles] batch ${batchNum}/${totalBatches}: ${batch[0]}..${batch[batch.length - 1]} (${Math.round((Date.now() - t0) / 1000)}s)`);
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
- date: p.date,
444
- asset_class: assetClass,
445
- underlying,
446
- imported_at: new Date().toISOString(),
447
- bar_count: p.rows.length,
448
- }, dataDir);
449
- } catch { /* best-effort metadata tracking */ }
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(` [importFlatFiles] done: ${totalImported} bars imported, ${totalSkipped} days skipped (${Math.round((Date.now() - t0) / 1000)}s)`);
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 = '/tmp/massive-flat-index';
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('./market-provider.ts');
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, 'index');
483
- if (!downloaded) return { date: dateStr, rows: [], error: 'download_failed' };
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: 'provider_no_flat_files' };
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: 'not_found' };
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) { isHeader = false; return; }
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 { unlinkSync(localPath); } catch { /* best-effort */ }
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('/tmp/massive-flat-index', { recursive: true });
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 + 'T00:00:00Z');
558
- const end = new Date(cov.latest + 'T00:00:00Z');
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(` [importIndexBars] all ${days.length} days have data for ${tickers.join(',')} — nothing to import`);
614
+ console.log(
615
+ ` [importIndexBars] all ${days.length} days have data for ${tickers.join(",")} — nothing to import`,
616
+ );
591
617
  } else {
592
- console.log(` [importIndexBars] ${daysToImport.length} days to import for ${tickers.join(',')}, ${skipDays.size} skipped`);
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(` [importIndexBars] batch ${batchNum}/${totalBatches}: ${batch[0]}..${batch[batch.length - 1]} (${Math.round((Date.now() - t0) / 1000)}s)`);
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(` [importIndexBars] done: ${totalImported} bars imported, ${totalSkipped} days skipped (${Math.round((Date.now() - t0) / 1000)}s)`);
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,