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
@@ -61,14 +61,14 @@ export function registerMarketEnrichmentTools(
61
61
  ticker: z
62
62
  .string()
63
63
  .describe(
64
- "Ticker symbol to enrich (e.g., 'SPX', 'QQQ'). Must match an existing ticker in market.spot_daily / market.enriched."
64
+ "Ticker symbol to enrich (e.g., 'SPX', 'QQQ'). Must match an existing ticker in market.spot_daily / market.enriched.",
65
65
  ),
66
66
  force_full: z
67
67
  .boolean()
68
68
  .default(false)
69
69
  .describe(
70
70
  "Currently a no-op against the store-backed compute path. " +
71
- "Originally cleared the enriched_through watermark and recomputed all rows from scratch."
71
+ "Originally cleared the enriched_through watermark and recomputed all rows from scratch.",
72
72
  ),
73
73
  }),
74
74
  },
@@ -115,6 +115,6 @@ export function registerMarketEnrichmentTools(
115
115
  } finally {
116
116
  await downgradeToReadOnly(baseDir);
117
117
  }
118
- }
118
+ },
119
119
  );
120
120
  }
@@ -29,13 +29,21 @@ export function registerMarketFetchTools(
29
29
  tickers: z.array(z.string()).min(1).describe("Ticker symbols (e.g., ['SPX','VIX'])"),
30
30
  from: z.string().describe("Start date YYYY-MM-DD"),
31
31
  to: z.string().describe("End date YYYY-MM-DD"),
32
- timespan: z.enum(["1d", "1m", "5m", "15m", "1h"]).default("1d")
32
+ timespan: z
33
+ .enum(["1d", "1m", "5m", "15m", "1h"])
34
+ .default("1d")
33
35
  .describe("Bar granularity. '1d' = daily; others = intraday."),
34
- provider: z.enum(["massive", "thetadata"]).optional()
36
+ provider: z
37
+ .enum(["massive", "thetadata"])
38
+ .optional()
35
39
  .describe("Optional per-call provider override. Defaults to env MARKET_DATA_PROVIDER."),
36
- skip_enrichment: z.boolean().default(false)
40
+ skip_enrichment: z
41
+ .boolean()
42
+ .default(false)
37
43
  .describe("Skip per-ticker enrichment (RSI/ATR/etc.) after write."),
38
- dry_run: z.boolean().default(false)
44
+ dry_run: z
45
+ .boolean()
46
+ .default(false)
39
47
  .describe("Validate inputs and preview without writing."),
40
48
  }),
41
49
  },
