tradeblocks-mcp 3.0.2 → 3.3.1

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 (218) hide show
  1. package/README.md +85 -80
  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-QTTR7AAW.js → chunk-2I3S2ZLT.js} +325 -84
  7. package/dist/chunk-2I3S2ZLT.js.map +1 -0
  8. package/dist/{chunk-4BLCXNQ6.js → chunk-4HZLVUOZ.js} +179 -183
  9. package/dist/chunk-4HZLVUOZ.js.map +1 -0
  10. package/dist/{chunk-XXYOUIZY.js → chunk-NWNSKALN.js} +63 -80
  11. package/dist/chunk-NWNSKALN.js.map +1 -0
  12. package/dist/{chunk-PRAYH3RT.js → chunk-WNTYA7ER.js} +29 -12
  13. package/dist/chunk-WNTYA7ER.js.map +1 -0
  14. package/dist/{chunk-BKQ4PM4Y.js → chunk-YZA4RS76.js} +24 -18
  15. package/dist/chunk-YZA4RS76.js.map +1 -0
  16. package/dist/{chunk-W2PP3LEH.js → chunk-ZTJXC3LS.js} +17 -8
  17. package/dist/chunk-ZTJXC3LS.js.map +1 -0
  18. package/dist/daily-log-processor-MZW2XBZY.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-6U33TQUT.js +16 -0
  22. package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
  23. package/dist/{sync-PO4IPCYV.js → sync-3UBC37VW.js} +7 -7
  24. package/dist/test-exports.js +876 -483
  25. package/dist/test-exports.js.map +1 -1
  26. package/dist/trade-processor-DKDCWGKD.js +9 -0
  27. package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
  28. package/package.json +2 -6
  29. package/server/{chunk-CSDVJPBB.js → chunk-536NSFED.js} +29 -12
  30. package/server/chunk-536NSFED.js.map +1 -0
  31. package/server/{chunk-WA5AAPCH.js → chunk-A3UKD64A.js} +17 -8
  32. package/server/chunk-A3UKD64A.js.map +1 -0
  33. package/server/{chunk-PNKG7RY7.js → chunk-OMUDNJBJ.js} +63 -80
  34. package/server/chunk-OMUDNJBJ.js.map +1 -0
  35. package/server/{chunk-72GKJE2U.js → chunk-RJDJHZ5Y.js} +934 -335
  36. package/server/chunk-RJDJHZ5Y.js.map +1 -0
  37. package/server/{chunk-NZO6PT64.js → chunk-RM4B2VKS.js} +167 -177
  38. package/server/chunk-RM4B2VKS.js.map +1 -0
  39. package/server/{chunk-5EBXHT6C.js → chunk-SY6GDNVI.js} +24 -18
  40. package/server/chunk-SY6GDNVI.js.map +1 -0
  41. package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
  42. package/server/chunk-T66KH2XH.js.map +1 -0
  43. package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
  44. package/server/chunk-W5E7FHC4.js.map +1 -0
  45. package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
  46. package/server/config-DK7KOMNL.js.map +1 -0
  47. package/server/daily-log-processor-B2VPU2FQ.js +10 -0
  48. package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
  49. package/server/http-server.js +2 -5
  50. package/server/index.js +2497 -2029
  51. package/server/index.js.map +1 -1
  52. package/server/iv-solver-worker.js +9 -1
  53. package/server/market-provider-L56RFHAC.js +17 -0
  54. package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
  55. package/server/{sync-QFI5L7S7.js → sync-CENKDN53.js} +7 -7
  56. package/server/trade-processor-3PI4LHZI.js +10 -0
  57. package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
  58. package/src/auth/clients-store.ts +4 -4
  59. package/src/auth/code-store.ts +1 -1
  60. package/src/auth/config.ts +11 -12
  61. package/src/auth/login-page.ts +8 -10
  62. package/src/auth/provider.ts +35 -30
  63. package/src/auth/token.ts +17 -15
  64. package/src/db/backtest-schemas.ts +38 -8
  65. package/src/db/connection.ts +107 -40
  66. package/src/db/data-root.ts +22 -0
  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 +139 -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/index.ts +2 -0
  114. package/src/tools/blocks/paired-comparison.ts +425 -0
  115. package/src/tools/blocks/similarity.ts +316 -283
  116. package/src/tools/edge-decay.ts +124 -197
  117. package/src/tools/exit-analysis.ts +130 -77
  118. package/src/tools/greeks-attribution.ts +84 -35
  119. package/src/tools/guides.ts +4 -6
  120. package/src/tools/imports.ts +11 -14
  121. package/src/tools/market-data.ts +1067 -757
  122. package/src/tools/market-enrichment.ts +3 -3
  123. package/src/tools/market-fetch.ts +148 -67
  124. package/src/tools/market-imports.ts +12 -12
  125. package/src/tools/middleware/sync-middleware.ts +5 -6
  126. package/src/tools/performance.ts +185 -302
  127. package/src/tools/profile-analysis.ts +52 -66
  128. package/src/tools/profiles.ts +106 -69
  129. package/src/tools/regime-advisor.ts +20 -45
  130. package/src/tools/replay.ts +81 -77
  131. package/src/tools/reports/discrepancies.ts +298 -328
  132. package/src/tools/reports/fields.ts +7 -25
  133. package/src/tools/reports/helpers.ts +18 -49
  134. package/src/tools/reports/predictive.ts +27 -70
  135. package/src/tools/reports/slippage-trends.ts +315 -345
  136. package/src/tools/reports/slippage.ts +1 -4
  137. package/src/tools/reports/strategy-matches.ts +399 -441
  138. package/src/tools/schema.ts +43 -40
  139. package/src/tools/shared/filters.ts +3 -9
  140. package/src/tools/snapshot.ts +9 -30
  141. package/src/tools/sql.ts +15 -14
  142. package/src/tools/tickers.ts +1 -4
  143. package/src/utils/batch-exit-analysis.ts +31 -42
  144. package/src/utils/black-scholes.ts +39 -29
  145. package/src/utils/block-loader.ts +69 -83
  146. package/src/utils/calibration-probe.ts +3 -4
  147. package/src/utils/chain-loader.ts +3 -3
  148. package/src/utils/csv-discovery.ts +16 -22
  149. package/src/utils/data-quality.ts +24 -36
  150. package/src/utils/exit-triggers.ts +91 -96
  151. package/src/utils/field-timing.ts +94 -79
  152. package/src/utils/filter-predicates.ts +13 -9
  153. package/src/utils/flatfile-importer.ts +94 -64
  154. package/src/utils/greeks-decomposition.ts +152 -100
  155. package/src/utils/iv-solver-pool.ts +55 -25
  156. package/src/utils/iv-solver-worker.ts +5 -5
  157. package/src/utils/market-enricher.ts +528 -497
  158. package/src/utils/market-importer.ts +31 -12
  159. package/src/utils/market-provider.ts +21 -23
  160. package/src/utils/massive-tier.ts +5 -7
  161. package/src/utils/migrate-option-data-helpers.ts +2 -8
  162. package/src/utils/option-quote-greeks.ts +25 -31
  163. package/src/utils/option-time.ts +4 -8
  164. package/src/utils/output-formatter.ts +1 -4
  165. package/src/utils/provider-capabilities.ts +1 -4
  166. package/src/utils/providers/massive.ts +59 -93
  167. package/src/utils/providers/thetadata/backfill.ts +14 -23
  168. package/src/utils/providers/thetadata/client.ts +12 -8
  169. package/src/utils/providers/thetadata/decode.ts +2 -20
  170. package/src/utils/providers/thetadata/endpoints.ts +32 -40
  171. package/src/utils/providers/thetadata/join.ts +11 -10
  172. package/src/utils/providers/thetadata/proto.ts +12 -10
  173. package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
  174. package/src/utils/providers/thetadata.ts +11 -10
  175. package/src/utils/quote-enricher.ts +4 -4
  176. package/src/utils/quote-parquet-projection.ts +3 -11
  177. package/src/utils/sample-date-selector.ts +3 -5
  178. package/src/utils/schema-metadata.ts +102 -70
  179. package/src/utils/ticker.ts +5 -9
  180. package/src/utils/trade-replay.ts +77 -68
  181. package/dist/chunk-4BLCXNQ6.js.map +0 -1
  182. package/dist/chunk-BKQ4PM4Y.js.map +0 -1
  183. package/dist/chunk-FGZH632F.js.map +0 -1
  184. package/dist/chunk-LDKTV7GW.js.map +0 -1
  185. package/dist/chunk-PRAYH3RT.js.map +0 -1
  186. package/dist/chunk-QTTR7AAW.js.map +0 -1
  187. package/dist/chunk-W2PP3LEH.js.map +0 -1
  188. package/dist/chunk-XXYOUIZY.js.map +0 -1
  189. package/dist/daily-log-processor-6MWJ23JK.js +0 -9
  190. package/dist/market-provider-VDRJUEF2.js +0 -16
  191. package/dist/trade-processor-NHU4VWRX.js +0 -9
  192. package/manifest.json +0 -141
  193. package/server/chunk-5EBXHT6C.js.map +0 -1
  194. package/server/chunk-72GKJE2U.js.map +0 -1
  195. package/server/chunk-CSDVJPBB.js.map +0 -1
  196. package/server/chunk-FBNDMCT5.js.map +0 -1
  197. package/server/chunk-NRFXAJF7.js.map +0 -1
  198. package/server/chunk-NZO6PT64.js.map +0 -1
  199. package/server/chunk-PNKG7RY7.js.map +0 -1
  200. package/server/chunk-WA5AAPCH.js.map +0 -1
  201. package/server/config-6IZXEFEX.js.map +0 -1
  202. package/server/daily-log-processor-Y3PVSVBM.js +0 -10
  203. package/server/market-provider-VOYYVYWT.js +0 -17
  204. package/server/trade-processor-JWVS37KM.js +0 -10
  205. /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-MZW2XBZY.js.map} +0 -0
  206. /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
  207. /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-6U33TQUT.js.map} +0 -0
  208. /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
  209. /package/dist/{sync-PO4IPCYV.js.map → sync-3UBC37VW.js.map} +0 -0
  210. /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-DKDCWGKD.js.map} +0 -0
  211. /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
  212. /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-B2VPU2FQ.js.map} +0 -0
  213. /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
  214. /package/server/{market-provider-VOYYVYWT.js.map → market-provider-L56RFHAC.js.map} +0 -0
  215. /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
  216. /package/server/{sync-QFI5L7S7.js.map → sync-CENKDN53.js.map} +0 -0
  217. /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-3PI4LHZI.js.map} +0 -0
  218. /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
@@ -57,6 +57,7 @@ import {
57
57
  resolveCanonicalMarketPartitionDir,
58
58
  resolveCanonicalMarketPartitionFile,
59
59
  resolveCanonicalMarketPartitionPath,
60
+ resolveDbPath,
60
61
  resolveMarketDir,
61
62
  resolveTradeTicker,
62
63
  setDataRoot,
@@ -80,7 +81,7 @@ import {
80
81
  writeParquetPartition,
81
82
  writeQuoteMinutesPartition,
82
83
  writeSpotPartition
83
- } from "./chunk-4BLCXNQ6.js";
84
+ } from "./chunk-4HZLVUOZ.js";
84
85
  import {
85
86
  BACHELIER_DTE_THRESHOLD,
86
87
  IvSolverPool,
@@ -156,42 +157,54 @@ import {
156
157
  stockHistoryOhlc,
157
158
  thetaTimestampToEtMinute,
158
159
  toMassiveTicker
159
- } from "./chunk-XXYOUIZY.js";
160
+ } from "./chunk-NWNSKALN.js";
160
161
  import {
161
162
  PortfolioStatsCalculator,
162
163
  calculateCorrelationMatrix,
164
+ holdingPeriodBlockDays,
165
+ pairedBlockBootstrap,
163
166
  performTailRiskAnalysis
164
- } from "./chunk-QTTR7AAW.js";
165
- import "./chunk-PRAYH3RT.js";
166
- import "./chunk-W2PP3LEH.js";
167
- import "./chunk-BKQ4PM4Y.js";
168
- import "./chunk-LDKTV7GW.js";
169
- import "./chunk-FGZH632F.js";
167
+ } from "./chunk-2I3S2ZLT.js";
168
+ import "./chunk-WNTYA7ER.js";
169
+ import "./chunk-ZTJXC3LS.js";
170
+ import "./chunk-YZA4RS76.js";
171
+ import "./chunk-27S67XW3.js";
172
+ import "./chunk-2E63THNI.js";
170
173
  import "./chunk-5WRI5ZAA.js";
171
174
 