@@ -73,10 +81,20 @@ export function registerMarketFetchTools(
73
81
  "Massive falls back automatically on Developer/lower plans; ThetaData (both modes) returns true NBBO. " +
74
82
  "Returns status='unsupported' with a clear error only when the active provider lacks the requested mode (e.g., bulk-by-underlying on Massive).",
75
83
  inputSchema: z.object({
76
- tickers: z.array(z.string()).min(1).optional()
77
- .describe("Option tickers in OCC format (e.g., 'SPXW260321C05800000'). No O: prefix. Mutually exclusive with 'underlyings'."),
78
- underlyings: z.array(z.string()).min(1).optional()
79
- .describe("Underlying symbols (e.g. ['SPX']). Bulk mode — returns every contract's minute quotes per date. Mutually exclusive with 'tickers'."),
84
+ tickers: z
85
+ .array(z.string())
86
+ .min(1)
87
+ .optional()
88
+ .describe(
89
+ "Option tickers in OCC format (e.g., 'SPXW260321C05800000'). No O: prefix. Mutually exclusive with 'underlyings'.",
90
+ ),
91
+ underlyings: z
92
+ .array(z.string())
93
+ .min(1)
94
+ .optional()
95
+ .describe(
96
+ "Underlying symbols (e.g. ['SPX']). Bulk mode — returns every contract's minute quotes per date. Mutually exclusive with 'tickers'.",
97
+ ),
80
98
  from: z.string().describe("Start date YYYY-MM-DD"),
81
99
  to: z.string().describe("End date YYYY-MM-DD"),
82
100
  provider: z.enum(["massive", "thetadata"]).optional(),
@@ -97,13 +115,14 @@ export function registerMarketFetchTools(
97
115
  let progress = 0;
98
116
  onProgress = async (event) => {
99
117
  progress++;
100
- const message = event.kind === "group"
101
- ? `${event.underlying} ${event.root}/${event.right} ${event.date} ` +
102
- `${event.phase === "checkpoint" ? "checkpoint" : "done"} (${event.status})` +
103
- (event.totalContracts != null
104
- ? ` ${event.completedContracts ?? 0}/${event.totalContracts} contracts`
105
- : "")
106
- : `${event.underlying} ${event.date} flushed — ${event.rowsWritten} rows`;
118
+ const message =
119
+ event.kind === "group"
120
+ ? `${event.underlying} ${event.root}/${event.right} ${event.date} ` +
121
+ `${event.phase === "checkpoint" ? "checkpoint" : "done"} (${event.status})` +
122
+ (event.totalContracts != null
123
+ ? ` ${event.completedContracts ?? 0}/${event.totalContracts} contracts`
124
+ : "")
125
+ : `${event.underlying} ${event.date} flushed — ${event.rowsWritten} rows`;
107
126
  try {
108
127
  await extra.sendNotification({
109
128
  method: "notifications/progress",
@@ -116,9 +135,17 @@ export function registerMarketFetchTools(
116
135
  }
117
136
 
118
137
  const result = await ingestor.ingestQuotes({
119
- tickers, underlyings, from, to, provider, dryRun: dry_run, onProgress,
138
+ tickers,
139
+ underlyings,
140
+ from,
141
+ to,
142
+ provider,
143
+ dryRun: dry_run,
144
+ onProgress,
120
145
  });
121
- const mode = underlyings ? `bulk (${underlyings.join(",")})` : `per-ticker (${tickers?.length ?? 0})`;
146
+ const mode = underlyings
147
+ ? `bulk (${underlyings.join(",")})`
148
+ : `per-ticker (${tickers?.length ?? 0})`;
122
149
  // `partial` means some batches were logged-and-skipped. The structured
123
150
  // `skipped[]` array on the result body carries the details; we surface
124
151
  // the count in the summary so console callers see it.
@@ -127,10 +154,13 @@ export function registerMarketFetchTools(
127
154
  ? ` — ${result.skipped.length} batch(es) skipped (see result.skipped[])`
128
155
  : "";
129
156
  const summary =
130
- result.status === "unsupported" ? `Unsupported: ${result.error}` :
131
- result.status === "error" ? `Error: ${result.error}` :
132
- dry_run ? `[DRY RUN] Would fetch quotes: ${mode}` :
133
- `${result.status}: wrote ${result.rowsWritten} quote rows (${mode})${partialSuffix}`;
157
+ result.status === "unsupported"
158
+ ? `Unsupported: ${result.error}`
159
+ : result.status === "error"
160
+ ? `Error: ${result.error}`
161
+ : dry_run
162
+ ? `[DRY RUN] Would fetch quotes: ${mode}`
163
+ : `${result.status}: wrote ${result.rowsWritten} quote rows (${mode})${partialSuffix}`;
134
164
  return createToolOutput(summary, result);
135
165
  } finally {
136
166
  await downgradeToReadOnly(baseDir);
@@ -147,7 +177,10 @@ export function registerMarketFetchTools(
147
177
  "Writes contract metadata to market.option_chain. Capability-gated on provider.fetchContractList. " +
148
178
  "Use for pre-populating the contract universe before running backtests or enriching quotes.",
149
179
  inputSchema: z.object({
150
- underlyings: z.array(z.string()).min(1).describe("Underlying tickers (e.g., ['SPX','QQQ'])"),
180
+ underlyings: z
181
+ .array(z.string())
182
+ .min(1)
183
+ .describe("Underlying tickers (e.g., ['SPX','QQQ'])"),
151
184
  from: z.string().describe("Start date YYYY-MM-DD"),
152
185
  to: z.string().describe("End date YYYY-MM-DD"),
153
186
  provider: z.enum(["massive", "thetadata"]).optional(),
@@ -157,11 +190,19 @@ export function registerMarketFetchTools(
157
190
  async ({ underlyings, from, to, provider, dry_run }) => {
158
191
  await upgradeToReadWrite(baseDir);
159
192
  try {
160
- const result = await ingestor.ingestChain({ underlyings, from, to, provider, dryRun: dry_run });
193
+ const result = await ingestor.ingestChain({
194
+ underlyings,
195
+ from,
196
+ to,
197
+ provider,
198
+ dryRun: dry_run,
199
+ });
161
200
  const summary =
162
- result.status === "unsupported" ? `Unsupported: ${result.error}` :
163
- dry_run ? `[DRY RUN] Would fetch chain for ${underlyings.join(", ")}` :
164
- `${result.status}: wrote ${result.rowsWritten} contract rows`;
201
+ result.status === "unsupported"
202
+ ? `Unsupported: ${result.error}`
203
+ : dry_run
204
+ ? `[DRY RUN] Would fetch chain for ${underlyings.join(", ")}`
205
+ : `${result.status}: wrote ${result.rowsWritten} contract rows`;
165
206
  return createToolOutput(summary, result);
166
207
  } finally {
167
208
  await downgradeToReadOnly(baseDir);
@@ -186,23 +227,34 @@ export function registerMarketFetchTools(
186
227
  " option_chain → market.option_chain — partition {underlying, date}\n\n" +
187
228
  "The SELECT must output the target store's canonical columns in order. Writes are single-partition: every row must belong to the named partition. Works in both Parquet and DuckDB modes — the store handles mode routing.",
188
229
  inputSchema: z.object({
189
- file_path: z.string().describe(
190
- "Absolute path to a local file DuckDB can read. The SELECT references this path via read_parquet('<file_path>') / read_csv('<file_path>') / read_json('<file_path>'). Provider downloads (e.g., Massive rclone output) or any user-supplied file are all valid sources.",
191
- ),
192
- dataset_type: z.enum(["spot_bars", "option_quotes", "option_chain"]).describe(
193
- "Target store. Determines which writeFromSelect is invoked and which partition keys are required.",
194
- ),
195
- select_sql: z.string().describe(
196
- "SELECT (or WITH ... SELECT) that produces the target store's canonical columns. " +
197
- "spot_bars columns: (ticker, date, time, open, high, low, close, bid, ask). " +
198
- "option_quote_minutes columns: (underlying, date, ticker, time, bid, ask, mid, last_updated_ns, source, delta, gamma, theta, vega, iv, greeks_source, greeks_revision, rate_type, rate_value, gamma_source); missing greeks/provenance columns are filled as null. " +
199
- "option_chain columns: (underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style).",
200
- ),
201
- partition: z.object({
202
- ticker: z.string().optional().describe("Required for dataset_type='spot_bars'."),
203
- underlying: z.string().optional().describe("Required for dataset_type='option_quotes' | 'option_chain'."),
204
- date: z.string().describe("Partition date YYYY-MM-DD."),
205
- }).describe("Single-partition target. Keys depend on dataset_type."),
230
+ file_path: z
231
+ .string()
232
+ .describe(
233
+ "Absolute path to a local file DuckDB can read. The SELECT references this path via read_parquet('<file_path>') / read_csv('<file_path>') / read_json('<file_path>'). Provider downloads (e.g., Massive rclone output) or any user-supplied file are all valid sources.",
234
+ ),
235
+ dataset_type: z
236
+ .enum(["spot_bars", "option_quotes", "option_chain"])
237
+ .describe(
238
+ "Target store. Determines which writeFromSelect is invoked and which partition keys are required.",
239
+ ),
240
+ select_sql: z
241
+ .string()
242
+ .describe(
243
+ "SELECT (or WITH ... SELECT) that produces the target store's canonical columns. " +
244
+ "spot_bars columns: (ticker, date, time, open, high, low, close, bid, ask). " +
245
+ "option_quote_minutes columns: (underlying, date, ticker, time, bid, ask, mid, last_updated_ns, source, delta, gamma, theta, vega, iv, greeks_source, greeks_revision, rate_type, rate_value, gamma_source); missing greeks/provenance columns are filled as null. " +
246
+ "option_chain columns: (underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style).",
247
+ ),
248
+ partition: z
249
+ .object({
250
+ ticker: z.string().optional().describe("Required for dataset_type='spot_bars'."),
251
+ underlying: z
252
+ .string()
253
+ .optional()
254
+ .describe("Required for dataset_type='option_quotes' | 'option_chain'."),
255
+ date: z.string().describe("Partition date YYYY-MM-DD."),
256
+ })
257
+ .describe("Single-partition target. Keys depend on dataset_type."),
206
258
  dry_run: z.boolean().default(false),
207
259
  }),
208
260
  },
@@ -217,10 +269,13 @@ export function registerMarketFetchTools(
217
269
  dryRun: dry_run,
218
270
  });
219
271
  const summary =
220
- result.status === "error" ? `Error: ${result.error}` :
221
- result.status === "unsupported" ? `Unsupported: ${result.error}` :
222
- dry_run ? `[DRY RUN] Would write ${dataset_type} rows from ${file_path}` :
223
- `${result.status}: wrote ${result.rowsWritten} rows to ${dataset_type}`;
272
+ result.status === "error"
273
+ ? `Error: ${result.error}`
274
+ : result.status === "unsupported"
275
+ ? `Unsupported: ${result.error}`
276
+ : dry_run
277
+ ? `[DRY RUN] Would write ${dataset_type} rows from ${file_path}`
278
+ : `${result.status}: wrote ${result.rowsWritten} rows to ${dataset_type}`;
224
279
  return createToolOutput(summary, result);
225
280
  } finally {
226
281
  await downgradeToReadOnly(baseDir);
@@ -269,21 +324,44 @@ export function registerMarketFetchTools(
269
324
  inputSchema: z.object({
270
325
  asOf: z.string().describe("Target date YYYY-MM-DD. Typically yesterday."),
271
326
  spot_tickers: z.array(z.string()).min(1).describe("Tickers to fetch daily bars for"),
272
- chain_underlyings: z.array(z.string()).optional().describe("Underlyings to fetch option chains for"),
273
- quote_tickers: z.array(z.string()).optional().describe("Option tickers to fetch minute quotes for"),
274
- quote_underlyings: z.array(z.string()).optional().describe(
275
- "Underlyings to fetch minute quotes for via the bulk-by-underlying path (e.g., ['SPX']). " +
276
- "Complementary with quote_tickers — pass whichever matches the target provider's capability. " +
277
- "ThetaData supports bulk mode; Massive is per-ticker only. Both may be passed in one call — " +
278
- "they dispatch as independent ingestQuotes invocations.",
279
- ),
280
- compute_vix_context: z.boolean().default(true).describe(
281
- "If true and any VIX-family ticker is in spot_tickers, runs compute_vix_context at the end. Default true.",
282
- ),
327
+ chain_underlyings: z
328
+ .array(z.string())
329
+ .optional()
330
+ .describe("Underlyings to fetch option chains for"),
331
+ quote_tickers: z
332
+ .array(z.string())
333
+ .optional()
334
+ .describe("Option tickers to fetch minute quotes for"),
335
+ quote_underlyings: z
336
+ .array(z.string())
337
+ .optional()
338
+ .describe(
339
+ "Underlyings to fetch minute quotes for via the bulk-by-underlying path (e.g., ['SPX']). " +
340
+ "Complementary with quote_tickers — pass whichever matches the target provider's capability. " +
341
+ "ThetaData supports bulk mode; Massive is per-ticker only. Both may be passed in one call — " +
342
+ "they dispatch as independent ingestQuotes invocations.",
343
+ ),
344
+ compute_vix_context: z
345
+ .boolean()
346
+ .default(true)
347
+ .describe(
348
+ "If true and any VIX-family ticker is in spot_tickers, runs compute_vix_context at the end. Default true.",
349
+ ),
283
350
  provider: z.enum(["massive", "thetadata"]).optional(),
284
351
  }),
285
352
  },
286
- async ({ asOf, spot_tickers, chain_underlyings, quote_tickers, quote_underlyings, compute_vix_context, provider }, extra) => {
353
+ async (
354
+ {
355
+ asOf,
356
+ spot_tickers,
357
+ chain_underlyings,
358
+ quote_tickers,
359
+ quote_underlyings,
360
+ compute_vix_context,
361
+ provider,
362
+ },
363
+ extra,
364
+ ) => {
287
365
  await upgradeToReadWrite(baseDir);
288
366
  try {
289
367
  // Bulk-underlying quote fetches are the only step in this composite
@@ -300,13 +378,14 @@ export function registerMarketFetchTools(
300
378
  let progress = 0;
301
379
  onProgress = async (event) => {
302
380
  progress++;
303
- const message = event.kind === "group"
304
- ? `${event.underlying} ${event.root}/${event.right} ${event.date} ` +
305
- `${event.phase === "checkpoint" ? "checkpoint" : "done"} (${event.status})` +
306
- (event.totalContracts != null
307
- ? ` ${event.completedContracts ?? 0}/${event.totalContracts} contracts`
308
- : "")
309
- : `${event.underlying} ${event.date} flushed — ${event.rowsWritten} rows`;
381
+ const message =
382
+ event.kind === "group"
383
+ ? `${event.underlying} ${event.root}/${event.right} ${event.date} ` +
384
+ `${event.phase === "checkpoint" ? "checkpoint" : "done"} (${event.status})` +
385
+ (event.totalContracts != null
386
+ ? ` ${event.completedContracts ?? 0}/${event.totalContracts} contracts`
387
+ : "")
388
+ : `${event.underlying} ${event.date} flushed — ${event.rowsWritten} rows`;
310
389
  try {
311
390
  await extra.sendNotification({
312
391
  method: "notifications/progress",
@@ -334,7 +413,9 @@ export function registerMarketFetchTools(
334
413
  ...result.perOperation.quotes,
335
414
  ...(result.perOperation.vixContext ? [result.perOperation.vixContext] : []),
336
415
  ];
337
- const unsupportedCount = operationResults.filter((item) => item.status === "unsupported").length;
416
+ const unsupportedCount = operationResults.filter(
417
+ (item) => item.status === "unsupported",
418
+ ).length;
338
419
  const skippedOpCount = operationResults.filter((item) => item.status === "skipped").length;
339
420
  // `partial` surfaces enrichQuoteRows batch-skip details. The structured
340
421
  // skipped[] array lives on the refresh result body; we surface the count
@@ -27,10 +27,7 @@ import * as os from "os";
27
27
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
28
28
  import { getConnection, upgradeToReadWrite, downgradeToReadOnly } from "../db/connection.ts";
29
29
  import { createToolOutput } from "../utils/output-formatter.ts";
30
- import {
31
- parseCsvToBars,
32
- parseDatabaseRowsToBars,
33
- } from "../utils/market-importer.ts";
30
+ import { parseCsvToBars, parseDatabaseRowsToBars } from "../utils/market-importer.ts";
34
31
  import { normalizeTicker } from "../utils/ticker.ts";
35
32
  import type { MarketStores } from "../market/stores/index.ts";
36
33
  import type { BarRow } from "../market/stores/types.ts";
@@ -121,8 +118,8 @@ export function registerMarketImportTools(
121
118
  .record(z.string(), z.string())
122
119
  .describe(
123
120
  "Maps CSV column names (keys) to schema column names (values). " +
124
- "Required: date (or unix time), open, high, low, close. " +
125
- "Time auto-extracted from the date column when it carries a Unix timestamp.",
121
+ "Required: date (or unix time), open, high, low, close. " +
122
+ "Time auto-extracted from the date column when it carries a Unix timestamp.",
126
123
  ),
127
124
  dry_run: z
128
125
  .boolean()
@@ -133,7 +130,7 @@ export function registerMarketImportTools(
133
130
  .default(false)
134
131
  .describe(
135
132
  "If true, skips EnrichedStore.compute() (and computeContext for VIX-family) " +
136
- "after the spot write. Re-run enrich_market_data later to populate derived fields.",
133
+ "after the spot write. Re-run enrich_market_data later to populate derived fields.",
137
134
  ),
138
135
  }),
139
136
  },
@@ -246,7 +243,7 @@ export function registerMarketImportTools(
246
243
  .string()
247
244
  .describe(
248
245
  "DuckDB SELECT query to execute against the external database. " +
249
- "Must reference tables with the alias 'ext_import_source'.",
246
+ "Must reference tables with the alias 'ext_import_source'.",
250
247
  ),
251
248
  ticker: z
252
249
  .string()
@@ -257,8 +254,8 @@ export function registerMarketImportTools(
257
254
  .record(z.string(), z.string())
258
255
  .describe(
259
256
  "Maps query column names (keys) to schema column names (values). " +
260
- "Required: date (or unix time), open, high, low, close. " +
261
- "Time auto-extracted from the date column when it carries a Unix timestamp.",
257
+ "Required: date (or unix time), open, high, low, close. " +
258
+ "Time auto-extracted from the date column when it carries a Unix timestamp.",
262
259
  ),
263
260
  dry_run: z
264
261
  .boolean()
@@ -309,7 +306,11 @@ export function registerMarketImportTools(
309
306
  } finally {
310
307
  // Always DETACH regardless of success — non-fatal if the ATTACH never
311
308
  // succeeded (e.g. invalid db_path) so the original error surfaces.
312
- try { await conn.run(`DETACH ${EXT_ALIAS}`); } catch { /* best-effort */ }
309
+ try {
310
+ await conn.run(`DETACH ${EXT_ALIAS}`);
311
+ } catch {
312
+ /* best-effort */
313
+ }
313
314
  }
314
315
 
315
316
  if (bars.length === 0) {
@@ -379,5 +380,4 @@ export function registerMarketImportTools(
379
380
  }
380
381
  },
381
382
  );
382
-
383
383
  }
@@ -48,7 +48,7 @@ export interface FullSyncContext {
48
48
  */
49
49
  export function withSyncedBlock<TInput extends { blockId: string }, TOutput>(
50
50
  baseDir: string,
51
- handler: (input: TInput, ctx: SingleBlockContext) => Promise<TOutput>
51
+ handler: (input: TInput, ctx: SingleBlockContext) => Promise<TOutput>,
52
52
  ): (input: TInput) => Promise<TOutput | ToolError> {
53
53
  return async (input: TInput) => {
54
54
  // NOTE: single-shot RW upgrade + silent RO fallback. withFullSync uses a bounded
@@ -106,13 +106,12 @@ export function withSyncedBlocks<
106
106
  TOutput,
107
107
  >(
108
108
  baseDir: string,
109
- handler: (input: TInput, ctx: MultiBlockContext) => Promise<TOutput>
109
+ handler: (input: TInput, ctx: MultiBlockContext) => Promise<TOutput>,
110
110
  ): (input: TInput) => Promise<TOutput | ToolError> {
111
111
  return async (input: TInput) => {
112
112
  // Collect block IDs from various input patterns
113
113
  const blockIds: string[] =
114
- input.blockIds ??
115
- [input.blockIdA, input.blockIdB].filter((id): id is string => !!id);
114
+ input.blockIds ?? [input.blockIdA, input.blockIdB].filter((id): id is string => !!id);
116
115
 
117
116
  const syncResults = new Map<string, BlockSyncResult>();
118
117
 
@@ -171,7 +170,7 @@ export function withSyncedBlocks<
171
170
  */
172
171
  export function withFullSync<TInput, TOutput>(
173
172
  baseDir: string,
174
- handler: (input: TInput, ctx: FullSyncContext) => Promise<TOutput>
173
+ handler: (input: TInput, ctx: FullSyncContext) => Promise<TOutput>,
175
174
  ): (input: TInput) => Promise<TOutput> {
176
175
  return async (input: TInput) => {
177
176
  // Outer retry loop: papers over short RW-lock races where another session is
@@ -209,7 +208,7 @@ export function withFullSync<TInput, TOutput>(
209
208
  // tell that sync was skipped and data may be stale.
210
209
  console.warn(
211
210
  `[sync-middleware] sync skipped: could not acquire write lock after ${attempts} retries; ` +
212
- `downstream data may be stale until next call succeeds.`
211
+ `downstream data may be stale until next call succeeds.`,
213
212
  );
214
213
  blockSyncResult = {
215
214
  blocksProcessed: 0,