172
- // src/utils/trading-dates.ts
173
- function yesterdayET(now = /* @__PURE__ */ new Date()) {
174
- const todayET = new Intl.DateTimeFormat("en-CA", {
175
- timeZone: "America/New_York",
176
- year: "numeric",
177
- month: "2-digit",
178
- day: "2-digit"
179
- }).format(now);
180
- const [y, m, d] = todayET.split("-").map(Number);
181
- const prior = new Date(Date.UTC(y, m - 1, d));
182
- prior.setUTCDate(prior.getUTCDate() - 1);
183
- const py = prior.getUTCFullYear();
184
- const pm = String(prior.getUTCMonth() + 1).padStart(2, "0");
185
- const pd = String(prior.getUTCDate()).padStart(2, "0");
186
- return `${py}-${pm}-${pd}`;
175
+ // src/tools/blocks/paired-comparison.ts
176
+ import { z } from "zod";
177
+
178
+ // src/utils/output-formatter.ts
179
+ function createToolOutput(summary, data) {
180
+ return {
181
+ content: [
182
+ { type: "text", text: summary },
183
+ {
184
+ type: "resource",
185
+ resource: {
186
+ uri: "data:application/json",
187
+ mimeType: "application/json",
188
+ text: JSON.stringify(data)
189
+ }
190
+ }
191
+ ]
192
+ };
193
+ }
194
+ function formatCurrency(value) {
195
+ const isNegative = value < 0;
196
+ const absValue = Math.abs(value);
197
+ const formatted = absValue.toLocaleString("en-US", {
198
+ minimumFractionDigits: 2,
199
+ maximumFractionDigits: 2
200
+ });
201
+ return isNegative ? `-$${formatted}` : `$${formatted}`;
187
202
  }
188
203
 
189
204
  // src/tools/shared/filters.ts
190
205
  function filterByStrategy(trades, strategy) {
191
206
  if (!strategy) return trades;
192
- return trades.filter(
193
- (t) => t.strategy.toLowerCase() === strategy.toLowerCase()
194
- );
207
+ return trades.filter((t) => t.strategy.toLowerCase() === strategy.toLowerCase());
195
208
  }
196
209
  var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
197
210
  function validateDateParam(date) {
@@ -234,6 +247,294 @@ function filterDailyLogsByDateRange(dailyLogs, startDate, endDate) {
234
247
  return filtered;
235
248
  }
236
249
 
250
+ // src/tools/middleware/sync-middleware.ts
251
+ function withSyncedBlock(baseDir, handler) {
252
+ return async (input) => {
253
+ await upgradeToReadWrite(baseDir, { fallbackToReadOnly: true });
254
+ let syncResult;
255
+ if (getConnectionMode() === "read_write") {
256
+ try {
257
+ syncResult = await syncBlock(input.blockId, baseDir);
258
+ } finally {
259
+ await downgradeToReadOnly(baseDir);
260
+ }
261
+ } else {
262
+ syncResult = { blockId: input.blockId, status: "unchanged" };
263
+ }
264
+ if (syncResult.status === "deleted") {
265
+ return {
266
+ content: [
267
+ {
268
+ type: "text",
269
+ text: `Block '${input.blockId}' no longer exists (folder was deleted). Call list_blocks to see available blocks.`
270
+ }
271
+ ],
272
+ isError: true
273
+ };
274
+ }
275
+ if (syncResult.status === "error" && syncResult.error) {
276
+ return {
277
+ content: [
278
+ {
279
+ type: "text",
280
+ text: `Sync error for block '${input.blockId}': ${syncResult.error}. Call list_blocks to see available blocks.`
281
+ }
282
+ ],
283
+ isError: true
284
+ };
285
+ }
286
+ return handler(input, { syncResult, baseDir });
287
+ };
288
+ }
289
+
290
+ // src/tools/blocks/paired-comparison.ts
291
+ var ATTRIBUTION_NOTE = "A trade's P&L is attributed evenly across the trading days it was open (dateOpened through dateClosed). Trading days are derived from the block's own trade data (the union of every trade's open and close dates) -- no market calendar is assumed, so weekends or holidays appear only if a trade spans them.";
292
+ var DEFAULT_CI_LEVEL = 0.95;
293
+ var DEFAULT_RESAMPLES = 2e3;
294
+ var DEFAULT_SEED = 42;
295
+ var SENSITIVITY_MULTIPLIERS = [0.5, 2];
296
+ var pairedComparisonInputSchema = z.object({
297
+ blockId: z.string().describe("Block folder name to analyze"),
298
+ strategyA: z.string().describe("Strategy name for arm A (case-insensitive)"),
299
+ strategyB: z.string().optional().describe(
300
+ "Strategy name for arm B (case-insensitive). If omitted, arm A is compared against a constant 0 -- i.e. is the edge distinguishable from nothing."
301
+ ),
302
+ statistic: z.enum(["mean_daily_pnl", "median_daily_pnl"]).default("mean_daily_pnl").describe("Which daily-P&L functional to compare. Default mean_daily_pnl."),
303
+ dateRange: z.object({
304
+ start: z.string().describe("Start date (YYYY-MM-DD)"),
305
+ end: z.string().describe("End date (YYYY-MM-DD)")
306
+ }).optional().describe("Optional date range filter applied to both arms (by trade open date)."),
307
+ ciLevel: z.number().default(DEFAULT_CI_LEVEL).describe("Confidence level in (0, 1). Default 0.95 (deterministic)."),
308
+ resamples: z.number().int().default(DEFAULT_RESAMPLES).describe("Number of bootstrap resamples. Default 2000 (deterministic)."),
309
+ seed: z.number().int().default(DEFAULT_SEED).describe("PRNG seed. Same inputs + seed -> identical result. Default 42 (deterministic)."),
310
+ blockDays: z.number().int().min(1).optional().describe(
311
+ "Override the block length. By default it is derived from this block's own holding-period distribution (95th percentile of days-open)."
312
+ ),
313
+ effectiveNFloorBlocks: z.number().min(0).optional().describe(
314
+ "Optional: refuse with notComparable when the shared overlap yields fewer distinct blocks than this floor. When omitted, only structural degeneracy refuses (underpowered)."
315
+ )
316
+ });
317
+ function toCalendarDateStr2(date) {
318
+ const y = date.getUTCFullYear();
319
+ const m = String(date.getUTCMonth() + 1).padStart(2, "0");
320
+ const d = String(date.getUTCDate()).padStart(2, "0");
321
+ return `${y}-${m}-${d}`;
322
+ }
323
+ function buildBlockTradingDayIndex(trades) {
324
+ const days = /* @__PURE__ */ new Set();
325
+ for (const t of trades) {
326
+ days.add(toCalendarDateStr2(t.dateOpened));
327
+ days.add(toCalendarDateStr2(t.dateClosed ?? t.dateOpened));
328
+ }
329
+ return Array.from(days).sort();
330
+ }
331
+ function gridSpan(grid, openStr, closeStr) {
332
+ let lo = grid.findIndex((d) => d >= openStr);
333
+ if (lo === -1) lo = grid.length;
334
+ let hi = -1;
335
+ for (let i = grid.length - 1; i >= 0; i--) {
336
+ if (grid[i] <= closeStr) {
337
+ hi = i;
338
+ break;
339
+ }
340
+ }
341
+ return [lo, hi];
342
+ }
343
+ function armHoldingPeriods(armTrades, grid) {
344
+ return armTrades.map((t) => {
345
+ const openStr = toCalendarDateStr2(t.dateOpened);
346
+ const closeStr = toCalendarDateStr2(t.dateClosed ?? t.dateOpened);
347
+ const [lo, hi] = gridSpan(grid, openStr, closeStr);
348
+ return Math.max(1, hi - lo + 1);
349
+ });
350
+ }
351
+ function buildArmDaySeries(armTrades, grid) {
352
+ const values = new Array(grid.length).fill(0);
353
+ const observedMask = new Array(grid.length).fill(false);
354
+ for (const t of armTrades) {
355
+ const openStr = toCalendarDateStr2(t.dateOpened);
356
+ const closeStr = toCalendarDateStr2(t.dateClosed ?? t.dateOpened);
357
+ const [lo, hi] = gridSpan(grid, openStr, closeStr);
358
+ if (hi < lo) continue;
359
+ const span = hi - lo + 1;
360
+ const perDay = t.pl / span;
361
+ for (let i = lo; i <= hi; i++) {
362
+ values[i] += perDay;
363
+ observedMask[i] = true;
364
+ }
365
+ }
366
+ return { index: grid, values, observedMask };
367
+ }
368
+ function countObserved(series) {
369
+ return series.observedMask.reduce((n, o) => n + (o ? 1 : 0), 0);
370
+ }
371
+ function meanOf(values) {
372
+ return values.reduce((s, v) => s + v, 0) / values.length;
373
+ }
374
+ function medianOf(values) {
375
+ const sorted = [...values].sort((a, b) => a - b);
376
+ const mid = Math.floor(sorted.length / 2);
377
+ return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
378
+ }
379
+ var STATISTIC_FNS = {
380
+ mean_daily_pnl: meanOf,
381
+ median_daily_pnl: medianOf
382
+ };
383
+ var STATISTIC_LABELS = {
384
+ mean_daily_pnl: "mean daily P&L",
385
+ median_daily_pnl: "median daily P&L"
386
+ };
387
+ function runPairedBootstrapComparison(params) {
388
+ const {
389
+ armATrades,
390
+ armBTrades,
391
+ blockTrades,
392
+ statistic,
393
+ ciLevel,
394
+ resamples,
395
+ seed,
396
+ blockDays: blockDaysOverride,
397
+ effectiveNFloorBlocks,
398
+ strategyA,
399
+ strategyB
400
+ } = params;
401
+ if (armATrades.length === 0) {
402
+ throw new Error(`No trades found for strategy "${strategyA}".`);
403
+ }
404
+ if (armBTrades !== null && armBTrades.length === 0) {
405
+ throw new Error(`No trades found for strategy "${strategyB}".`);
406
+ }
407
+ const grid = buildBlockTradingDayIndex(blockTrades);
408
+ const armA = buildArmDaySeries(armATrades, grid);
409
+ const armB = armBTrades !== null ? buildArmDaySeries(armBTrades, grid) : null;
410
+ const hpA = holdingPeriodBlockDays(armHoldingPeriods(armATrades, grid));
411
+ const hpB = armBTrades !== null ? holdingPeriodBlockDays(armHoldingPeriods(armBTrades, grid)) : hpA;
412
+ const derivedBlockDays = Math.max(hpA, hpB);
413
+ const derivedFromArm = hpB > hpA ? "B" : "A";
414
+ const blockDays = blockDaysOverride ?? derivedBlockDays;
415
+ const blockDaysNote = blockDaysOverride !== void 0 ? `block length = ${blockDays} trading day(s), caller-supplied override` : armB !== null ? `block length = ${blockDays} trading day(s), derived from the 95th-percentile holding period of arm ${derivedFromArm} (the longer-held arm -- the conservative choice)` : `block length = ${blockDays} trading day(s), derived from the 95th-percentile holding period of arm A`;
416
+ const result = pairedBlockBootstrap({
417
+ armA,
418
+ armB: armB ?? { constant: 0 },
419
+ statistic: STATISTIC_FNS[statistic],
420
+ holdingRule: { blockDays, sensitivity: SENSITIVITY_MULTIPLIERS },
421
+ ciLevel,
422
+ resamples,
423
+ seed,
424
+ effectiveNFloorBlocks
425
+ });
426
+ const statLabel = STATISTIC_LABELS[statistic];
427
+ const comparisonLabel = armB !== null ? `${strategyA} minus ${strategyB}` : `${strategyA} vs 0`;
428
+ let summary;
429
+ let refusalMessage = null;
430
+ if (result.status === "notComparable") {
431
+ refusalMessage = `Not comparable: effective sample (${result.effectiveN.toFixed(2)} blocks) is below the required power floor. A confidence interval is withheld.`;
432
+ summary = `Paired comparison (${comparisonLabel}): ${refusalMessage}`;
433
+ } else if (result.status === "underpowered") {
434
+ refusalMessage = `Underpowered: the shared overlap is too short to resample at a block length of ${blockDays} trading day(s) (fewer than two drawable blocks). A confidence interval is withheld.`;
435
+ summary = `Paired comparison (${comparisonLabel}): ${refusalMessage}`;
436
+ } else {
437
+ const point = result.point ?? 0;
438
+ const low = result.ci.low ?? 0;
439
+ const high = result.ci.high ?? 0;
440
+ const includesZero = low <= 0 && high >= 0;
441
+ const zeroClause = includesZero ? "includes zero (not distinguishable)" : "excludes zero (distinguishable)";
442
+ const subject = armB !== null ? `the difference in ${statLabel} (${comparisonLabel})` : `${statLabel}`;
443
+ summary = `Paired comparison: ${subject} is ${formatCurrency(point)}, ${(ciLevel * 100).toFixed(0)}% CI [${formatCurrency(low)}, ${formatCurrency(high)}] -- ${zeroClause}.`;
444
+ }
445
+ const data = {
446
+ comparison: {
447
+ strategyA,
448
+ strategyB: strategyB ?? null,
449
+ mode: armB !== null ? "two-arm" : "single-arm-vs-zero",
450
+ description: comparisonLabel
451
+ },
452
+ statistic,
453
+ point: result.point,
454
+ ci: result.ci,
455
+ status: result.status,
456
+ refusal: refusalMessage,
457
+ effectiveN: result.effectiveN,
458
+ blockDays: result.blockDays,
459
+ blockDaysDerivation: blockDaysNote,
460
+ overlapWindow: result.overlapWindow,
461
+ observedDays: {
462
+ armA: countObserved(armA),
463
+ armB: armB !== null ? countObserved(armB) : null
464
+ },
465
+ tradeCounts: {
466
+ armA: armATrades.length,
467
+ armB: armBTrades !== null ? armBTrades.length : null
468
+ },
469
+ sensitivity: result.sensitivity,
470
+ resamples,
471
+ seed: result.seed,
472
+ ciLevel,
473
+ attributionMethodology: ATTRIBUTION_NOTE
474
+ };
475
+ return { summary, data, result };
476
+ }
477
+ function registerPairedComparisonTool(server, baseDir) {
478
+ server.registerTool(
479
+ "paired_bootstrap_comparison",
480
+ {
481
+ description: "Honest confidence intervals for 'is strategy A actually different from strategy B (or from zero)' -- day-block resampling that accounts for multi-day positions, dormant periods, and paired comparison on shared days. " + ATTRIBUTION_NOTE,
482
+ inputSchema: pairedComparisonInputSchema
483
+ },
484
+ withSyncedBlock(baseDir, async (input) => {
485
+ try {
486
+ const { blockId, strategyA, strategyB, statistic, dateRange } = input;
487
+ const block = await loadBlock(baseDir, blockId);
488
+ const inRange = (trades) => dateRange ? filterByDateRange(trades, dateRange.start, dateRange.end) : trades;
489
+ const blockTrades = inRange(block.trades);
490
+ const armATrades = inRange(filterByStrategy(block.trades, strategyA));
491
+ const armBTrades = strategyB !== void 0 ? inRange(filterByStrategy(block.trades, strategyB)) : null;
492
+ const { summary, data } = runPairedBootstrapComparison({
493
+ armATrades,
494
+ armBTrades,
495
+ blockTrades,
496
+ statistic,
497
+ ciLevel: input.ciLevel,
498
+ resamples: input.resamples,
499
+ seed: input.seed,
500
+ blockDays: input.blockDays,
501
+ effectiveNFloorBlocks: input.effectiveNFloorBlocks,
502
+ strategyA,
503
+ strategyB
504
+ });
505
+ return createToolOutput(summary, { blockId, ...data });
506
+ } catch (error) {
507
+ return {
508
+ content: [
509
+ {
510
+ type: "text",
511
+ text: `Error running paired bootstrap comparison: ${error.message}`
512
+ }
513
+ ],
514
+ isError: true
515
+ };
516
+ }
517
+ })
518
+ );
519
+ }
520
+
521
+ // src/utils/trading-dates.ts
522
+ function yesterdayET(now = /* @__PURE__ */ new Date()) {
523
+ const todayET = new Intl.DateTimeFormat("en-CA", {
524
+ timeZone: "America/New_York",
525
+ year: "numeric",
526
+ month: "2-digit",
527
+ day: "2-digit"
528
+ }).format(now);
529
+ const [y, m, d] = todayET.split("-").map(Number);
530
+ const prior = new Date(Date.UTC(y, m - 1, d));
531
+ prior.setUTCDate(prior.getUTCDate() - 1);
532
+ const py = prior.getUTCFullYear();
533
+ const pm = String(prior.getUTCMonth() + 1).padStart(2, "0");
534
+ const pd = String(prior.getUTCDate()).padStart(2, "0");
535
+ return `${py}-${pm}-${pd}`;
536
+ }
537
+
237
538
  // src/utils/schema-metadata.ts
238
539
  var SCHEMA_DESCRIPTIONS = {
239
540
  trades: {
@@ -798,10 +1099,22 @@ var VIX_OHLCV_MAPPINGS = [
798
1099
  { alias: "VIX_Low", tableAlias: "vix", sourceCol: "low", ticker: "VIX", timing: "close" },
799
1100
  // VIX9D
800
1101
  { alias: "VIX9D_Open", tableAlias: "vix9d", sourceCol: "open", ticker: "VIX9D", timing: "open" },
801
- { alias: "VIX9D_Close", tableAlias: "vix9d", sourceCol: "close", ticker: "VIX9D", timing: "close" },
1102
+ {
1103
+ alias: "VIX9D_Close",
1104
+ tableAlias: "vix9d",
1105
+ sourceCol: "close",
1106
+ ticker: "VIX9D",
1107
+ timing: "close"
1108
+ },
802
1109
  // VIX3M
803
1110
  { alias: "VIX3M_Open", tableAlias: "vix3m", sourceCol: "open", ticker: "VIX3M", timing: "open" },
804
- { alias: "VIX3M_Close", tableAlias: "vix3m", sourceCol: "close", ticker: "VIX3M", timing: "close" }
1111
+ {
1112
+ alias: "VIX3M_Close",
1113
+ tableAlias: "vix3m",
1114
+ sourceCol: "close",
1115
+ ticker: "VIX3M",
1116
+ timing: "close"
1117
+ }
805
1118
  ];
806
1119
  var VIX_ENRICHED_MAPPINGS = [
807
1120
  { alias: "VIX_IVR", tableAlias: "evix", sourceCol: "ivr", ticker: "VIX", timing: "close" },
@@ -811,10 +1124,7 @@ var VIX_ENRICHED_MAPPINGS = [
811
1124
  { alias: "VIX3M_IVR", tableAlias: "evix3m", sourceCol: "ivr", ticker: "VIX3M", timing: "close" },
812
1125
  { alias: "VIX3M_IVP", tableAlias: "evix3m", sourceCol: "ivp", ticker: "VIX3M", timing: "close" }
813
1126
  ];
814
- var VIX_ALL_MAPPINGS = [
815
- ...VIX_OHLCV_MAPPINGS,
816
- ...VIX_ENRICHED_MAPPINGS
817
- ];
1127
+ var VIX_ALL_MAPPINGS = [...VIX_OHLCV_MAPPINGS, ...VIX_ENRICHED_MAPPINGS];
818
1128
  var VIX_TICKER_ALIASES = [...new Set(VIX_OHLCV_MAPPINGS.map((m) => m.tableAlias))];
819
1129
  var VIX_TICKER_FOR_ALIAS = Object.fromEntries([
820
1130
  ...VIX_OHLCV_MAPPINGS.map((m) => [m.tableAlias, m.ticker]),
@@ -851,9 +1161,7 @@ var CLOSE_KNOWN_FIELDS = /* @__PURE__ */ new Set([
851
1161
  ...DAILY_CLOSE_FIELDS,
852
1162
  ...CONTEXT_CLOSE_FIELDS
853
1163
  ]);
854
- var STATIC_FIELDS = /* @__PURE__ */ new Set([
855
- ...DAILY_STATIC_FIELDS
856
- ]);
1164
+ var STATIC_FIELDS = /* @__PURE__ */ new Set([...DAILY_STATIC_FIELDS]);
857
1165
  function buildVixJoinClause(tickerAliases, baseAlias = "d") {
858
1166
  return tickerAliases.flatMap((alias) => {
859
1167
  const ticker = VIX_TICKER_FOR_ALIAS[alias];
@@ -978,7 +1286,11 @@ function buildOutcomeQuery(tradeDatesOrKeys) {
978
1286
  if (typeof tradeDatesOrKeys[0] === "string") {
979
1287
  return buildOutcomeQueryForDates(tradeDatesOrKeys, vixCloseCols, derivedCloseCols);
980
1288
  }
981
- return buildOutcomeQueryForKeys(tradeDatesOrKeys, vixCloseCols, derivedCloseCols);
1289
+ return buildOutcomeQueryForKeys(
1290
+ tradeDatesOrKeys,
1291
+ vixCloseCols,
1292
+ derivedCloseCols
1293
+ );
982
1294
  }
983
1295
  function buildTargetCloseCols(eAlias, sAlias) {
984
1296
  return [...DAILY_CLOSE_FIELDS].map((f) => `${OHLCV_COLS.has(f) ? sAlias : eAlias}."${f}"`).join(", ");
@@ -1098,11 +1410,11 @@ function scoreDataQuality(input) {
1098
1410
  async function queryCoverage(stores, underlying, fromDate, toDate) {
1099
1411
  const spotCov = await stores.spot.getCoverage(underlying, fromDate, toDate);
1100
1412
  const quoteCov = await stores.quote.getCoverage(underlying, fromDate, toDate);
1101
- const dates = enumerateCoveredDates(spotCov.earliest, spotCov.latest).concat(enumerateCoveredDates(quoteCov.earliest, quoteCov.latest));
1102
- const uniqueDates = [...new Set(dates)].sort();
1103
- const quoteDateSet = new Set(
1413
+ const dates = enumerateCoveredDates(spotCov.earliest, spotCov.latest).concat(
1104
1414
  enumerateCoveredDates(quoteCov.earliest, quoteCov.latest)
1105
1415
  );
1416
+ const uniqueDates = [...new Set(dates)].sort();
1417
+ const quoteDateSet = new Set(enumerateCoveredDates(quoteCov.earliest, quoteCov.latest));
1106
1418
  if (uniqueDates.length === 0) {
1107
1419
  return {
1108
1420
  totalBars: 0,
@@ -1168,9 +1480,7 @@ function formatCoverageReport(tickerPattern, coverage) {
1168
1480
  }
1169
1481
  if (current) groups.push(current);
1170
1482
  for (const group of groups) {
1171
- const avgBars = group.barCounts.length > 0 ? Math.round(
1172
- group.barCounts.reduce((s, v) => s + v, 0) / group.barCounts.length
1173
- ) : 0;
1483
+ const avgBars = group.barCounts.length > 0 ? Math.round(group.barCounts.reduce((s, v) => s + v, 0) / group.barCounts.length) : 0;
1174
1484
  const rangeStr = group.fromDate === group.toDate ? group.fromDate : `${group.fromDate} to ${group.toDate}`;
1175
1485
  let detail;
1176
1486
  if (group.density === "dense") {
@@ -1445,7 +1755,9 @@ async function setupParquetWorkingTables(conn, dataDir) {
1445
1755
  const enrichedTickerGlob = path.join(enrichedDir, "ticker=*", "data.parquet");
1446
1756
  const enrichedContextPath = path.join(enrichedDir, "context", "data.parquet");
1447
1757
  if (existsSync(dailyPath)) {
1448
- await conn.run(`CREATE TEMP TABLE "${dailyTable}" AS SELECT * FROM read_parquet('${dailyPath}')`);
1758
+ await conn.run(
1759
+ `CREATE TEMP TABLE "${dailyTable}" AS SELECT * FROM read_parquet('${dailyPath}')`
1760
+ );
1449
1761
  await alignDailyWorkingTableColumns(conn, dailyTable);
1450
1762
  } else if (hasEnrichedTickerFiles(enrichedDir)) {
1451
1763
  await conn.run(
@@ -1460,9 +1772,7 @@ async function setupParquetWorkingTables(conn, dataDir) {
1460
1772
  }
1461
1773
  await alignDailyWorkingTableColumns(conn, dailyTable);
1462
1774
  } else {
1463
- await conn.run(
1464
- `CREATE TEMP TABLE "${dailyTable}" AS SELECT * FROM market.enriched WHERE 1=0`
1465
- );
1775
+ await conn.run(`CREATE TEMP TABLE "${dailyTable}" AS SELECT * FROM market.enriched WHERE 1=0`);
1466
1776
  for (const ohlcv of ["open", "high", "low", "close"]) {
1467
1777
  try {
1468
1778
  await conn.run(`ALTER TABLE "${dailyTable}" ADD COLUMN "${ohlcv}" DOUBLE`);
@@ -1485,7 +1795,9 @@ async function setupParquetWorkingTables(conn, dataDir) {
1485
1795
  } catch {
1486
1796
  }
1487
1797
  if (existsSync(dateContextPath)) {
1488
- await conn.run(`CREATE TEMP TABLE "${dateContextTable}" AS SELECT * FROM read_parquet('${dateContextPath}')`);
1798
+ await conn.run(
1799
+ `CREATE TEMP TABLE "${dateContextTable}" AS SELECT * FROM read_parquet('${dateContextPath}')`
1800
+ );
1489
1801
  } else if (existsSync(enrichedContextPath)) {
1490
1802
  await conn.run(
1491
1803
  `CREATE TEMP TABLE "${dateContextTable}" AS SELECT * FROM read_parquet('${enrichedContextPath}')`
@@ -1496,7 +1808,9 @@ async function setupParquetWorkingTables(conn, dataDir) {
1496
1808
  Trend_Direction VARCHAR, VIX_Spike_Pct DOUBLE, VIX_Gap_Pct DOUBLE
1497
1809
  )`);
1498
1810
  }
1499
- await conn.run(`CREATE UNIQUE INDEX "idx_${dateContextTable}_date" ON "${dateContextTable}"(date)`);
1811
+ await conn.run(
1812
+ `CREATE UNIQUE INDEX "idx_${dateContextTable}_date" ON "${dateContextTable}"(date)`
1813
+ );
1500
1814
  await conn.run(`CREATE UNIQUE INDEX "idx_${dailyTable}_pk" ON "${dailyTable}"(ticker, date)`);
1501
1815
  return { dailyTable, dateContextTable };
1502
1816
  }
@@ -1523,12 +1837,7 @@ async function flushEnrichedToParquet(conn, dataDir, ticker, tables) {
1523
1837
  targetPath: tickerFile,
1524
1838
  selectQuery: `SELECT ticker, date, ${enrichedColList} FROM "${tables.dailyTable}" WHERE ticker = '${ticker}' ORDER BY date`
1525
1839
  });
1526
- const contextFile = path.join(
1527
- resolveMarketDir(dataDir),
1528
- "enriched",
1529
- "context",
1530
- "data.parquet"
1531
- );
1840
+ const contextFile = path.join(resolveMarketDir(dataDir), "enriched", "context", "data.parquet");
1532
1841
  await writeParquetAtomic(conn, {
1533
1842
  targetPath: contextFile,
1534
1843
  selectQuery: `SELECT * FROM "${tables.dateContextTable}" ORDER BY date`
@@ -1564,7 +1873,9 @@ async function runTier2(conn, targets, spotStore) {
1564
1873
  const BATCH_SIZE = 500;
1565
1874
  for (let start = 0; start < bars.length; start += BATCH_SIZE) {
1566
1875
  const batch = bars.slice(start, start + BATCH_SIZE);
1567
- const placeholders = batch.map((_, i) => `($${i * 6 + 1},$${i * 6 + 2},$${i * 6 + 3},$${i * 6 + 4},$${i * 6 + 5},$${i * 6 + 6})`).join(",");
1876
+ const placeholders = batch.map(
1877
+ (_, i) => `($${i * 6 + 1},$${i * 6 + 2},$${i * 6 + 3},$${i * 6 + 4},$${i * 6 + 5},$${i * 6 + 6})`
1878
+ ).join(",");
1568
1879
  const params = batch.flatMap((b) => [b.ticker, b.date, b.open, b.high, b.low, b.close]);
1569
1880
  await conn.run(
1570
1881
  `INSERT INTO "${vixTempTable}" VALUES ${placeholders}`,
@@ -1696,12 +2007,21 @@ async function runTier2(conn, targets, spotStore) {
1696
2007
  };
1697
2008
  });
1698
2009
  const enrichedContext = computeVIXDerivedFields(contextRows);
1699
- const derivedCols = ["date", "Vol_Regime", "Term_Structure_State", "Trend_Direction", "VIX_Spike_Pct", "VIX_Gap_Pct"];
2010
+ const derivedCols = [
2011
+ "date",
2012
+ "Vol_Regime",
2013
+ "Term_Structure_State",
2014
+ "Trend_Direction",
2015
+ "VIX_Spike_Pct",
2016
+ "VIX_Gap_Pct"
2017
+ ];
1700
2018
  const BATCH_SIZE = 500;
1701
2019
  for (let start = 0; start < enrichedContext.length; start += BATCH_SIZE) {
1702
2020
  const batch = enrichedContext.slice(start, start + BATCH_SIZE);
1703
2021
  const placeholders = batch.map((_, rowIdx) => {
1704
- const params2 = derivedCols.map((__, colIdx) => `$${rowIdx * derivedCols.length + colIdx + 1}`);
2022
+ const params2 = derivedCols.map(
2023
+ (__, colIdx) => `$${rowIdx * derivedCols.length + colIdx + 1}`
2024
+ );
1705
2025
  return `(${params2.join(", ")})`;
1706
2026
  }).join(", ");
1707
2027
  const sql = `INSERT OR REPLACE INTO ${dateContextTarget} (${derivedCols.join(", ")}) VALUES ${placeholders}`;
@@ -1735,10 +2055,9 @@ async function hasTier3Data(conn, ticker, spotStore) {
1735
2055
  const cov = await spotStore.getCoverage(ticker, "1970-01-01", "9999-12-31");
1736
2056
  return cov.totalDates > 0;
1737
2057
  }
1738
- const r = await conn.runAndReadAll(
1739
- `SELECT COUNT(*) FROM market.spot WHERE ticker = $1 LIMIT 1`,
1740
- [ticker]
1741
- );
2058
+ const r = await conn.runAndReadAll(`SELECT COUNT(*) FROM market.spot WHERE ticker = $1 LIMIT 1`, [
2059
+ ticker
2060
+ ]);
1742
2061
  return Number(r.getRows()[0]?.[0] ?? 0) > 0;
1743
2062
  }
1744
2063
  async function runContextEnrichment(conn, targets) {
@@ -1778,7 +2097,10 @@ async function runEnrichment(conn, ticker, opts = {}, io) {
1778
2097
  fetchParams.push(lookbackStart);
1779
2098
  }
1780
2099
  fetchSql += ` ORDER BY date ASC`;
1781
- const rawReader = await conn.runAndReadAll(fetchSql, fetchParams);
2100
+ const rawReader = await conn.runAndReadAll(
2101
+ fetchSql,
2102
+ fetchParams
2103
+ );
1782
2104
  rawRows = rawReader.getRows();
1783
2105
  }
1784
2106
  if (rawRows.length === 0) {
@@ -1995,7 +2317,8 @@ function computeIntradayTimingFields(bars) {
1995
2317
  const highInAfternoon = highTime >= 12;
1996
2318
  const lowInAfternoon = lowTime >= 12;
1997
2319
  let reversalType = 0;
1998
- if (highInMorning && lowInAfternoon) reversalType = 1;
2320
+ if (highInMorning && lowInAfternoon)
2321
+ reversalType = 1;
1999
2322
  else if (lowInMorning && highInAfternoon) reversalType = -1;
2000
2323
  const openingBars = bars.filter((b) => hhmmToDecimalHours(b.time) < 10);
2001
2324
  let openingDriveStrength = 0;
@@ -2020,7 +2343,14 @@ function computeIntradayTimingFields(bars) {
2020
2343
  intradayRealizedVol = barStdDev * Math.sqrt(bars.length * 252);
2021
2344
  }
2022
2345
  }
2023
- return { highTime, lowTime, highBeforeLow, reversalType, openingDriveStrength, intradayRealizedVol };
2346
+ return {
2347
+ highTime,
2348
+ lowTime,
2349
+ highBeforeLow,
2350
+ reversalType,
2351
+ openingDriveStrength,
2352
+ intradayRealizedVol
2353
+ };
2024
2354
  }
2025
2355
  async function runTier3(conn, ticker, dates, dailyTarget = "market.enriched", spotStore) {
2026
2356
  const hasData = await hasTier3Data(conn, ticker, spotStore);
@@ -2086,7 +2416,14 @@ async function runTier3(conn, ticker, dates, dailyTarget = "market.enriched", sp
2086
2416
  reason: "intraday data exists but no bars overlap with enrichment date range"
2087
2417
  };
2088
2418
  }
2089
- const tier3Cols = ["High_Time", "Low_Time", "High_Before_Low", "Reversal_Type", "Opening_Drive_Strength", "Intraday_Realized_Vol"];
2419
+ const tier3Cols = [
2420
+ "High_Time",
2421
+ "Low_Time",
2422
+ "High_Before_Low",
2423
+ "Reversal_Type",
2424
+ "Opening_Drive_Strength",
2425
+ "Intraday_Realized_Vol"
2426
+ ];
2090
2427
  const enrichedRows = [];
2091
2428
  for (const [dateStr, bars] of barsByDate) {
2092
2429
  const timing = computeIntradayTimingFields(bars);
@@ -2205,7 +2542,9 @@ function applyColumnMapping(rows, columnMapping, ticker) {
2205
2542
  if (hasNullDate) continue;
2206
2543
  if (!("date" in mapped)) continue;
2207
2544
  if (!("time" in mapped)) {
2208
- const dateSourceCol = Object.entries(columnMapping).find(([, schema]) => schema === "date")?.[0];
2545
+ const dateSourceCol = Object.entries(columnMapping).find(
2546
+ ([, schema]) => schema === "date"
2547
+ )?.[0];
2209
2548
  if (dateSourceCol) {
2210
2549
  const rawDateValue = row[dateSourceCol] ?? "";
2211
2550
  const numericDate = Number(rawDateValue);
@@ -2553,134 +2892,126 @@ function buildFilterPredicate(filter) {
2553
2892
  }
2554
2893
 
2555
2894
  // src/tools/profiles.ts
2556
- import { z } from "zod";
2557
-
2558
- // src/utils/output-formatter.ts
2559
- function createToolOutput(summary, data) {
2560
- return {
2561
- content: [
2562
- { type: "text", text: summary },
2563
- {
2564
- type: "resource",
2565
- resource: {
2566
- uri: "data:application/json",
2567
- mimeType: "application/json",
2568
- text: JSON.stringify(data)
2569
- }
2570
- }
2571
- ]
2572
- };
2573
- }
2574
-
2575
- // src/tools/profiles.ts
2576
- var profileStrategySchema = z.object({
2577
- blockId: z.string().describe("Block ID (block_id) to associate the profile with"),
2578
- strategyName: z.string().describe("Human-readable strategy name (e.g., 'Pickle RIC v2')"),
2579
- structureType: z.string().describe(
2895
+ import { z as z2 } from "zod";
2896
+ var profileStrategySchema = z2.object({
2897
+ blockId: z2.string().describe("Block ID (block_id) to associate the profile with"),
2898
+ strategyName: z2.string().describe("Human-readable strategy name (e.g., 'Pickle RIC v2')"),
2899
+ structureType: z2.string().describe(
2580
2900
  "Option structure type: iron_condor, calendar_spread, double_calendar, vertical_spread, butterfly, reverse_iron_condor, short_put_spread, short_call_spread, straddle, strangle, etc."
2581
2901
  ),
2582
- greeksBias: z.string().describe(
2902
+ greeksBias: z2.string().describe(
2583
2903
  "Primary greeks exposure: theta_positive, vega_negative, delta_neutral, delta_positive, delta_negative, gamma_scalp, etc."
2584
2904
  ),
2585
- thesis: z.string().default("").describe("Free-text description of the strategy thesis"),
2586
- legs: z.array(
2587
- z.object({
2588
- type: z.string().describe("Leg type: long_put, short_call, long_call, short_put, etc."),
2589
- strike: z.string().describe("Strike selection: ATM, 5-delta, 30-delta, etc."),
2590
- expiry: z.string().describe("Expiry selection: same-day, weekly, 45-DTE, etc."),
2591
- quantity: z.number().describe("Quantity (positive=long, negative=short)"),
2592
- strikeMethod: z.enum(["delta", "dollar_price", "offset", "percentage"]).optional().describe("How strike is selected"),
2593
- strikeValue: z.number().optional().describe("Numeric strike value (e.g., 25 for 25-delta)")
2905
+ thesis: z2.string().default("").describe("Free-text description of the strategy thesis"),
2906
+ legs: z2.array(
2907
+ z2.object({
2908
+ type: z2.string().describe("Leg type: long_put, short_call, long_call, short_put, etc."),
2909
+ strike: z2.string().describe("Strike selection: ATM, 5-delta, 30-delta, etc."),
2910
+ expiry: z2.string().describe("Expiry selection: same-day, weekly, 45-DTE, etc."),
2911
+ quantity: z2.number().describe("Quantity (positive=long, negative=short)"),
2912
+ strikeMethod: z2.enum(["delta", "dollar_price", "offset", "percentage"]).optional().describe("How strike is selected"),
2913
+ strikeValue: z2.number().optional().describe("Numeric strike value (e.g., 25 for 25-delta)")
2594
2914
  })
2595
2915
  ).default([]).describe("Structured leg descriptions"),
2596
- entryFilters: z.array(
2597
- z.object({
2598
- field: z.string().describe("Market data field: VIX_Close, RSI_14, Vol_Regime, etc."),
2599
- operator: z.string().describe("Comparison operator: >, <, >=, <=, ==, between, in"),
2600
- value: z.union([z.string(), z.number(), z.array(z.union([z.string(), z.number()]))]).describe("Filter value or array for between/in operators"),
2601
- description: z.string().optional().describe("Human-readable description of this filter"),
2602
- source: z.enum(["market", "execution"]).optional().describe("Filter source: 'market' = testable against market data columns, 'execution' = platform-level (time windows, leg ratios). Defaults to 'market'. Execution filters are documented but skipped during validate_entry_filters analysis.")
2916
+ entryFilters: z2.array(
2917
+ z2.object({
2918
+ field: z2.string().describe("Market data field: VIX_Close, RSI_14, Vol_Regime, etc."),
2919
+ operator: z2.string().describe("Comparison operator: >, <, >=, <=, ==, between, in"),
2920
+ value: z2.union([z2.string(), z2.number(), z2.array(z2.union([z2.string(), z2.number()]))]).describe("Filter value or array for between/in operators"),
2921
+ description: z2.string().optional().describe("Human-readable description of this filter"),
2922
+ source: z2.enum(["market", "execution"]).optional().describe(
2923
+ "Filter source: 'market' = testable against market data columns, 'execution' = platform-level (time windows, leg ratios). Defaults to 'market'. Execution filters are documented but skipped during validate_entry_filters analysis."
2924
+ )
2603
2925
  })
2604
- ).default([]).describe("Entry condition filters. Tag each with source: 'market' (testable in analysis) or 'execution' (OO/platform-level, skipped in analysis)."),
2605
- exitRules: z.array(
2606
- z.object({
2607
- type: z.string().describe("Rule type: stop_loss, profit_target, time_exit, conditional"),
2608
- trigger: z.string().describe("Trigger condition: '200% of credit', '50% of max profit', '15:00 ET'"),
2609
- description: z.string().optional().describe("Human-readable description"),
2610
- stopLossType: z.enum(["percentage", "dollar", "sl_ratio", "debit_percentage"]).optional().describe("Stop loss calculation method"),
2611
- stopLossValue: z.number().optional().describe("Stop loss numeric value"),
2612
- monitoring: z.object({
2613
- granularity: z.enum(["intra_minute", "candle_close", "end_of_bar"]).optional().describe("Price check frequency"),
2614
- priceSource: z.enum(["nbbo", "mid", "last"]).optional().describe("Which price to use")
2926
+ ).default([]).describe(
2927
+ "Entry condition filters. Tag each with source: 'market' (testable in analysis) or 'execution' (OO/platform-level, skipped in analysis)."
2928
+ ),
2929
+ exitRules: z2.array(
2930
+ z2.object({
2931
+ type: z2.string().describe("Rule type: stop_loss, profit_target, time_exit, conditional"),
2932
+ trigger: z2.string().describe("Trigger condition: '200% of credit', '50% of max profit', '15:00 ET'"),
2933
+ description: z2.string().optional().describe("Human-readable description"),
2934
+ stopLossType: z2.enum(["percentage", "dollar", "sl_ratio", "debit_percentage"]).optional().describe("Stop loss calculation method"),
2935
+ stopLossValue: z2.number().optional().describe("Stop loss numeric value"),
2936
+ monitoring: z2.object({
2937
+ granularity: z2.enum(["intra_minute", "candle_close", "end_of_bar"]).optional().describe("Price check frequency"),
2938
+ priceSource: z2.enum(["nbbo", "mid", "last"]).optional().describe("Which price to use")
2615
2939
  }).optional().describe("Monitoring configuration for this rule"),
2616
- slippage: z.number().optional().describe("Per-rule slippage override")
2940
+ slippage: z2.number().optional().describe("Per-rule slippage override")
2617
2941
  })
2618
2942
  ).default([]).describe("Exit rules and triggers"),
2619
- expectedRegimes: z.array(z.enum(["very_low", "low", "below_avg", "above_avg", "high", "extreme"])).default([]).describe("VIX-based vol regimes this strategy targets. very_low=VIX<13, low=13-16, below_avg=16-20, above_avg=20-25, high=25-30, extreme=30+"),
2620
- keyMetrics: z.object({
2621
- expectedWinRate: z.number().optional().describe("Expected win rate (0-1)"),
2622
- targetPremium: z.number().optional().describe("Target premium collected ($)"),
2623
- maxLoss: z.number().optional().describe("Maximum loss per contract ($)"),
2624
- profitTarget: z.number().optional().describe("Profit target ($ or %)")
2943
+ expectedRegimes: z2.array(z2.enum(["very_low", "low", "below_avg", "above_avg", "high", "extreme"])).default([]).describe(
2944
+ "VIX-based vol regimes this strategy targets. very_low=VIX<13, low=13-16, below_avg=16-20, above_avg=20-25, high=25-30, extreme=30+"
2945
+ ),
2946
+ keyMetrics: z2.object({
2947
+ expectedWinRate: z2.number().optional().describe("Expected win rate (0-1)"),
2948
+ targetPremium: z2.number().optional().describe("Target premium collected ($)"),
2949
+ maxLoss: z2.number().optional().describe("Maximum loss per contract ($)"),
2950
+ profitTarget: z2.number().optional().describe("Profit target ($ or %)")
2625
2951
  }).passthrough().default({}).describe("Performance benchmarks and strategy-specific metrics"),
2626
- positionSizing: z.object({
2627
- method: z.string().describe("Sizing method: pct_of_portfolio, fixed_contracts, fixed_dollar, discretionary"),
2628
- allocationPct: z.number().optional().describe("Portfolio allocation percentage (e.g., 2 for 2%)"),
2629
- maxContracts: z.number().optional().describe("Maximum contracts per trade"),
2630
- maxAllocationDollar: z.number().optional().describe("Maximum dollar allocation per trade"),
2631
- maxOpenPositions: z.number().optional().describe("Maximum concurrent open positions"),
2632
- description: z.string().optional().describe("Free-text sizing notes"),
2633
- backtestAllocationPct: z.number().optional().describe("Allocation % used in backtest"),
2634
- liveAllocationPct: z.number().optional().describe("Allocation % used in live portfolio"),
2635
- maxContractsPerTrade: z.number().optional().describe("Per-entry contract cap (distinct from maxContracts hard cap)")
2636
- }).optional().describe("Position sizing rules. Per-block \u2014 same strategy in backtest vs portfolio may have different sizing."),
2637
- underlying: z.string().optional().describe("Underlying symbol: SPX, QQQ, etc."),
2638
- reEntry: z.boolean().optional().describe("Strategy supports re-entry on same day"),
2639
- capProfits: z.boolean().optional().describe("Profits are capped by structure"),
2640
- capLosses: z.boolean().optional().describe("Losses are capped by structure"),
2641
- requireTwoPricesPT: z.boolean().optional().describe("Profit target requires two prices"),
2642
- closeOnCompletion: z.boolean().optional().describe("Close entire position when any leg hits target"),
2643
- ignoreMarginReq: z.boolean().optional().describe("Strategy ignores standard margin requirements")
2952
+ positionSizing: z2.object({
2953
+ method: z2.string().describe("Sizing method: pct_of_portfolio, fixed_contracts, fixed_dollar, discretionary"),
2954
+ allocationPct: z2.number().optional().describe("Portfolio allocation percentage (e.g., 2 for 2%)"),
2955
+ maxContracts: z2.number().optional().describe("Maximum contracts per trade"),
2956
+ maxAllocationDollar: z2.number().optional().describe("Maximum dollar allocation per trade"),
2957
+ maxOpenPositions: z2.number().optional().describe("Maximum concurrent open positions"),
2958
+ description: z2.string().optional().describe("Free-text sizing notes"),
2959
+ backtestAllocationPct: z2.number().optional().describe("Allocation % used in backtest"),
2960
+ liveAllocationPct: z2.number().optional().describe("Allocation % used in live portfolio"),
2961
+ maxContractsPerTrade: z2.number().optional().describe("Per-entry contract cap (distinct from maxContracts hard cap)")
2962
+ }).optional().describe(
2963
+ "Position sizing rules. Per-block \u2014 same strategy in backtest vs portfolio may have different sizing."
2964
+ ),
2965
+ underlying: z2.string().optional().describe("Underlying symbol: SPX, QQQ, etc."),
2966
+ reEntry: z2.boolean().optional().describe("Strategy supports re-entry on same day"),
2967
+ capProfits: z2.boolean().optional().describe("Profits are capped by structure"),
2968
+ capLosses: z2.boolean().optional().describe("Losses are capped by structure"),
2969
+ requireTwoPricesPT: z2.boolean().optional().describe("Profit target requires two prices"),
2970
+ closeOnCompletion: z2.boolean().optional().describe("Close entire position when any leg hits target"),
2971
+ ignoreMarginReq: z2.boolean().optional().describe("Strategy ignores standard margin requirements")
2644
2972
  });
2645
- var getStrategyProfileSchema = z.object({
2646
- blockId: z.string().describe("Block ID to look up"),
2647
- strategyName: z.string().describe("Strategy name to look up")
2973
+ var getStrategyProfileSchema = z2.object({
2974
+ blockId: z2.string().describe("Block ID to look up"),
2975
+ strategyName: z2.string().describe("Strategy name to look up")
2648
2976
  });
2649
- var listProfilesSchema = z.object({
2650
- blockId: z.string().optional().describe("Optional block ID filter. Omit to list all profiles across all blocks.")
2977
+ var listProfilesSchema = z2.object({
2978
+ blockId: z2.string().optional().describe("Optional block ID filter. Omit to list all profiles across all blocks.")
2651
2979
  });
2652
- var deleteProfileSchema = z.object({
2653
- blockId: z.string().describe("Block ID of the profile to delete"),
2654
- strategyName: z.string().describe("Strategy name of the profile to delete")
2980
+ var deleteProfileSchema = z2.object({
2981
+ blockId: z2.string().describe("Block ID of the profile to delete"),
2982
+ strategyName: z2.string().describe("Strategy name of the profile to delete")
2655
2983
  });
2656
2984
  async function handleProfileStrategy(input, baseDir) {
2657
2985
  await upgradeToReadWrite(baseDir);
2658
2986
  try {
2659
2987
  const conn = await getConnection(baseDir);
2660
- const stored = await upsertProfile(conn, {
2661
- blockId: input.blockId,
2662
- strategyName: input.strategyName,
2663
- structureType: input.structureType,
2664
- greeksBias: input.greeksBias,
2665
- thesis: input.thesis,
2666
- legs: input.legs,
2667
- entryFilters: input.entryFilters,
2668
- exitRules: input.exitRules,
2669
- expectedRegimes: input.expectedRegimes,
2670
- keyMetrics: input.keyMetrics,
2671
- positionSizing: input.positionSizing,
2672
- underlying: input.underlying,
2673
- reEntry: input.reEntry,
2674
- capProfits: input.capProfits,
2675
- capLosses: input.capLosses,
2676
- requireTwoPricesPT: input.requireTwoPricesPT,
2677
- closeOnCompletion: input.closeOnCompletion,
2678
- ignoreMarginReq: input.ignoreMarginReq
2679
- }, baseDir);
2680
- return createToolOutput(
2681
- `Profile saved: ${input.strategyName} for block ${input.blockId}`,
2682
- { profile: stored }
2988
+ const stored = await upsertProfile(
2989
+ conn,
2990
+ {
2991
+ blockId: input.blockId,
2992
+ strategyName: input.strategyName,
2993
+ structureType: input.structureType,
2994
+ greeksBias: input.greeksBias,
2995
+ thesis: input.thesis,
2996
+ legs: input.legs,
2997
+ entryFilters: input.entryFilters,
2998
+ exitRules: input.exitRules,
2999
+ expectedRegimes: input.expectedRegimes,
3000
+ keyMetrics: input.keyMetrics,
3001
+ positionSizing: input.positionSizing,
3002
+ underlying: input.underlying,
3003
+ reEntry: input.reEntry,
3004
+ capProfits: input.capProfits,
3005
+ capLosses: input.capLosses,
3006
+ requireTwoPricesPT: input.requireTwoPricesPT,
3007
+ closeOnCompletion: input.closeOnCompletion,
3008
+ ignoreMarginReq: input.ignoreMarginReq
3009
+ },
3010
+ baseDir
2683
3011
  );
3012
+ return createToolOutput(`Profile saved: ${input.strategyName} for block ${input.blockId}`, {
3013
+ profile: stored
3014
+ });
2684
3015
  } finally {
2685
3016
  await downgradeToReadOnly(baseDir);
2686
3017
  }
@@ -2694,10 +3025,7 @@ async function handleGetStrategyProfile(input, baseDir) {
2694
3025
  { profile: null }
2695
3026
  );
2696
3027
  }
2697
- return createToolOutput(
2698
- `Profile: ${input.strategyName} in block ${input.blockId}`,
2699
- { profile }
2700
- );
3028
+ return createToolOutput(`Profile: ${input.strategyName} in block ${input.blockId}`, { profile });
2701
3029
  }
2702
3030
  async function handleListProfiles(input, baseDir) {
2703
3031
  const conn = await getConnection(baseDir);
@@ -2737,7 +3065,7 @@ async function handleDeleteProfile(input, baseDir) {
2737
3065
  }
2738
3066
 
2739
3067
  // src/tools/profile-analysis.ts
2740
- import { z as z2 } from "zod";
3068
+ import { z as z3 } from "zod";
2741
3069
  function formatTradeDate(date) {
2742
3070
  if (typeof date === "string") {
2743
3071
  const match = date.match(/^(\d{4})-(\d{2})-(\d{2})/);
@@ -2893,10 +3221,10 @@ function findBucket(value, buckets) {
2893
3221
  }
2894
3222
  return null;
2895
3223
  }
2896
- var analyzeStructureFitSchema = z2.object({
2897
- blockId: z2.string().describe("Block ID to analyze"),
2898
- strategyName: z2.string().describe("Strategy name matching a stored profile"),
2899
- minTrades: z2.number().optional().default(10).describe("Minimum trades per bucket for reliable stats (thin-data warning threshold)")
3224
+ var analyzeStructureFitSchema = z3.object({
3225
+ blockId: z3.string().describe("Block ID to analyze"),
3226
+ strategyName: z3.string().describe("Strategy name matching a stored profile"),
3227
+ minTrades: z3.number().optional().default(10).describe("Minimum trades per bucket for reliable stats (thin-data warning threshold)")
2900
3228
  });
2901
3229
  async function handleAnalyzeStructureFit(input, baseDir) {
2902
3230
  const { blockId, strategyName } = input;
@@ -3069,11 +3397,11 @@ async function handleAnalyzeStructureFit(input, baseDir) {
3069
3397
  }
3070
3398
  });
3071
3399
  }
3072
- var validateEntryFiltersSchema = z2.object({
3073
- blockId: z2.string().describe("Block ID to analyze"),
3074
- strategyName: z2.string().describe("Strategy name matching a stored profile"),
3075
- minTrades: z2.number().optional().default(10).describe("Minimum trades per group for reliable stats"),
3076
- maxAblationFilters: z2.number().optional().default(8).describe("Maximum number of filters for pairwise ablation (cap for combinatorial explosion)")
3400
+ var validateEntryFiltersSchema = z3.object({
3401
+ blockId: z3.string().describe("Block ID to analyze"),
3402
+ strategyName: z3.string().describe("Strategy name matching a stored profile"),
3403
+ minTrades: z3.number().optional().default(10).describe("Minimum trades per group for reliable stats"),
3404
+ maxAblationFilters: z3.number().optional().default(8).describe("Maximum number of filters for pairwise ablation (cap for combinatorial explosion)")
3077
3405
  });
3078
3406
  async function handleValidateEntryFilters(input, baseDir) {
3079
3407
  const { blockId, strategyName } = input;
@@ -3286,14 +3614,16 @@ async function handleValidateEntryFilters(input, baseDir) {
3286
3614
  single: ablationSingle,
3287
3615
  pairs: ablationPairs
3288
3616
  },
3289
- execution_filters_skipped: executionFilters.map((f) => f.description || `${f.field} ${f.operator} ${f.value}`),
3617
+ execution_filters_skipped: executionFilters.map(
3618
+ (f) => f.description || `${f.field} ${f.operator} ${f.value}`
3619
+ ),
3290
3620
  profile_update_hints: profileUpdateHints,
3291
3621
  warnings
3292
3622
  });
3293
3623
  }
3294
- var portfolioStructureMapSchema = z2.object({
3295
- blockId: z2.string().optional().describe("Block ID to analyze. When omitted, aggregate across all blocks."),
3296
- minTrades: z2.number().optional().default(10).describe("Thin-data warning threshold (default: 10)")
3624
+ var portfolioStructureMapSchema = z3.object({
3625
+ blockId: z3.string().optional().describe("Block ID to analyze. When omitted, aggregate across all blocks."),
3626
+ minTrades: z3.number().optional().default(10).describe("Thin-data warning threshold (default: 10)")
3297
3627
  });
3298
3628
  async function handlePortfolioStructureMap(input, baseDir) {
3299
3629
  try {
@@ -3317,7 +3647,9 @@ async function handlePortfolioStructureMap(input, baseDir) {
3317
3647
  try {
3318
3648
  block = await loadBlock(baseDir, profile.blockId);
3319
3649
  } catch {
3320
- warnings.push(`Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'`);
3650
+ warnings.push(
3651
+ `Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'`
3652
+ );
3321
3653
  continue;
3322
3654
  }
3323
3655
  let trades = filterByStrategy(block.trades, profile.strategyName);
@@ -3328,7 +3660,9 @@ async function handlePortfolioStructureMap(input, baseDir) {
3328
3660
  }
3329
3661
  }
3330
3662
  if (trades.length === 0) {
3331
- warnings.push(`No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'`);
3663
+ warnings.push(
3664
+ `No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'`
3665
+ );
3332
3666
  continue;
3333
3667
  }
3334
3668
  const tradeKeys = uniqueTradeLookupKeys(trades);
@@ -3446,10 +3780,7 @@ async function handlePortfolioStructureMap(input, baseDir) {
3446
3780
  );
3447
3781
  }
3448
3782
  const unknownTrendStats = unknownTrendPls.size > 0 ? Object.fromEntries(
3449
- [...unknownTrendPls.entries()].map(([name, pls]) => [
3450
- name,
3451
- computeSliceStats(pls)
3452
- ])
3783
+ [...unknownTrendPls.entries()].map(([name, pls]) => [name, computeSliceStats(pls)])
3453
3784
  ) : void 0;
3454
3785
  const coverageSummary = {
3455
3786
  totalCells: 18,
@@ -3484,7 +3815,7 @@ async function handlePortfolioStructureMap(input, baseDir) {
3484
3815
  }
3485
3816
 
3486
3817
  // src/tools/regime-advisor.ts
3487
- import { z as z3 } from "zod";
3818
+ import { z as z4 } from "zod";
3488
3819
  function formatTradeDate2(date) {
3489
3820
  if (typeof date === "string") {
3490
3821
  const match = date.match(/^(\d{4})-(\d{2})-(\d{2})/);
@@ -3550,13 +3881,9 @@ var VOL_REGIME_LABELS2 = {
3550
3881
  5: "high",
3551
3882
  6: "extreme"
3552
3883
  };
3553
- var regimeAllocationAdvisorSchema = z3.object({
3554
- blockId: z3.string().optional().describe(
3555
- "Block ID to analyze. When omitted, aggregate across all profiled strategies."
3556
- ),
3557
- minTrades: z3.number().optional().default(5).describe(
3558
- "Minimum trades per regime cell for reliable stats (default: 5)"
3559
- )
3884
+ var regimeAllocationAdvisorSchema = z4.object({
3885
+ blockId: z4.string().optional().describe("Block ID to analyze. When omitted, aggregate across all profiled strategies."),
3886
+ minTrades: z4.number().optional().default(5).describe("Minimum trades per regime cell for reliable stats (default: 5)")
3560
3887
  });
3561
3888
  async function handleRegimeAllocationAdvisor(input, baseDir) {
3562
3889
  const minTrades = input.minTrades ?? 5;
@@ -3648,9 +3975,7 @@ async function handleRegimeAllocationAdvisor(input, baseDir) {
3648
3975
  if (!regimeAggPls[label]) regimeAggPls[label] = [];
3649
3976
  regimeAggPls[label].push(trade.pl);
3650
3977
  }
3651
- const expectedSet = new Set(
3652
- profile.expectedRegimes.map((r) => r.toLowerCase())
3653
- );
3978
+ const expectedSet = new Set(profile.expectedRegimes.map((r) => r.toLowerCase()));
3654
3979
  const regimePerformance = {};
3655
3980
  for (const [label, pls] of Object.entries(regimePls)) {
3656
3981
  const stats = computeSliceStats(pls);
@@ -3754,7 +4079,7 @@ function getResolvedProviderCapabilities(env = process.env) {
3754
4079
  }
3755
4080
 
3756
4081
  // src/tools/replay.ts
3757
- import { z as z4 } from "zod";
4082
+ import { z as z5 } from "zod";
3758
4083
 
3759
4084
  // src/market/tickers/resolver.ts
3760
4085
  var OCC_RE = /^([A-Z]+)\d{6}[CP]\d{6,11}$/;
@@ -3773,38 +4098,36 @@ function rootToUnderlying(input, registry) {
3773
4098
  }
3774
4099
 
3775
4100
  // src/tools/replay.ts
3776
- var replayTradeSchema = z4.object({
4101
+ var replayTradeSchema = z5.object({
3777
4102
  // Mode A: Hypothetical / explicit legs
3778
- legs: z4.array(
3779
- z4.object({
3780
- ticker: z4.string().describe("Underlying ticker, e.g., 'SPY', 'SPX'"),
3781
- strike: z4.number().describe("Strike price"),
3782
- type: z4.enum(["C", "P"]).describe("Call or Put"),
3783
- expiry: z4.string().describe("Expiration date YYYY-MM-DD"),
3784
- quantity: z4.number().describe("Positive = long, negative = short"),
3785
- entry_price: z4.number().describe("Per-contract entry price (premium paid/received)")
4103
+ legs: z5.array(
4104
+ z5.object({
4105
+ ticker: z5.string().describe("Underlying ticker, e.g., 'SPY', 'SPX'"),
4106
+ strike: z5.number().describe("Strike price"),
4107
+ type: z5.enum(["C", "P"]).describe("Call or Put"),
4108
+ expiry: z5.string().describe("Expiration date YYYY-MM-DD"),
4109
+ quantity: z5.number().describe("Positive = long, negative = short"),
4110
+ entry_price: z5.number().describe("Per-contract entry price (premium paid/received)")
3786
4111
  })
3787
4112
  ).optional().describe("Explicit leg definitions for hypothetical replay"),
3788
4113
  // Mode B: Tradelog replay
3789
- block_id: z4.string().optional().describe("Block ID to load trade from"),
3790
- trade_index: z4.number().optional().describe(
3791
- "0-based index of trade in block's tradelog (ordered by date_opened)"
3792
- ),
4114
+ block_id: z5.string().optional().describe("Block ID to load trade from"),
4115
+ trade_index: z5.number().optional().describe("0-based index of trade in block's tradelog (ordered by date_opened)"),
3793
4116
  // Common fields
3794
- open_date: z4.string().optional().describe(
4117
+ open_date: z5.string().optional().describe(
3795
4118
  "Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)"
3796
4119
  ),
3797
- close_date: z4.string().optional().describe(
4120
+ close_date: z5.string().optional().describe(
3798
4121
  "Trade close date YYYY-MM-DD (required for hypothetical, auto-resolved for tradelog)"
3799
4122
  ),
3800
- multiplier: z4.number().default(100).describe("Contract multiplier (default 100 for standard options)"),
3801
- format: z4.enum(["full", "summary", "sampled"]).default("sampled").describe(
4123
+ multiplier: z5.number().default(100).describe("Contract multiplier (default 100 for standard options)"),
4124
+ format: z5.enum(["full", "summary", "sampled"]).default("sampled").describe(
3802
4125
  "Output format: 'sampled' returns path sampled at ~15min intervals (default), 'full' returns complete minute-by-minute P&L path, 'summary' returns MFE/MAE/P&L without minute-level path"
3803
4126
  ),
3804
- close_at: z4.enum(["trade", "expiry"]).default("trade").describe(
4127
+ close_at: z5.enum(["trade", "expiry"]).default("trade").describe(
3805
4128
  "When to end the P&L path: 'trade' (default) truncates at the trade's actual close time, 'expiry' shows full path through option expiry. Only applies to tradelog mode."
3806
4129
  ),
3807
- skip_quotes: z4.boolean().default(false).describe(
4130
+ skip_quotes: z5.boolean().default(false).describe(
3808
4131
  "Skip NBBO quote enrichment for option bars. Faster, but uses cached trade bars / HL2 marks."
3809
4132
  )
3810
4133
  });
@@ -3836,22 +4159,13 @@ function resolveOODateRange(parsedLegs, tradeYear, tradeOpenDate) {
3836
4159
  return { from: tradeOpenDate, to: maxDate };
3837
4160
  }
3838
4161
  async function handleReplayTrade(params, baseDir, stores, injectedConn) {
3839
- const {
3840
- legs: inputLegs,
3841
- block_id,
3842
- trade_index,
3843
- multiplier,
3844
- close_at,
3845
- skip_quotes
3846
- } = params;
4162
+ const { legs: inputLegs, block_id, trade_index, multiplier, close_at, skip_quotes } = params;
3847
4163
  let { open_date, close_date } = params;
3848
4164
  let tradeCloseTimestamp;
3849
4165
  let replayLegs;
3850
4166
  if (inputLegs && inputLegs.length > 0) {
3851
4167
  if (!open_date || !close_date) {
3852
- throw new Error(
3853
- "open_date and close_date are required for hypothetical replay mode"
3854
- );
4168
+ throw new Error("open_date and close_date are required for hypothetical replay mode");
3855
4169
  }
3856
4170
  replayLegs = inputLegs.map((leg) => ({
3857
4171
  occTicker: buildOccTicker(leg.ticker, leg.expiry, leg.type, leg.strike),
@@ -3870,9 +4184,7 @@ async function handleReplayTrade(params, baseDir, stores, injectedConn) {
3870
4184
  );
3871
4185
  const rows = result.getRows();
3872
4186
  if (rows.length === 0) {
3873
- throw new Error(
3874
- `No trade found at index ${trade_index} in block "${block_id}"`
3875
- );
4187
+ throw new Error(`No trade found at index ${trade_index} in block "${block_id}"`);
3876
4188
  }
3877
4189
  const row = rows[0];
3878
4190
  const legsStr = String(row[0] ?? "");
@@ -3981,11 +4293,7 @@ async function handleReplayTrade(params, baseDir, stores, injectedConn) {
3981
4293
  );
3982
4294
  if (underlyingBars.length === 0) {
3983
4295
  try {
3984
- underlyingBars = await stores.spot.readDailyBars(
3985
- underlyingTicker,
3986
- open_date,
3987
- close_date
3988
- );
4296
+ underlyingBars = await stores.spot.readDailyBars(underlyingTicker, open_date, close_date);
3989
4297
  } catch {
3990
4298
  }
3991
4299
  }
@@ -4104,17 +4412,15 @@ async function handleReplayTrade(params, baseDir, stores, injectedConn) {
4104
4412
  }
4105
4413
 
4106
4414
  // src/tools/snapshot.ts
4107
- import { z as z5 } from "zod";
4108
- var getOptionSnapshotSchema = z5.object({
4109
- underlying: z5.string().describe("Underlying ticker symbol (e.g., 'SPX', 'SPY', 'AAPL')"),
4110
- strike_price_gte: z5.number().optional().describe("Minimum strike price filter"),
4111
- strike_price_lte: z5.number().optional().describe("Maximum strike price filter"),
4112
- expiration_date_gte: z5.string().optional().describe("Earliest expiration date (YYYY-MM-DD)"),
4113
- expiration_date_lte: z5.string().optional().describe("Latest expiration date (YYYY-MM-DD)"),
4114
- contract_type: z5.enum(["call", "put"]).optional().describe("Filter by call or put"),
4115
- limit: z5.number().optional().default(50).describe(
4116
- "Max contracts to return (default 50, use higher for full chain)"
4117
- )
4415
+ import { z as z6 } from "zod";
4416
+ var getOptionSnapshotSchema = z6.object({
4417
+ underlying: z6.string().describe("Underlying ticker symbol (e.g., 'SPX', 'SPY', 'AAPL')"),
4418
+ strike_price_gte: z6.number().optional().describe("Minimum strike price filter"),
4419
+ strike_price_lte: z6.number().optional().describe("Maximum strike price filter"),
4420
+ expiration_date_gte: z6.string().optional().describe("Earliest expiration date (YYYY-MM-DD)"),
4421
+ expiration_date_lte: z6.string().optional().describe("Latest expiration date (YYYY-MM-DD)"),
4422
+ contract_type: z6.enum(["call", "put"]).optional().describe("Filter by call or put"),
4423
+ limit: z6.number().optional().default(50).describe("Max contracts to return (default 50, use higher for full chain)")
4118
4424
  });
4119
4425
  async function handleGetOptionSnapshot(params) {
4120
4426
  try {
@@ -4231,7 +4537,9 @@ function numericalDecomposition(config, totalPnlChange, stepCount) {
4231
4537
  ...f,
4232
4538
  pctOfTotal: totalAbsSum > 0 ? Math.abs(f.totalPnl) / totalAbsSum * 100 : 0
4233
4539
  }));
4234
- const summaryParts = factors.map((f) => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);
4540
+ const summaryParts = factors.map(
4541
+ (f) => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`
4542
+ );
4235
4543
  const summary = `P&L of ${totalPnlChange.toFixed(2)} (numerical): ${summaryParts.join(", ")}`;
4236
4544
  return {
4237
4545
  factors,
@@ -4266,7 +4574,15 @@ function priceOption(type, S, K, dte, r, q, iv) {
4266
4574
  return bsPrice(bsType, S, K, T, r, q, iv);
4267
4575
  }
4268
4576
  function decomposeGreeks(config) {
4269
- const { pnlPath, legs, underlyingPrices, legGroups, legPricingInputs, riskFreeRate, dividendYield } = config;
4577
+ const {
4578
+ pnlPath,
4579
+ legs,
4580
+ underlyingPrices,
4581
+ legGroups,
4582
+ legPricingInputs,
4583
+ riskFreeRate,
4584
+ dividendYield
4585
+ } = config;
4270
4586
  if (pnlPath.length <= 1) {
4271
4587
  const emptyFactors = [
4272
4588
  { factor: "delta", totalPnl: 0, pctOfTotal: 0, steps: [] },
@@ -4426,7 +4742,9 @@ function decomposeGreeks(config) {
4426
4742
  const summaryParts = factors.filter((f) => f.factor !== "residual").map((f) => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);
4427
4743
  const residualFactor = factors.find((f) => f.factor === "residual");
4428
4744
  if (residualFactor && Math.abs(residualFactor.totalPnl) > 0.01) {
4429
- summaryParts.push(`residual ${residualFactor.totalPnl.toFixed(2)} (${residualFactor.pctOfTotal.toFixed(0)}%)`);
4745
+ summaryParts.push(
4746
+ `residual ${residualFactor.totalPnl.toFixed(2)} (${residualFactor.pctOfTotal.toFixed(0)}%)`
4747
+ );
4430
4748
  }
4431
4749
  const summary = `P&L of ${totalPnlChange.toFixed(2)} (${methodLabel}): ${summaryParts.join(", ")}`;
4432
4750
  const warning = residualPct > 0.5 ? `Residual ${(residualPct * 100).toFixed(0)}% \u2014 attribution limited for some legs.` : null;
@@ -4558,7 +4876,7 @@ function evaluateTrigger(trigger, pnlPath, legs) {
4558
4876
  switch (type) {
4559
4877
  case "profitTarget": {
4560
4878
  if (trigger.unit === "percent" && trigger.entryCost == null) break;
4561
- const requiredHits = trigger.requiredHits ?? 1;
4879
+ const requiredHits = trigger.requiredHits ?? 2;
4562
4880
  const dollarThresholdPT = trigger.unit === "percent" ? threshold * Math.abs(trigger.entryCost) : threshold;
4563
4881
  if (pnl >= dollarThresholdPT) {
4564
4882
  if (point.allLegsSync !== false) profitTargetHits++;
@@ -4914,8 +5232,8 @@ function analyzeExitTriggers(config) {
4914
5232
  }
4915
5233
 
4916
5234
  // src/tools/exit-analysis.ts
4917
- import { z as z6 } from "zod";
4918
- var triggerTypeEnum = z6.enum([
5235
+ import { z as z7 } from "zod";
5236
+ var triggerTypeEnum = z7.enum([
4919
5237
  "profitTarget",
4920
5238
  "stopLoss",
4921
5239
  "trailingStop",
@@ -4932,64 +5250,70 @@ var triggerTypeEnum = z6.enum([
4932
5250
  "slRatioThreshold",
4933
5251
  "slRatioMove"
4934
5252
  ]);
4935
- var triggerConfigSchema = z6.object({
5253
+ var triggerConfigSchema = z7.object({
4936
5254
  type: triggerTypeEnum,
4937
- threshold: z6.number(),
4938
- unit: z6.enum(["percent", "dollar"]).default("dollar").optional(),
4939
- expiry: z6.string().optional(),
4940
- openDate: z6.string().optional(),
4941
- clockTime: z6.string().optional(),
4942
- trailAmount: z6.number().optional(),
4943
- steps: z6.array(z6.object({
4944
- armAt: z6.number(),
4945
- stopAt: z6.number(),
4946
- closeAllocationPct: z6.number().min(0).max(1).optional().describe("Fraction of REMAINING position to close at this milestone (0-1)")
4947
- })).optional(),
4948
- spreadWidth: z6.number().optional(),
4949
- contracts: z6.number().optional(),
4950
- legIndex: z6.number().optional().describe("0-based leg index for perLegDelta \u2014 targets specific leg"),
4951
- exitAbove: z6.number().optional().describe("Fire when value exceeds this (directional, no abs)"),
4952
- exitBelow: z6.number().optional().describe("Fire when value drops below this (directional, no abs)")
5255
+ threshold: z7.number(),
5256
+ unit: z7.enum(["percent", "dollar"]).default("dollar").optional(),
5257
+ expiry: z7.string().optional(),
5258
+ openDate: z7.string().optional(),
5259
+ clockTime: z7.string().optional(),
5260
+ trailAmount: z7.number().optional(),
5261
+ steps: z7.array(
5262
+ z7.object({
5263
+ armAt: z7.number(),
5264
+ stopAt: z7.number(),
5265
+ closeAllocationPct: z7.number().min(0).max(1).optional().describe("Fraction of REMAINING position to close at this milestone (0-1)")
5266
+ })
5267
+ ).optional(),
5268
+ spreadWidth: z7.number().optional(),
5269
+ contracts: z7.number().optional(),
5270
+ legIndex: z7.number().optional().describe("0-based leg index for perLegDelta \u2014 targets specific leg"),
5271
+ exitAbove: z7.number().optional().describe("Fire when value exceeds this (directional, no abs)"),
5272
+ exitBelow: z7.number().optional().describe("Fire when value drops below this (directional, no abs)")
4953
5273
  });
4954
- var legSchema = z6.object({
4955
- ticker: z6.string(),
4956
- strike: z6.number(),
4957
- type: z6.enum(["C", "P"]),
4958
- expiry: z6.string(),
4959
- quantity: z6.number(),
4960
- entry_price: z6.number()
5274
+ var legSchema = z7.object({
5275
+ ticker: z7.string(),
5276
+ strike: z7.number(),
5277
+ type: z7.enum(["C", "P"]),
5278
+ expiry: z7.string(),
5279
+ quantity: z7.number(),
5280
+ entry_price: z7.number()
4961
5281
  });
4962
- var analyzeExitTriggersSchema = z6.object({
5282
+ var analyzeExitTriggersSchema = z7.object({
4963
5283
  // Replay inputs (same shape as replay_trade)
4964
- legs: z6.array(legSchema).optional(),
4965
- block_id: z6.string().optional(),
4966
- trade_index: z6.number().optional(),
4967
- open_date: z6.string().optional(),
4968
- close_date: z6.string().optional(),
4969
- multiplier: z6.number().default(100),
4970
- triggers: z6.array(triggerConfigSchema).describe("Exit triggers to evaluate against the P&L path"),
4971
- actual_exit_timestamp: z6.string().optional().describe("Actual exit time for comparison (format: YYYY-MM-DD HH:MM)"),
4972
- leg_groups: z6.array(z6.object({
4973
- label: z6.string(),
4974
- leg_indices: z6.array(z6.number()),
4975
- triggers: z6.array(triggerConfigSchema)
4976
- })).optional().describe("Per-leg-group exit triggers for multi-structure strategies"),
4977
- format: z6.enum(["summary", "full"]).default("summary").describe("'summary' omits per-step trigger states, 'full' includes all fire events")
5284
+ legs: z7.array(legSchema).optional(),
5285
+ block_id: z7.string().optional(),
5286
+ trade_index: z7.number().optional(),
5287
+ open_date: z7.string().optional(),
5288
+ close_date: z7.string().optional(),
5289
+ multiplier: z7.number().default(100),
5290
+ triggers: z7.array(triggerConfigSchema).describe("Exit triggers to evaluate against the P&L path"),
5291
+ actual_exit_timestamp: z7.string().optional().describe("Actual exit time for comparison (format: YYYY-MM-DD HH:MM)"),
5292
+ leg_groups: z7.array(
5293
+ z7.object({
5294
+ label: z7.string(),
5295
+ leg_indices: z7.array(z7.number()),
5296
+ triggers: z7.array(triggerConfigSchema)
5297
+ })
5298
+ ).optional().describe("Per-leg-group exit triggers for multi-structure strategies"),
5299
+ format: z7.enum(["summary", "full"]).default("summary").describe("'summary' omits per-step trigger states, 'full' includes all fire events")
4978
5300
  });
4979
- var decomposeGreeksSchema = z6.object({
5301
+ var decomposeGreeksSchema = z7.object({
4980
5302
  // Same replay inputs
4981
- legs: z6.array(legSchema).optional(),
4982
- block_id: z6.string().optional(),
4983
- trade_index: z6.number().optional(),
4984
- open_date: z6.string().optional(),
4985
- close_date: z6.string().optional(),
4986
- multiplier: z6.number().default(100),
4987
- leg_groups: z6.array(z6.object({
4988
- label: z6.string(),
4989
- leg_indices: z6.array(z6.number())
4990
- })).optional().describe("Leg grouping for per-group vega attribution (e.g., front_month vs back_month)"),
4991
- format: z6.enum(["summary", "full"]).default("summary").describe("'summary' shows ranked factors, 'full' includes per-step contributions"),
4992
- skip_quotes: z6.boolean().default(false).describe("Skip NBBO quote enrichment for option bars. Faster, but lower precision.")
5303
+ legs: z7.array(legSchema).optional(),
5304
+ block_id: z7.string().optional(),
5305
+ trade_index: z7.number().optional(),
5306
+ open_date: z7.string().optional(),
5307
+ close_date: z7.string().optional(),
5308
+ multiplier: z7.number().default(100),
5309
+ leg_groups: z7.array(
5310
+ z7.object({
5311
+ label: z7.string(),
5312
+ leg_indices: z7.array(z7.number())
5313
+ })
5314
+ ).optional().describe("Leg grouping for per-group vega attribution (e.g., front_month vs back_month)"),
5315
+ format: z7.enum(["summary", "full"]).default("summary").describe("'summary' shows ranked factors, 'full' includes per-step contributions"),
5316
+ skip_quotes: z7.boolean().default(false).describe("Skip NBBO quote enrichment for option bars. Faster, but lower precision.")
4993
5317
  });
4994
5318
  var REVERSE_ROOT_MAP = {
4995
5319
  SPXW: "SPX",
@@ -5012,7 +5336,8 @@ async function fetchPriceMap(stores, ticker, from, to) {
5012
5336
  }
5013
5337
  }
5014
5338
  for (const b of bars) {
5015
- if (!Number.isFinite(b.open) || b.open <= 0 || !Number.isFinite(b.high) || b.high <= 0 || !Number.isFinite(b.low) || b.low <= 0 || !Number.isFinite(b.close) || b.close <= 0) continue;
5339
+ if (!Number.isFinite(b.open) || b.open <= 0 || !Number.isFinite(b.high) || b.high <= 0 || !Number.isFinite(b.low) || b.low <= 0 || !Number.isFinite(b.close) || b.close <= 0)
5340
+ continue;
5016
5341
  const ts = `${b.date} ${b.time ?? ""}`.trim();
5017
5342
  map.set(ts, markPrice(b));
5018
5343
  }
@@ -5083,12 +5408,7 @@ async function handleAnalyzeExitTriggers(params, baseDir, stores, injectedConn)
5083
5408
  vix9dPrices = await fetchPriceMap(stores, "VIX9D", firstDate, lastDate);
5084
5409
  }
5085
5410
  if (needsUnderlying) {
5086
- underlyingPrices = await fetchPriceMap(
5087
- stores,
5088
- underlyingTicker,
5089
- firstDate,
5090
- lastDate
5091
- );
5411
+ underlyingPrices = await fetchPriceMap(stores, underlyingTicker, firstDate, lastDate);
5092
5412
  }
5093
5413
  const exitTriggers = triggers.map((t) => ({
5094
5414
  type: t.type,
@@ -5419,7 +5739,7 @@ function analyzeBatch(trades, config) {
5419
5739
  }
5420
5740
 
5421
5741
  // src/tools/batch-exit-analysis.ts
5422
- import { z as z7 } from "zod";
5742
+ import { z as z8 } from "zod";
5423
5743
  async function mapWithLimit(items, limit, fn) {
5424
5744
  const results = new Array(items.length);
5425
5745
  let idx = 0;
@@ -5433,7 +5753,7 @@ async function mapWithLimit(items, limit, fn) {
5433
5753
  await Promise.all(workers);
5434
5754
  return results;
5435
5755
  }
5436
- var triggerTypeEnum2 = z7.enum([
5756
+ var triggerTypeEnum2 = z8.enum([
5437
5757
  "profitTarget",
5438
5758
  "stopLoss",
5439
5759
  "trailingStop",
@@ -5450,44 +5770,52 @@ var triggerTypeEnum2 = z7.enum([
5450
5770
  "slRatioThreshold",
5451
5771
  "slRatioMove"
5452
5772
  ]);
5453
- var triggerConfigSchema2 = z7.object({
5773
+ var triggerConfigSchema2 = z8.object({
5454
5774
  type: triggerTypeEnum2,
5455
- threshold: z7.number(),
5456
- unit: z7.enum(["percent", "dollar"]).default("dollar").optional(),
5457
- expiry: z7.string().optional(),
5458
- openDate: z7.string().optional(),
5459
- clockTime: z7.string().optional(),
5460
- trailAmount: z7.number().optional(),
5461
- steps: z7.array(z7.object({
5462
- armAt: z7.number(),
5463
- stopAt: z7.number(),
5464
- closeAllocationPct: z7.number().min(0).max(1).optional().describe("Fraction of REMAINING position to close at this milestone (0-1)")
5465
- })).optional(),
5466
- spreadWidth: z7.number().optional(),
5467
- contracts: z7.number().optional(),
5468
- legIndex: z7.number().optional().describe("0-based leg index for perLegDelta \u2014 targets specific leg"),
5469
- exitAbove: z7.number().optional().describe("Fire when value exceeds this (directional, no abs)"),
5470
- exitBelow: z7.number().optional().describe("Fire when value drops below this (directional, no abs)")
5775
+ threshold: z8.number(),
5776
+ unit: z8.enum(["percent", "dollar"]).default("dollar").optional(),
5777
+ expiry: z8.string().optional(),
5778
+ openDate: z8.string().optional(),
5779
+ clockTime: z8.string().optional(),
5780
+ trailAmount: z8.number().optional(),
5781
+ steps: z8.array(
5782
+ z8.object({
5783
+ armAt: z8.number(),
5784
+ stopAt: z8.number(),
5785
+ closeAllocationPct: z8.number().min(0).max(1).optional().describe("Fraction of REMAINING position to close at this milestone (0-1)")
5786
+ })
5787
+ ).optional(),
5788
+ spreadWidth: z8.number().optional(),
5789
+ contracts: z8.number().optional(),
5790
+ legIndex: z8.number().optional().describe("0-based leg index for perLegDelta \u2014 targets specific leg"),
5791
+ exitAbove: z8.number().optional().describe("Fire when value exceeds this (directional, no abs)"),
5792
+ exitBelow: z8.number().optional().describe("Fire when value drops below this (directional, no abs)")
5471
5793
  });
5472
- var batchExitAnalysisSchema = z7.object({
5473
- block_id: z7.string().describe("Block ID to analyze trades from"),
5474
- strategy: z7.string().optional().describe("Filter trades by strategy name (case-insensitive ILIKE)"),
5475
- date_range: z7.object({
5476
- from: z7.string().optional().describe("Start date YYYY-MM-DD"),
5477
- to: z7.string().optional().describe("End date YYYY-MM-DD")
5794
+ var batchExitAnalysisSchema = z8.object({
5795
+ block_id: z8.string().describe("Block ID to analyze trades from"),
5796
+ strategy: z8.string().optional().describe("Filter trades by strategy name (case-insensitive ILIKE)"),
5797
+ date_range: z8.object({
5798
+ from: z8.string().optional().describe("Start date YYYY-MM-DD"),
5799
+ to: z8.string().optional().describe("End date YYYY-MM-DD")
5478
5800
  }).optional().describe("Filter trades by date range"),
5479
- candidate_policy: z7.array(triggerConfigSchema2).describe("Candidate exit policy triggers to evaluate -- same schema as analyze_exit_triggers"),
5480
- leg_groups: z7.array(z7.object({
5481
- label: z7.string(),
5482
- leg_indices: z7.array(z7.number()),
5483
- triggers: z7.array(triggerConfigSchema2)
5484
- })).optional().describe("Per-leg-group exit triggers for multi-structure strategies"),
5485
- baseline_mode: z7.enum(["actual", "holdToEnd"]).default("actual").describe("'actual' compares candidate vs trade's actual P&L; 'holdToEnd' compares vs last replay timestamp"),
5486
- limit: z7.number().min(1).max(200).default(50).describe("Max trades to analyze. Most recent trades selected"),
5487
- min_pl: z7.number().optional().describe("Only include trades with actual P&L >= this value"),
5488
- max_pl: z7.number().optional().describe("Only include trades with actual P&L <= this value"),
5489
- multiplier: z7.number().default(100).describe("Contract multiplier (default 100)"),
5490
- format: z7.enum(["summary", "full"]).default("summary").describe("'summary' returns aggregate stats + trigger attribution; 'full' adds per-trade breakdown")
5801
+ candidate_policy: z8.array(triggerConfigSchema2).describe("Candidate exit policy triggers to evaluate -- same schema as analyze_exit_triggers"),
5802
+ leg_groups: z8.array(
5803
+ z8.object({
5804
+ label: z8.string(),
5805
+ leg_indices: z8.array(z8.number()),
5806
+ triggers: z8.array(triggerConfigSchema2)
5807
+ })
5808
+ ).optional().describe("Per-leg-group exit triggers for multi-structure strategies"),
5809
+ baseline_mode: z8.enum(["actual", "holdToEnd"]).default("actual").describe(
5810
+ "'actual' compares candidate vs trade's actual P&L; 'holdToEnd' compares vs last replay timestamp"
5811
+ ),
5812
+ limit: z8.number().min(1).max(200).default(50).describe("Max trades to analyze. Most recent trades selected"),
5813
+ min_pl: z8.number().optional().describe("Only include trades with actual P&L >= this value"),
5814
+ max_pl: z8.number().optional().describe("Only include trades with actual P&L <= this value"),
5815
+ multiplier: z8.number().default(100).describe("Contract multiplier (default 100)"),
5816
+ format: z8.enum(["summary", "full"]).default("summary").describe(
5817
+ "'summary' returns aggregate stats + trigger attribution; 'full' adds per-trade breakdown"
5818
+ )
5491
5819
  });
5492
5820
  async function handleBatchExitAnalysis(params, baseDir, stores, injectedConn) {
5493
5821
  const {
@@ -5650,9 +5978,7 @@ async function handleBatchExitAnalysis(params, baseDir, stores, injectedConn) {
5650
5978
  if (profile) {
5651
5979
  result.profileContext = {
5652
5980
  structureType: profile.structureType,
5653
- exitRules: profile.exitRules.map(
5654
- (r) => r.description ?? `${r.type} ${r.trigger}`
5655
- )
5981
+ exitRules: profile.exitRules.map((r) => r.description ?? `${r.type} ${r.trigger}`)
5656
5982
  };
5657
5983
  }
5658
5984
  } catch {
@@ -5699,7 +6025,7 @@ function expandDateRange(fromDate, toDate) {
5699
6025
 
5700
6026
  // src/tools/sql.ts
5701
6027
  import * as path2 from "path";
5702
- import { z as z8 } from "zod";
6028
+ import { z as z9 } from "zod";
5703
6029
  var BLOCKED_PATTERNS = [
5704
6030
  // External access
5705
6031
  { pattern: /\bCOPY\b/i, operation: "COPY" },
@@ -5813,13 +6139,7 @@ function filterChain(contracts, filter) {
5813
6139
  import { open, mkdir } from "fs/promises";
5814
6140
  import { dirname, join as join2 } from "path";
5815
6141
  function backfillManifestPath(dataRoot, runId) {
5816
- return join2(
5817
- dataRoot,
5818
- "market",
5819
- "_manifests",
5820
- "thetadata-mdds-backfill",
5821
- `${runId}.ndjson`
5822
- );
6142
+ return join2(dataRoot, "market", "_manifests", "thetadata-mdds-backfill", `${runId}.ndjson`);
5823
6143
  }
5824
6144
  function backfillPartitionPath(dataRoot, underlying, date) {
5825
6145
  return join2(
@@ -5969,15 +6289,15 @@ function collectBackfillConcreteFallbacks(input) {
5969
6289
  return fallbacks;
5970
6290
  }
5971
6291
  async function appendBackfillManifestLineDurable(manifestPath, line) {
5972
- const path12 = requireNonEmpty("manifestPath", manifestPath);
6292
+ const path11 = requireNonEmpty("manifestPath", manifestPath);
5973
6293
  const text = String(line);
5974
6294
  if (!text.endsWith("\n")) {
5975
6295
  throw new Error("manifest line must end with a newline");
5976
6296
  }
5977
- const parentDir = dirname(path12);
6297
+ const parentDir = dirname(path11);
5978
6298
  await mkdir(parentDir, { recursive: true });
5979
- const existedBeforeOpen = await pathExists(path12);
5980
- const handle = await open(path12, "a");
6299
+ const existedBeforeOpen = await pathExists(path11);
6300
+ const handle = await open(path11, "a");
5981
6301
  try {
5982
6302
  await handle.writeFile(text);
5983
6303
  await handle.sync();
@@ -6043,9 +6363,9 @@ function validateManifestStatus(value) {
6043
6363
  }
6044
6364
  throw new Error("status must be prepared, committed, failed, or committed_manifest_failed");
6045
6365
  }
6046
- async function pathExists(path12) {
6366
+ async function pathExists(path11) {
6047
6367
  try {
6048
- const handle = await open(path12, "r");
6368
+ const handle = await open(path11, "r");
6049
6369
  await handle.close();
6050
6370
  return true;
6051
6371
  } catch {
@@ -6108,7 +6428,7 @@ function formatIsoDate(date) {
6108
6428
  }
6109
6429
 
6110
6430
  // src/tools/greeks-attribution.ts
6111
- import { z as z9 } from "zod";
6431
+ import { z as z10 } from "zod";
6112
6432
 
6113
6433
  // src/utils/flatfile-importer.ts
6114
6434
  function tradingDays(from, to) {
@@ -6126,19 +6446,32 @@ function tradingDays(from, to) {
6126
6446
  }
6127
6447
 
6128
6448
  // src/tools/greeks-attribution.ts
6129
- var getGreeksAttributionSchema = z9.object({
6130
- block_id: z9.string().describe("Block ID to analyze"),
6131
- mode: z9.enum(["summary", "instance"]).default("summary").describe("summary: block-level attribution. instance: single trade time-series."),
6132
- trade_index: z9.number().int().min(0).optional().describe("Trade index (required for instance mode). Use get_block_info to find trade indices."),
6133
- skip_quotes: z9.boolean().default(true).describe("Use cached bar data only (fast). Set false to fetch NBBO quotes for higher precision."),
6134
- detailed: z9.boolean().default(false).describe("false: 5 factors (delta, gamma, theta, vega, residual). true: adds charm, vanna."),
6135
- strategy: z9.string().optional().describe("Filter to trades matching this strategy name (case-insensitive).")
6449
+ var getGreeksAttributionSchema = z10.object({
6450
+ block_id: z10.string().describe("Block ID to analyze"),
6451
+ mode: z10.enum(["summary", "instance"]).default("summary").describe("summary: block-level attribution. instance: single trade time-series."),
6452
+ trade_index: z10.number().int().min(0).optional().describe(
6453
+ "Trade index (required for instance mode). Use get_block_info to find trade indices."
6454
+ ),
6455
+ skip_quotes: z10.boolean().default(true).describe(
6456
+ "Use cached bar data only (fast). Set false to fetch NBBO quotes for higher precision."
6457
+ ),
6458
+ detailed: z10.boolean().default(false).describe("false: 5 factors (delta, gamma, theta, vega, residual). true: adds charm, vanna."),
6459
+ strategy: z10.string().optional().describe("Filter to trades matching this strategy name (case-insensitive).")
6136
6460
  });
6137
6461
  var COLLAPSE_MAP = {
6138
6462
  charm: "delta",
6139
6463
  vanna: "vega"
6140
6464
  };
6141
- var FACTOR_ORDER = ["theta", "vega", "delta", "gamma", "residual", "time_and_vol", "charm", "vanna"];
6465
+ var FACTOR_ORDER = [
6466
+ "theta",
6467
+ "vega",
6468
+ "delta",
6469
+ "gamma",
6470
+ "residual",
6471
+ "time_and_vol",
6472
+ "charm",
6473
+ "vanna"
6474
+ ];
6142
6475
  function collapseFactors(factors, detailed) {
6143
6476
  const totals = /* @__PURE__ */ new Map();
6144
6477
  for (const f of factors) {
@@ -6188,9 +6521,25 @@ async function handleGetGreeksAttribution(params, baseDir, stores, injectedConn)
6188
6521
  if (trade_index == null) {
6189
6522
  throw new Error("trade_index is required for instance mode");
6190
6523
  }
6191
- return handleInstanceMode(block_id, trade_index, skip_quotes, detailed, baseDir, stores, injectedConn);
6524
+ return handleInstanceMode(
6525
+ block_id,
6526
+ trade_index,
6527
+ skip_quotes,
6528
+ detailed,
6529
+ baseDir,
6530
+ stores,
6531
+ injectedConn
6532
+ );
6192
6533
  }
6193
- return handleSummaryMode(block_id, skip_quotes, detailed, strategy, baseDir, stores, injectedConn);
6534
+ return handleSummaryMode(
6535
+ block_id,
6536
+ skip_quotes,
6537
+ detailed,
6538
+ strategy,
6539
+ baseDir,
6540
+ stores,
6541
+ injectedConn
6542
+ );
6194
6543
  }
6195
6544
  async function handleSummaryMode(block_id, skip_quotes, detailed, strategy, baseDir, stores, injectedConn) {
6196
6545
  const conn = injectedConn ?? await getConnection(baseDir);
@@ -6242,7 +6591,10 @@ async function handleSummaryMode(block_id, skip_quotes, detailed, strategy, base
6242
6591
  injectedConn
6243
6592
  ).then((result) => {
6244
6593
  for (const factor of result.factors) {
6245
- accumulated.set(factor.factor, (accumulated.get(factor.factor) ?? 0) + factor.totalPnl);
6594
+ accumulated.set(
6595
+ factor.factor,
6596
+ (accumulated.get(factor.factor) ?? 0) + factor.totalPnl
6597
+ );
6246
6598
  }
6247
6599
  actualTotalPnl += trade.actualPl;
6248
6600
  markTotalPnl += result.totalPnlChange;
@@ -6345,10 +6697,20 @@ async function handleInstanceMode(block_id, trade_index, skip_quotes, detailed,
6345
6697
  for (let i = 0; i <= stepCount; i++) {
6346
6698
  const entry = {
6347
6699
  date: getStepDate(i),
6348
- delta: getStepValue(factorSteps, "delta", i, detailed ? 0 : factorSteps.get("charm")?.[i] ?? 0),
6700
+ delta: getStepValue(
6701
+ factorSteps,
6702
+ "delta",
6703
+ i,
6704
+ detailed ? 0 : factorSteps.get("charm")?.[i] ?? 0
6705
+ ),
6349
6706
  gamma: getStepValue(factorSteps, "gamma", i, 0),
6350
6707
  theta: getStepValue(factorSteps, "theta", i, 0),
6351
- vega: getStepValue(factorSteps, "vega", i, detailed ? 0 : factorSteps.get("vanna")?.[i] ?? 0),
6708
+ vega: getStepValue(
6709
+ factorSteps,
6710
+ "vega",
6711
+ i,
6712
+ detailed ? 0 : factorSteps.get("vanna")?.[i] ?? 0
6713
+ ),
6352
6714
  residual: getStepValue(factorSteps, "residual", i, 0)
6353
6715
  };
6354
6716
  if (factorSteps.has("time_and_vol")) {
@@ -6815,11 +7177,7 @@ var ParquetSpotStore = class extends SpotStore {
6815
7177
  }));
6816
7178
  }
6817
7179
  async getCoverage(ticker, from, to) {
6818
- const tickerDir = path5.join(
6819
- resolveMarketDir(this.ctx.dataDir),
6820
- "spot",
6821
- `ticker=${ticker}`
6822
- );
7180
+ const tickerDir = path5.join(resolveMarketDir(this.ctx.dataDir), "spot", `ticker=${ticker}`);
6823
7181
  if (!existsSync4(tickerDir)) {
6824
7182
  return { earliest: null, latest: null, missingDates: [], totalDates: 0 };
6825
7183
  }
@@ -7044,9 +7402,7 @@ var ParquetEnrichedStore = class extends EnrichedStore {
7044
7402
  });
7045
7403
  const reader = await this.ctx.conn.runAndReadAll(sql);
7046
7404
  const names = reader.columnNames();
7047
- return reader.getRows().map(
7048
- (row) => Object.fromEntries(names.map((n, i) => [n, row[i]]))
7049
- );
7405
+ return reader.getRows().map((row) => Object.fromEntries(names.map((n, i) => [n, row[i]])));
7050
7406
  }
7051
7407
  async getCoverage(ticker) {
7052
7408
  const filePath = path6.join(
@@ -7119,9 +7475,7 @@ var DuckdbEnrichedStore = class extends EnrichedStore {
7119
7475
  });
7120
7476
  const reader = await this.ctx.conn.runAndReadAll(sql);
7121
7477
  const names = reader.columnNames();
7122
- return reader.getRows().map(
7123
- (row) => Object.fromEntries(names.map((n, i) => [n, row[i]]))
7124
- );
7478
+ return reader.getRows().map((row) => Object.fromEntries(names.map((n, i) => [n, row[i]])));
7125
7479
  }
7126
7480
  async getCoverage(ticker) {
7127
7481
  const tickerLit = ticker.replace(/'/g, "''");
@@ -7474,9 +7828,7 @@ var ParquetQuoteStore = class extends QuoteStore {
7474
7828
  }
7475
7829
  async readQuotes(occTickers, from, to) {
7476
7830
  if (occTickers.length === 0) return /* @__PURE__ */ new Map();
7477
- const firstUnderlying = this.ctx.tickers.resolve(
7478
- extractRoot(occTickers[0])
7479
- );
7831
+ const firstUnderlying = this.ctx.tickers.resolve(extractRoot(occTickers[0]));
7480
7832
  for (const t of occTickers) {
7481
7833
  const u = this.ctx.tickers.resolve(extractRoot(t));
7482
7834
  if (u !== firstUnderlying) {
@@ -7791,9 +8143,7 @@ var DuckdbQuoteStore = class extends QuoteStore {
7791
8143
  }
7792
8144
  async readQuotes(occTickers, from, to) {
7793
8145
  if (occTickers.length === 0) return /* @__PURE__ */ new Map();
7794
- const firstUnderlying = this.ctx.tickers.resolve(
7795
- extractRoot(occTickers[0])
7796
- );
8146
+ const firstUnderlying = this.ctx.tickers.resolve(extractRoot(occTickers[0]));
7797
8147
  for (const t of occTickers) {
7798
8148
  const u = this.ctx.tickers.resolve(extractRoot(t));
7799
8149
  if (u !== firstUnderlying) {
@@ -8071,31 +8421,27 @@ function createMarketStores(ctx) {
8071
8421
  }
8072
8422
 
8073
8423
  // src/market/tickers/schemas.ts
8074
- import { z as z10 } from "zod";
8424
+ import { z as z11 } from "zod";
8075
8425
  var TICKER_RE = /^[A-Z][A-Z0-9^_-]*$/;
8076
- var UnderlyingsFileSchema = z10.object({
8077
- version: z10.literal(1),
8078
- underlyings: z10.array(
8079
- z10.object({
8080
- underlying: z10.string().min(1).max(16).regex(TICKER_RE),
8081
- roots: z10.array(z10.string().min(1).max(16).regex(TICKER_RE)).min(1).max(32)
8426
+ var UnderlyingsFileSchema = z11.object({
8427
+ version: z11.literal(1),
8428
+ underlyings: z11.array(
8429
+ z11.object({
8430
+ underlying: z11.string().min(1).max(16).regex(TICKER_RE),
8431
+ roots: z11.array(z11.string().min(1).max(16).regex(TICKER_RE)).min(1).max(32)
8082
8432
  })
8083
8433
  )
8084
8434
  });
8085
- var registerUnderlyingSchema = z10.object({
8086
- underlying: z10.string().min(1).max(16).regex(TICKER_RE).describe("Canonical underlying symbol, e.g. SPX"),
8087
- roots: z10.array(z10.string().min(1).max(16).regex(TICKER_RE)).min(1).max(32).describe(
8088
- "OCC roots that resolve to this underlying, e.g. ['SPX','SPXW','SPXQ']"
8089
- )
8435
+ var registerUnderlyingSchema = z11.object({
8436
+ underlying: z11.string().min(1).max(16).regex(TICKER_RE).describe("Canonical underlying symbol, e.g. SPX"),
8437
+ roots: z11.array(z11.string().min(1).max(16).regex(TICKER_RE)).min(1).max(32).describe("OCC roots that resolve to this underlying, e.g. ['SPX','SPXW','SPXQ']")
8090
8438
  });
8091
- var unregisterUnderlyingSchema = z10.object({
8092
- underlying: z10.string().min(1).max(16).regex(TICKER_RE).describe("Underlying to remove. Bundled defaults cannot be removed.")
8439
+ var unregisterUnderlyingSchema = z11.object({
8440
+ underlying: z11.string().min(1).max(16).regex(TICKER_RE).describe("Underlying to remove. Bundled defaults cannot be removed.")
8093
8441
  });
8094
- var listUnderlyingsSchema = z10.object({});
8095
- var resolveRootSchema = z10.object({
8096
- input: z10.string().min(1).max(32).describe(
8097
- "Bare root ('SPXW') or full OCC ticker ('SPXW251219C05000000')"
8098
- )
8442
+ var listUnderlyingsSchema = z11.object({});
8443
+ var resolveRootSchema = z11.object({
8444
+ input: z11.string().min(1).max(32).describe("Bare root ('SPXW') or full OCC ticker ('SPXW251219C05000000')")
8099
8445
  });
8100
8446
 
8101
8447
  // src/market/tickers/registry.ts
@@ -8107,9 +8453,7 @@ function validate(underlying, roots) {
8107
8453
  }
8108
8454
  for (const r of roots) {
8109
8455
  if (!TICKER_RE.test(r)) {
8110
- throw new Error(
8111
- `TickerRegistry: invalid root "${r}" \u2014 must match ${TICKER_RE.source}`
8112
- );
8456
+ throw new Error(`TickerRegistry: invalid root "${r}" \u2014 must match ${TICKER_RE.source}`);
8113
8457
  }
8114
8458
  }
8115
8459
  }
@@ -8192,9 +8536,7 @@ var TickerRegistry = class {
8192
8536
  unregister(underlying) {
8193
8537
  const entry = this.entries.get(underlying);
8194
8538
  if (!entry) {
8195
- throw new Error(
8196
- `TickerRegistry.unregister: unknown underlying "${underlying}"`
8197
- );
8539
+ throw new Error(`TickerRegistry.unregister: unknown underlying "${underlying}"`);
8198
8540
  }
8199
8541
  if (entry.source === "default") {
8200
8542
  throw new Error(
@@ -8482,7 +8824,13 @@ var MarketIngestor = class {
8482
8824
  assetClass
8483
8825
  });
8484
8826
  } catch (error) {
8485
- const mapped = this.mapProviderFailure(provider, "bars", normalizedTicker, error, assetClass);
8827
+ const mapped = this.mapProviderFailure(
8828
+ provider,
8829
+ "bars",
8830
+ normalizedTicker,
8831
+ error,
8832
+ assetClass
8833
+ );
8486
8834
  if (mapped) return mapped;
8487
8835
  throw error;
8488
8836
  }
@@ -8735,11 +9083,7 @@ var MarketIngestor = class {
8735
9083
  if (mapped) return mapped;
8736
9084
  throw error;
8737
9085
  }
8738
- const written = await this.writeQuotesForTicker(
8739
- provider,
8740
- ticker,
8741
- quotes
8742
- );
9086
+ const written = await this.writeQuotesForTicker(provider, ticker, quotes);
8743
9087
  totalRows += written.rowsWritten;
8744
9088
  if (written.minDate && (!minDate || written.minDate < minDate)) minDate = written.minDate;
8745
9089
  if (written.maxDate && (!maxDate || written.maxDate > maxDate)) maxDate = written.maxDate;
@@ -8775,12 +9119,7 @@ var MarketIngestor = class {
8775
9119
  for (const underlying of underlyings) {
8776
9120
  const upperUnderlying = underlying.toUpperCase();
8777
9121
  for (const date of dates) {
8778
- const drain = await this.drainBulkQuotes(
8779
- provider,
8780
- upperUnderlying,
8781
- date,
8782
- onProgress
8783
- );
9122
+ const drain = await this.drainBulkQuotes(provider, upperUnderlying, date, onProgress);
8784
9123
  if (drain.rowsWritten > 0) {
8785
9124
  totalRows += drain.rowsWritten;
8786
9125
  if (!minDate || date < minDate) minDate = date;
@@ -8840,7 +9179,11 @@ var MarketIngestor = class {
8840
9179
  totalContracts: info.totalContracts
8841
9180
  });
8842
9181
  } : void 0;
8843
- const stream = provider.fetchBulkQuotes({ underlying: upperUnderlying, date, onGroupComplete });
9182
+ const stream = provider.fetchBulkQuotes({
9183
+ underlying: upperUnderlying,
9184
+ date,
9185
+ onGroupComplete
9186
+ });
8844
9187
  for await (const chunk of stream) {
8845
9188
  for (const row of chunk) {
8846
9189
  const root = extractRoot(row.ticker);
@@ -8891,15 +9234,12 @@ var MarketIngestor = class {
8891
9234
  );
8892
9235
  } catch (error) {
8893
9236
  const message = error instanceof Error ? error.message : String(error);
8894
- console.warn(
8895
- "[drainBulkQuotes] enrichQuoteRows failed; skipping batch",
8896
- {
8897
- underlying: resolvedUnderlying,
8898
- date,
8899
- rows: rows.length,
8900
- error: message
8901
- }
8902
- );
9237
+ console.warn("[drainBulkQuotes] enrichQuoteRows failed; skipping batch", {
9238
+ underlying: resolvedUnderlying,
9239
+ date,
9240
+ rows: rows.length,
9241
+ error: message
9242
+ });
8903
9243
  skipped.push({
8904
9244
  underlying: resolvedUnderlying,
8905
9245
  date,
@@ -8986,16 +9326,13 @@ var MarketIngestor = class {
8986
9326
  );
8987
9327
  } catch (error) {
8988
9328
  const message = error instanceof Error ? error.message : String(error);
8989
- console.warn(
8990
- "[writeQuotesForTicker] enrichQuoteRows failed; skipping batch",
8991
- {
8992
- underlying,
8993
- date,
8994
- ticker,
8995
- rows: rows.length,
8996
- error: message
8997
- }
8998
- );
9329
+ console.warn("[writeQuotesForTicker] enrichQuoteRows failed; skipping batch", {
9330
+ underlying,
9331
+ date,
9332
+ ticker,
9333
+ rows: rows.length,
9334
+ error: message
9335
+ });
8999
9336
  skipped.push({
9000
9337
  underlying,
9001
9338
  date,
@@ -9062,7 +9399,12 @@ var MarketIngestor = class {
9062
9399
  for (const underlying of opts.underlyings) {
9063
9400
  const upperUnderlying = underlying.toUpperCase();
9064
9401
  const assetClass = this.detectAssetClass(upperUnderlying);
9065
- const unsupported = this.preflightProviderSupport(provider, "chain", upperUnderlying, assetClass);
9402
+ const unsupported = this.preflightProviderSupport(
9403
+ provider,
9404
+ "chain",
9405
+ upperUnderlying,
9406
+ assetClass
9407
+ );
9066
9408
  if (unsupported) return unsupported;
9067
9409
  const dates = this.enumerateDates(opts.from, opts.to);
9068
9410
  for (const date of dates) {
@@ -9074,7 +9416,13 @@ var MarketIngestor = class {
9074
9416
  expired: true
9075
9417
  });
9076
9418
  } catch (error) {
9077
- const mapped = this.mapProviderFailure(provider, "chain", upperUnderlying, error, assetClass);
9419
+ const mapped = this.mapProviderFailure(
9420
+ provider,
9421
+ "chain",
9422
+ upperUnderlying,
9423
+ error,
9424
+ assetClass
9425
+ );
9078
9426
  if (mapped) return mapped;
9079
9427
  throw error;
9080
9428
  }
@@ -9222,43 +9570,75 @@ var MarketIngestor = class {
9222
9570
  case "spot_bars": {
9223
9571
  const ticker = opts.partition.ticker;
9224
9572
  if (!ticker) {
9225
- return { status: "error", rowsWritten: 0, error: "partition.ticker is required for datasetType='spot_bars'" };
9573
+ return {
9574
+ status: "error",
9575
+ rowsWritten: 0,
9576
+ error: "partition.ticker is required for datasetType='spot_bars'"
9577
+ };
9226
9578
  }
9227
9579
  const { rowCount } = await this.deps.stores.spot.writeFromSelect(
9228
9580
  { ticker: ticker.toUpperCase(), date: partitionDate },
9229
9581
  opts.selectSql
9230
9582
  );
9231
- return { status: "ok", rowsWritten: rowCount, dateRange: { from: partitionDate, to: partitionDate } };
9583
+ return {
9584
+ status: "ok",
9585
+ rowsWritten: rowCount,
9586
+ dateRange: { from: partitionDate, to: partitionDate }
9587
+ };
9232
9588
  }
9233
9589
  case "option_quotes": {
9234
9590
  const underlying = opts.partition.underlying;
9235
9591
  if (!underlying) {
9236
- return { status: "error", rowsWritten: 0, error: "partition.underlying is required for datasetType='option_quotes'" };
9592
+ return {
9593
+ status: "error",
9594
+ rowsWritten: 0,
9595
+ error: "partition.underlying is required for datasetType='option_quotes'"
9596
+ };
9237
9597
  }
9238
9598
  const { rowCount } = await this.deps.stores.quote.writeFromSelect(
9239
9599
  { underlying: underlying.toUpperCase(), date: partitionDate },
9240
9600
  opts.selectSql
9241
9601
  );
9242
- return { status: "ok", rowsWritten: rowCount, dateRange: { from: partitionDate, to: partitionDate } };
9602
+ return {
9603
+ status: "ok",
9604
+ rowsWritten: rowCount,
9605
+ dateRange: { from: partitionDate, to: partitionDate }
9606
+ };
9243
9607
  }
9244
9608
  case "option_chain": {
9245
9609
  const underlying = opts.partition.underlying;
9246
9610
  if (!underlying) {
9247
- return { status: "error", rowsWritten: 0, error: "partition.underlying is required for datasetType='option_chain'" };
9611
+ return {
9612
+ status: "error",
9613
+ rowsWritten: 0,
9614
+ error: "partition.underlying is required for datasetType='option_chain'"
9615
+ };
9248
9616
  }
9249
9617
  const { rowCount } = await this.deps.stores.chain.writeFromSelect(
9250
9618
  { underlying: underlying.toUpperCase(), date: partitionDate },
9251
9619
  opts.selectSql
9252
9620
  );
9253
- return { status: "ok", rowsWritten: rowCount, dateRange: { from: partitionDate, to: partitionDate } };
9621
+ return {
9622
+ status: "ok",
9623
+ rowsWritten: rowCount,
9624
+ dateRange: { from: partitionDate, to: partitionDate }
9625
+ };
9254
9626
  }
9255
9627
  default: {
9256
9628
  const _exhaustive = opts.datasetType;
9257
- return { status: "error", rowsWritten: 0, error: `Unknown datasetType: ${String(_exhaustive)}` };
9629
+ return {
9630
+ status: "error",
9631
+ rowsWritten: 0,
9632
+ error: `Unknown datasetType: ${String(_exhaustive)}`
9633
+ };
9258
9634
  }
9259
9635
  }
9260
9636
  } catch (err) {
9261
- return { status: "error", rowsWritten: 0, error: err instanceof Error ? err.message : String(err) };
9637
+ return {
9638
+ status: "error",
9639
+ rowsWritten: 0,
9640
+ error: err instanceof Error ? err.message : String(err)
9641
+ };
9262
9642
  }
9263
9643
  }
9264
9644
  async computeVixContext(opts) {
@@ -9348,7 +9728,11 @@ var MarketIngestor = class {
9348
9728
  const coverage = {};
9349
9729
  for (const ticker of opts.spotTickers) {
9350
9730
  try {
9351
- const cov = await this.deps.stores.spot.getCoverage(ticker.toUpperCase(), opts.asOf, opts.asOf);
9731
+ const cov = await this.deps.stores.spot.getCoverage(
9732
+ ticker.toUpperCase(),
9733
+ opts.asOf,
9734
+ opts.asOf
9735
+ );
9352
9736
  coverage[ticker] = {
9353
9737
  totalDates: cov.totalDates,
9354
9738
  dateRange: cov.earliest && cov.latest ? { from: cov.earliest, to: cov.latest } : void 0
@@ -9372,7 +9756,13 @@ var MarketIngestor = class {
9372
9756
  else status = "ok";
9373
9757
  return {
9374
9758
  status,
9375
- perOperation: { spot: spotResults, chain: chainResults, quotes: quoteResults, openInterest: openInterestResults, vixContext },
9759
+ perOperation: {
9760
+ spot: spotResults,
9761
+ chain: chainResults,
9762
+ quotes: quoteResults,
9763
+ openInterest: openInterestResults,
9764
+ vixContext
9765
+ },
9376
9766
  coverage,
9377
9767
  errors,
9378
9768
  ...aggregateSkipped.length > 0 ? { skipped: aggregateSkipped } : {}
@@ -9455,9 +9845,7 @@ function selectVerificationSampleDates(fromDate = "2022-01-01", toDate = (/* @__
9455
9845
  ...PHASE_5_KNOWN_EVENTS.map((s) => s.date),
9456
9846
  ...PHASE_5_STRUCTURAL_DATES.map((s) => s.date)
9457
9847
  ]);
9458
- const candidates = enumerateWeekdays(fromDate, toDate).filter(
9459
- (d) => !selectedSet.has(d)
9460
- );
9848
+ const candidates = enumerateWeekdays(fromDate, toDate).filter((d) => !selectedSet.has(d));
9461
9849
  const random = [];
9462
9850
  const pool = [...candidates];
9463
9851
  for (let i = 0; i < randomCount && pool.length > 0; i++) {
@@ -9582,11 +9970,10 @@ function compareRow(oldRow, newRow, kind, ticker, date) {
9582
9970
  }
9583
9971
 
9584
9972
  // src/utils/calibration-probe.ts
9585
- import * as path11 from "path";
9586
9973
  import { DuckDBInstance } from "@duckdb/node-api";
9587
9974
  async function calibrateProviderFetch(ticker, probeDates, dataRoot) {
9588
9975
  const provider = getProvider();
9589
- const dbPath = path11.join(dataRoot, "database", "market.duckdb");
9976
+ const dbPath = resolveDbPath(dataRoot, "market");
9590
9977
  const instance = await DuckDBInstance.create(dbPath);
9591
9978
  const conn = await instance.connect();
9592
9979
  const deltas = [];
@@ -9701,6 +10088,7 @@ export {
9701
10088
  appendBackfillManifestLineDurable,
9702
10089
  applyQuoteGreeks,
9703
10090
  applyQuoteGreeksParallel,
10091
+ armHoldingPeriods,
9704
10092
  assessPrecision,
9705
10093
  bachelierDelta,
9706
10094
  bachelierGamma,
@@ -9717,6 +10105,8 @@ export {
9717
10105
  bsPrice,
9718
10106
  bsTheta,
9719
10107
  bsVega,
10108
+ buildArmDaySeries,
10109
+ buildBlockTradingDayIndex,
9720
10110
  buildEnrichmentPlan,
9721
10111
  buildFilterPredicate,
9722
10112
  buildLookaheadFreeQuery,
@@ -9884,6 +10274,7 @@ export {
9884
10274
  optionHistoryQuote,
9885
10275
  optionHistoryQuoteBand,
9886
10276
  optionListContracts,
10277
+ pairedComparisonInputSchema,
9887
10278
  parseBackfillOccTicker,
9888
10279
  parseCsvToBars,
9889
10280
  parseDatabaseRowsToBars,
@@ -9898,6 +10289,7 @@ export {
9898
10289
  readJsonFile,
9899
10290
  readParquetFilesSql,
9900
10291
  regimeAllocationAdvisorSchema,
10292
+ registerPairedComparisonTool,
9901
10293
  registerTickerTools,
9902
10294
  registerUnderlyingSchema,
9903
10295
  replayTradeSchema,
@@ -9915,6 +10307,7 @@ export {
9915
10307
  rthDailyAggregateSubquery,
9916
10308
  runContextEnrichment,
9917
10309
  runEnrichment,
10310
+ runPairedBootstrapComparison,
9918
10311
  saveUserOverride,
9919
10312
  scoreDataQuality,
9920
10313
  selectVerificationSampleDates,