tradeblocks-mcp 3.0.3 → 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 (67) hide show
  1. package/README.md +3 -12
  2. package/dist/{chunk-C6LL746C.js → chunk-2I3S2ZLT.js} +259 -1
  3. package/dist/chunk-2I3S2ZLT.js.map +1 -0
  4. package/dist/{chunk-UBUC5A66.js → chunk-4HZLVUOZ.js} +81 -76
  5. package/dist/chunk-4HZLVUOZ.js.map +1 -0
  6. package/dist/{chunk-VDU25Z6X.js → chunk-NWNSKALN.js} +2 -2
  7. package/dist/{chunk-AP7IHUUR.js → chunk-WNTYA7ER.js} +2 -2
  8. package/dist/{chunk-6S37CXUA.js → chunk-YZA4RS76.js} +14 -6
  9. package/dist/chunk-YZA4RS76.js.map +1 -0
  10. package/dist/{chunk-SEUZYQGQ.js → chunk-ZTJXC3LS.js} +2 -2
  11. package/dist/daily-log-processor-MZW2XBZY.js +9 -0
  12. package/dist/market-provider-6U33TQUT.js +16 -0
  13. package/dist/{sync-EVLKB3ZJ.js → sync-3UBC37VW.js} +6 -6
  14. package/dist/test-exports.js +560 -271
  15. package/dist/test-exports.js.map +1 -1
  16. package/dist/trade-processor-DKDCWGKD.js +9 -0
  17. package/package.json +2 -6
  18. package/server/{chunk-YUCOAJ4Z.js → chunk-536NSFED.js} +2 -2
  19. package/server/{chunk-GH2552SE.js → chunk-A3UKD64A.js} +2 -2
  20. package/server/{chunk-ZBJCF4ZG.js → chunk-OMUDNJBJ.js} +2 -2
  21. package/server/{chunk-BOPHW5M6.js → chunk-RJDJHZ5Y.js} +414 -101
  22. package/server/chunk-RJDJHZ5Y.js.map +1 -0
  23. package/server/{chunk-4P7D7YZP.js → chunk-RM4B2VKS.js} +72 -71
  24. package/server/chunk-RM4B2VKS.js.map +1 -0
  25. package/server/{chunk-OBYKFW2B.js → chunk-SY6GDNVI.js} +14 -6
  26. package/server/chunk-SY6GDNVI.js.map +1 -0
  27. package/server/daily-log-processor-B2VPU2FQ.js +10 -0
  28. package/server/index.js +787 -552
  29. package/server/index.js.map +1 -1
  30. package/server/market-provider-L56RFHAC.js +17 -0
  31. package/server/{sync-V25UQJA3.js → sync-CENKDN53.js} +6 -6
  32. package/server/trade-processor-3PI4LHZI.js +10 -0
  33. package/src/db/backtest-schemas.ts +26 -4
  34. package/src/db/data-root.ts +22 -0
  35. package/src/test-exports.ts +12 -0
  36. package/src/tools/blocks/index.ts +2 -0
  37. package/src/tools/blocks/paired-comparison.ts +425 -0
  38. package/src/tools/imports.ts +2 -2
  39. package/src/utils/block-loader.ts +1 -1
  40. package/src/utils/calibration-probe.ts +2 -2
  41. package/dist/chunk-6S37CXUA.js.map +0 -1
  42. package/dist/chunk-C6LL746C.js.map +0 -1
  43. package/dist/chunk-UBUC5A66.js.map +0 -1
  44. package/dist/daily-log-processor-BY3ISY6K.js +0 -9
  45. package/dist/market-provider-H3ARIVZ4.js +0 -16
  46. package/dist/trade-processor-EYA3I3XB.js +0 -9
  47. package/manifest.json +0 -133
  48. package/server/chunk-4P7D7YZP.js.map +0 -1
  49. package/server/chunk-BOPHW5M6.js.map +0 -1
  50. package/server/chunk-OBYKFW2B.js.map +0 -1
  51. package/server/daily-log-processor-ENEUT22A.js +0 -10
  52. package/server/market-provider-B437HKLW.js +0 -17
  53. package/server/trade-processor-L3PIQ5TG.js +0 -10
  54. /package/dist/{chunk-VDU25Z6X.js.map → chunk-NWNSKALN.js.map} +0 -0
  55. /package/dist/{chunk-AP7IHUUR.js.map → chunk-WNTYA7ER.js.map} +0 -0
  56. /package/dist/{chunk-SEUZYQGQ.js.map → chunk-ZTJXC3LS.js.map} +0 -0
  57. /package/dist/{daily-log-processor-BY3ISY6K.js.map → daily-log-processor-MZW2XBZY.js.map} +0 -0
  58. /package/dist/{market-provider-H3ARIVZ4.js.map → market-provider-6U33TQUT.js.map} +0 -0
  59. /package/dist/{sync-EVLKB3ZJ.js.map → sync-3UBC37VW.js.map} +0 -0
  60. /package/dist/{trade-processor-EYA3I3XB.js.map → trade-processor-DKDCWGKD.js.map} +0 -0
  61. /package/server/{chunk-YUCOAJ4Z.js.map → chunk-536NSFED.js.map} +0 -0
  62. /package/server/{chunk-GH2552SE.js.map → chunk-A3UKD64A.js.map} +0 -0
  63. /package/server/{chunk-ZBJCF4ZG.js.map → chunk-OMUDNJBJ.js.map} +0 -0
  64. /package/server/{daily-log-processor-ENEUT22A.js.map → daily-log-processor-B2VPU2FQ.js.map} +0 -0
  65. /package/server/{market-provider-B437HKLW.js.map → market-provider-L56RFHAC.js.map} +0 -0
  66. /package/server/{sync-V25UQJA3.js.map → sync-CENKDN53.js.map} +0 -0
  67. /package/server/{trade-processor-L3PIQ5TG.js.map → trade-processor-3PI4LHZI.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-UBUC5A66.js";
84
+ } from "./chunk-4HZLVUOZ.js";
84
85
  import {
85
86
  BACHELIER_DTE_THRESHOLD,
86
87
  IvSolverPool,
@@ -156,34 +157,48 @@ import {
156
157
  stockHistoryOhlc,
157
158
  thetaTimestampToEtMinute,
158
159
  toMassiveTicker
159
- } from "./chunk-VDU25Z6X.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-C6LL746C.js";
165
- import "./chunk-AP7IHUUR.js";
166
- import "./chunk-SEUZYQGQ.js";
167
- import "./chunk-6S37CXUA.js";
167
+ } from "./chunk-2I3S2ZLT.js";
168
+ import "./chunk-WNTYA7ER.js";
169
+ import "./chunk-ZTJXC3LS.js";
170
+ import "./chunk-YZA4RS76.js";
168
171
  import "./chunk-27S67XW3.js";
169
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
@@ -232,6 +247,294 @@ function filterDailyLogsByDateRange(dailyLogs, startDate, endDate) {
232
247
  return filtered;
233
248
  }
234
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
+
235
538
  // src/utils/schema-metadata.ts
236
539
  var SCHEMA_DESCRIPTIONS = {
237
540
  trades: {
@@ -2589,113 +2892,94 @@ function buildFilterPredicate(filter) {
2589
2892
  }
2590
2893
 
2591
2894
  // src/tools/profiles.ts
2592
- import { z } from "zod";
2593
-
2594
- // src/utils/output-formatter.ts
2595
- function createToolOutput(summary, data) {
2596
- return {
2597
- content: [
2598
- { type: "text", text: summary },
2599
- {
2600
- type: "resource",
2601
- resource: {
2602
- uri: "data:application/json",
2603
- mimeType: "application/json",
2604
- text: JSON.stringify(data)
2605
- }
2606
- }
2607
- ]
2608
- };
2609
- }
2610
-
2611
- // src/tools/profiles.ts
2612
- var profileStrategySchema = z.object({
2613
- blockId: z.string().describe("Block ID (block_id) to associate the profile with"),
2614
- strategyName: z.string().describe("Human-readable strategy name (e.g., 'Pickle RIC v2')"),
2615
- 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(
2616
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."
2617
2901
  ),
2618
- greeksBias: z.string().describe(
2902
+ greeksBias: z2.string().describe(
2619
2903
  "Primary greeks exposure: theta_positive, vega_negative, delta_neutral, delta_positive, delta_negative, gamma_scalp, etc."
2620
2904
  ),
2621
- thesis: z.string().default("").describe("Free-text description of the strategy thesis"),
2622
- legs: z.array(
2623
- z.object({
2624
- type: z.string().describe("Leg type: long_put, short_call, long_call, short_put, etc."),
2625
- strike: z.string().describe("Strike selection: ATM, 5-delta, 30-delta, etc."),
2626
- expiry: z.string().describe("Expiry selection: same-day, weekly, 45-DTE, etc."),
2627
- quantity: z.number().describe("Quantity (positive=long, negative=short)"),
2628
- strikeMethod: z.enum(["delta", "dollar_price", "offset", "percentage"]).optional().describe("How strike is selected"),
2629
- 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)")
2630
2914
  })
2631
2915
  ).default([]).describe("Structured leg descriptions"),
2632
- entryFilters: z.array(
2633
- z.object({
2634
- field: z.string().describe("Market data field: VIX_Close, RSI_14, Vol_Regime, etc."),
2635
- operator: z.string().describe("Comparison operator: >, <, >=, <=, ==, between, in"),
2636
- value: z.union([z.string(), z.number(), z.array(z.union([z.string(), z.number()]))]).describe("Filter value or array for between/in operators"),
2637
- description: z.string().optional().describe("Human-readable description of this filter"),
2638
- source: z.enum(["market", "execution"]).optional().describe(
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(
2639
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."
2640
2924
  )
2641
2925
  })
2642
2926
  ).default([]).describe(
2643
2927
  "Entry condition filters. Tag each with source: 'market' (testable in analysis) or 'execution' (OO/platform-level, skipped in analysis)."
2644
2928
  ),
2645
- exitRules: z.array(
2646
- z.object({
2647
- type: z.string().describe("Rule type: stop_loss, profit_target, time_exit, conditional"),
2648
- trigger: z.string().describe("Trigger condition: '200% of credit', '50% of max profit', '15:00 ET'"),
2649
- description: z.string().optional().describe("Human-readable description"),
2650
- stopLossType: z.enum(["percentage", "dollar", "sl_ratio", "debit_percentage"]).optional().describe("Stop loss calculation method"),
2651
- stopLossValue: z.number().optional().describe("Stop loss numeric value"),
2652
- monitoring: z.object({
2653
- granularity: z.enum(["intra_minute", "candle_close", "end_of_bar"]).optional().describe("Price check frequency"),
2654
- priceSource: z.enum(["nbbo", "mid", "last"]).optional().describe("Which price to use")
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")
2655
2939
  }).optional().describe("Monitoring configuration for this rule"),
2656
- slippage: z.number().optional().describe("Per-rule slippage override")
2940
+ slippage: z2.number().optional().describe("Per-rule slippage override")
2657
2941
  })
2658
2942
  ).default([]).describe("Exit rules and triggers"),
2659
- expectedRegimes: z.array(z.enum(["very_low", "low", "below_avg", "above_avg", "high", "extreme"])).default([]).describe(
2943
+ expectedRegimes: z2.array(z2.enum(["very_low", "low", "below_avg", "above_avg", "high", "extreme"])).default([]).describe(
2660
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+"
2661
2945
  ),
2662
- keyMetrics: z.object({
2663
- expectedWinRate: z.number().optional().describe("Expected win rate (0-1)"),
2664
- targetPremium: z.number().optional().describe("Target premium collected ($)"),
2665
- maxLoss: z.number().optional().describe("Maximum loss per contract ($)"),
2666
- profitTarget: z.number().optional().describe("Profit target ($ or %)")
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 %)")
2667
2951
  }).passthrough().default({}).describe("Performance benchmarks and strategy-specific metrics"),
2668
- positionSizing: z.object({
2669
- method: z.string().describe("Sizing method: pct_of_portfolio, fixed_contracts, fixed_dollar, discretionary"),
2670
- allocationPct: z.number().optional().describe("Portfolio allocation percentage (e.g., 2 for 2%)"),
2671
- maxContracts: z.number().optional().describe("Maximum contracts per trade"),
2672
- maxAllocationDollar: z.number().optional().describe("Maximum dollar allocation per trade"),
2673
- maxOpenPositions: z.number().optional().describe("Maximum concurrent open positions"),
2674
- description: z.string().optional().describe("Free-text sizing notes"),
2675
- backtestAllocationPct: z.number().optional().describe("Allocation % used in backtest"),
2676
- liveAllocationPct: z.number().optional().describe("Allocation % used in live portfolio"),
2677
- maxContractsPerTrade: z.number().optional().describe("Per-entry contract cap (distinct from maxContracts hard cap)")
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)")
2678
2962
  }).optional().describe(
2679
2963
  "Position sizing rules. Per-block \u2014 same strategy in backtest vs portfolio may have different sizing."
2680
2964
  ),
2681
- underlying: z.string().optional().describe("Underlying symbol: SPX, QQQ, etc."),
2682
- reEntry: z.boolean().optional().describe("Strategy supports re-entry on same day"),
2683
- capProfits: z.boolean().optional().describe("Profits are capped by structure"),
2684
- capLosses: z.boolean().optional().describe("Losses are capped by structure"),
2685
- requireTwoPricesPT: z.boolean().optional().describe("Profit target requires two prices"),
2686
- closeOnCompletion: z.boolean().optional().describe("Close entire position when any leg hits target"),
2687
- ignoreMarginReq: z.boolean().optional().describe("Strategy ignores standard margin requirements")
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")
2688
2972
  });
2689
- var getStrategyProfileSchema = z.object({
2690
- blockId: z.string().describe("Block ID to look up"),
2691
- 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")
2692
2976
  });
2693
- var listProfilesSchema = z.object({
2694
- 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.")
2695
2979
  });
2696
- var deleteProfileSchema = z.object({
2697
- blockId: z.string().describe("Block ID of the profile to delete"),
2698
- 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")
2699
2983
  });
2700
2984
  async function handleProfileStrategy(input, baseDir) {
2701
2985
  await upgradeToReadWrite(baseDir);
@@ -2781,7 +3065,7 @@ async function handleDeleteProfile(input, baseDir) {
2781
3065
  }
2782
3066
 
2783
3067
  // src/tools/profile-analysis.ts
2784
- import { z as z2 } from "zod";
3068
+ import { z as z3 } from "zod";
2785
3069
  function formatTradeDate(date) {
2786
3070
  if (typeof date === "string") {
2787
3071
  const match = date.match(/^(\d{4})-(\d{2})-(\d{2})/);
@@ -2937,10 +3221,10 @@ function findBucket(value, buckets) {
2937
3221
  }
2938
3222
  return null;
2939
3223
  }
2940
- var analyzeStructureFitSchema = z2.object({
2941
- blockId: z2.string().describe("Block ID to analyze"),
2942
- strategyName: z2.string().describe("Strategy name matching a stored profile"),
2943
- 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)")
2944
3228
  });
2945
3229
  async function handleAnalyzeStructureFit(input, baseDir) {
2946
3230
  const { blockId, strategyName } = input;
@@ -3113,11 +3397,11 @@ async function handleAnalyzeStructureFit(input, baseDir) {
3113
3397
  }
3114
3398
  });
3115
3399
  }
3116
- var validateEntryFiltersSchema = z2.object({
3117
- blockId: z2.string().describe("Block ID to analyze"),
3118
- strategyName: z2.string().describe("Strategy name matching a stored profile"),
3119
- minTrades: z2.number().optional().default(10).describe("Minimum trades per group for reliable stats"),
3120
- 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)")
3121
3405
  });
3122
3406
  async function handleValidateEntryFilters(input, baseDir) {
3123
3407
  const { blockId, strategyName } = input;
@@ -3337,9 +3621,9 @@ async function handleValidateEntryFilters(input, baseDir) {
3337
3621
  warnings
3338
3622
  });
3339
3623
  }
3340
- var portfolioStructureMapSchema = z2.object({
3341
- blockId: z2.string().optional().describe("Block ID to analyze. When omitted, aggregate across all blocks."),
3342
- 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)")
3343
3627
  });
3344
3628
  async function handlePortfolioStructureMap(input, baseDir) {
3345
3629
  try {
@@ -3531,7 +3815,7 @@ async function handlePortfolioStructureMap(input, baseDir) {
3531
3815
  }
3532
3816
 
3533
3817
  // src/tools/regime-advisor.ts
3534
- import { z as z3 } from "zod";
3818
+ import { z as z4 } from "zod";
3535
3819
  function formatTradeDate2(date) {
3536
3820
  if (typeof date === "string") {
3537
3821
  const match = date.match(/^(\d{4})-(\d{2})-(\d{2})/);
@@ -3597,9 +3881,9 @@ var VOL_REGIME_LABELS2 = {
3597
3881
  5: "high",
3598
3882
  6: "extreme"
3599
3883
  };
3600
- var regimeAllocationAdvisorSchema = z3.object({
3601
- blockId: z3.string().optional().describe("Block ID to analyze. When omitted, aggregate across all profiled strategies."),
3602
- minTrades: z3.number().optional().default(5).describe("Minimum trades per regime cell for reliable stats (default: 5)")
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)")
3603
3887
  });
3604
3888
  async function handleRegimeAllocationAdvisor(input, baseDir) {
3605
3889
  const minTrades = input.minTrades ?? 5;
@@ -3795,7 +4079,7 @@ function getResolvedProviderCapabilities(env = process.env) {
3795
4079
  }
3796
4080
 
3797
4081
  // src/tools/replay.ts
3798
- import { z as z4 } from "zod";
4082
+ import { z as z5 } from "zod";
3799
4083
 
3800
4084
  // src/market/tickers/resolver.ts
3801
4085
  var OCC_RE = /^([A-Z]+)\d{6}[CP]\d{6,11}$/;
@@ -3814,36 +4098,36 @@ function rootToUnderlying(input, registry) {
3814
4098
  }
3815
4099
 
3816
4100
  // src/tools/replay.ts
3817
- var replayTradeSchema = z4.object({
4101
+ var replayTradeSchema = z5.object({
3818
4102
  // Mode A: Hypothetical / explicit legs
3819
- legs: z4.array(
3820
- z4.object({
3821
- ticker: z4.string().describe("Underlying ticker, e.g., 'SPY', 'SPX'"),
3822
- strike: z4.number().describe("Strike price"),
3823
- type: z4.enum(["C", "P"]).describe("Call or Put"),
3824
- expiry: z4.string().describe("Expiration date YYYY-MM-DD"),
3825
- quantity: z4.number().describe("Positive = long, negative = short"),
3826
- 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)")
3827
4111
  })
3828
4112
  ).optional().describe("Explicit leg definitions for hypothetical replay"),
3829
4113
  // Mode B: Tradelog replay
3830
- block_id: z4.string().optional().describe("Block ID to load trade from"),
3831
- trade_index: z4.number().optional().describe("0-based index of trade in block's tradelog (ordered by date_opened)"),
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)"),
3832
4116
  // Common fields
3833
- open_date: z4.string().optional().describe(
4117
+ open_date: z5.string().optional().describe(
3834
4118
  "Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)"
3835
4119
  ),
3836
- close_date: z4.string().optional().describe(
4120
+ close_date: z5.string().optional().describe(
3837
4121
  "Trade close date YYYY-MM-DD (required for hypothetical, auto-resolved for tradelog)"
3838
4122
  ),
3839
- multiplier: z4.number().default(100).describe("Contract multiplier (default 100 for standard options)"),
3840
- 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(
3841
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"
3842
4126
  ),
3843
- close_at: z4.enum(["trade", "expiry"]).default("trade").describe(
4127
+ close_at: z5.enum(["trade", "expiry"]).default("trade").describe(
3844
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."
3845
4129
  ),
3846
- skip_quotes: z4.boolean().default(false).describe(
4130
+ skip_quotes: z5.boolean().default(false).describe(
3847
4131
  "Skip NBBO quote enrichment for option bars. Faster, but uses cached trade bars / HL2 marks."
3848
4132
  )
3849
4133
  });
@@ -4128,15 +4412,15 @@ async function handleReplayTrade(params, baseDir, stores, injectedConn) {
4128
4412
  }
4129
4413
 
4130
4414
  // src/tools/snapshot.ts
4131
- import { z as z5 } from "zod";
4132
- var getOptionSnapshotSchema = z5.object({
4133
- underlying: z5.string().describe("Underlying ticker symbol (e.g., 'SPX', 'SPY', 'AAPL')"),
4134
- strike_price_gte: z5.number().optional().describe("Minimum strike price filter"),
4135
- strike_price_lte: z5.number().optional().describe("Maximum strike price filter"),
4136
- expiration_date_gte: z5.string().optional().describe("Earliest expiration date (YYYY-MM-DD)"),
4137
- expiration_date_lte: z5.string().optional().describe("Latest expiration date (YYYY-MM-DD)"),
4138
- contract_type: z5.enum(["call", "put"]).optional().describe("Filter by call or put"),
4139
- limit: z5.number().optional().default(50).describe("Max contracts to return (default 50, use higher for full chain)")
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)")
4140
4424
  });
4141
4425
  async function handleGetOptionSnapshot(params) {
4142
4426
  try {
@@ -4948,8 +5232,8 @@ function analyzeExitTriggers(config) {
4948
5232
  }
4949
5233
 
4950
5234
  // src/tools/exit-analysis.ts
4951
- import { z as z6 } from "zod";
4952
- var triggerTypeEnum = z6.enum([
5235
+ import { z as z7 } from "zod";
5236
+ var triggerTypeEnum = z7.enum([
4953
5237
  "profitTarget",
4954
5238
  "stopLoss",
4955
5239
  "trailingStop",
@@ -4966,70 +5250,70 @@ var triggerTypeEnum = z6.enum([
4966
5250
  "slRatioThreshold",
4967
5251
  "slRatioMove"
4968
5252
  ]);
4969
- var triggerConfigSchema = z6.object({
5253
+ var triggerConfigSchema = z7.object({
4970
5254
  type: triggerTypeEnum,
4971
- threshold: z6.number(),
4972
- unit: z6.enum(["percent", "dollar"]).default("dollar").optional(),
4973
- expiry: z6.string().optional(),
4974
- openDate: z6.string().optional(),
4975
- clockTime: z6.string().optional(),
4976
- trailAmount: z6.number().optional(),
4977
- steps: z6.array(
4978
- z6.object({
4979
- armAt: z6.number(),
4980
- stopAt: z6.number(),
4981
- closeAllocationPct: z6.number().min(0).max(1).optional().describe("Fraction of REMAINING position to close at this milestone (0-1)")
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)")
4982
5266
  })
4983
5267
  ).optional(),
4984
- spreadWidth: z6.number().optional(),
4985
- contracts: z6.number().optional(),
4986
- legIndex: z6.number().optional().describe("0-based leg index for perLegDelta \u2014 targets specific leg"),
4987
- exitAbove: z6.number().optional().describe("Fire when value exceeds this (directional, no abs)"),
4988
- exitBelow: z6.number().optional().describe("Fire when value drops below this (directional, no abs)")
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)")
4989
5273
  });
4990
- var legSchema = z6.object({
4991
- ticker: z6.string(),
4992
- strike: z6.number(),
4993
- type: z6.enum(["C", "P"]),
4994
- expiry: z6.string(),
4995
- quantity: z6.number(),
4996
- 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()
4997
5281
  });
4998
- var analyzeExitTriggersSchema = z6.object({
5282
+ var analyzeExitTriggersSchema = z7.object({
4999
5283
  // Replay inputs (same shape as replay_trade)
5000
- legs: z6.array(legSchema).optional(),
5001
- block_id: z6.string().optional(),
5002
- trade_index: z6.number().optional(),
5003
- open_date: z6.string().optional(),
5004
- close_date: z6.string().optional(),
5005
- multiplier: z6.number().default(100),
5006
- triggers: z6.array(triggerConfigSchema).describe("Exit triggers to evaluate against the P&L path"),
5007
- actual_exit_timestamp: z6.string().optional().describe("Actual exit time for comparison (format: YYYY-MM-DD HH:MM)"),
5008
- leg_groups: z6.array(
5009
- z6.object({
5010
- label: z6.string(),
5011
- leg_indices: z6.array(z6.number()),
5012
- triggers: z6.array(triggerConfigSchema)
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)
5013
5297
  })
5014
5298
  ).optional().describe("Per-leg-group exit triggers for multi-structure strategies"),
5015
- format: z6.enum(["summary", "full"]).default("summary").describe("'summary' omits per-step trigger states, 'full' includes all fire events")
5299
+ format: z7.enum(["summary", "full"]).default("summary").describe("'summary' omits per-step trigger states, 'full' includes all fire events")
5016
5300
  });
5017
- var decomposeGreeksSchema = z6.object({
5301
+ var decomposeGreeksSchema = z7.object({
5018
5302
  // Same replay inputs
5019
- legs: z6.array(legSchema).optional(),
5020
- block_id: z6.string().optional(),
5021
- trade_index: z6.number().optional(),
5022
- open_date: z6.string().optional(),
5023
- close_date: z6.string().optional(),
5024
- multiplier: z6.number().default(100),
5025
- leg_groups: z6.array(
5026
- z6.object({
5027
- label: z6.string(),
5028
- leg_indices: z6.array(z6.number())
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())
5029
5313
  })
5030
5314
  ).optional().describe("Leg grouping for per-group vega attribution (e.g., front_month vs back_month)"),
5031
- format: z6.enum(["summary", "full"]).default("summary").describe("'summary' shows ranked factors, 'full' includes per-step contributions"),
5032
- skip_quotes: z6.boolean().default(false).describe("Skip NBBO quote enrichment for option bars. Faster, but lower precision.")
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.")
5033
5317
  });
5034
5318
  var REVERSE_ROOT_MAP = {
5035
5319
  SPXW: "SPX",
@@ -5455,7 +5739,7 @@ function analyzeBatch(trades, config) {
5455
5739
  }
5456
5740
 
5457
5741
  // src/tools/batch-exit-analysis.ts
5458
- import { z as z7 } from "zod";
5742
+ import { z as z8 } from "zod";
5459
5743
  async function mapWithLimit(items, limit, fn) {
5460
5744
  const results = new Array(items.length);
5461
5745
  let idx = 0;
@@ -5469,7 +5753,7 @@ async function mapWithLimit(items, limit, fn) {
5469
5753
  await Promise.all(workers);
5470
5754
  return results;
5471
5755
  }
5472
- var triggerTypeEnum2 = z7.enum([
5756
+ var triggerTypeEnum2 = z8.enum([
5473
5757
  "profitTarget",
5474
5758
  "stopLoss",
5475
5759
  "trailingStop",
@@ -5486,50 +5770,50 @@ var triggerTypeEnum2 = z7.enum([
5486
5770
  "slRatioThreshold",
5487
5771
  "slRatioMove"
5488
5772
  ]);
5489
- var triggerConfigSchema2 = z7.object({
5773
+ var triggerConfigSchema2 = z8.object({
5490
5774
  type: triggerTypeEnum2,
5491
- threshold: z7.number(),
5492
- unit: z7.enum(["percent", "dollar"]).default("dollar").optional(),
5493
- expiry: z7.string().optional(),
5494
- openDate: z7.string().optional(),
5495
- clockTime: z7.string().optional(),
5496
- trailAmount: z7.number().optional(),
5497
- steps: z7.array(
5498
- z7.object({
5499
- armAt: z7.number(),
5500
- stopAt: z7.number(),
5501
- closeAllocationPct: z7.number().min(0).max(1).optional().describe("Fraction of REMAINING position to close at this milestone (0-1)")
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)")
5502
5786
  })
5503
5787
  ).optional(),
5504
- spreadWidth: z7.number().optional(),
5505
- contracts: z7.number().optional(),
5506
- legIndex: z7.number().optional().describe("0-based leg index for perLegDelta \u2014 targets specific leg"),
5507
- exitAbove: z7.number().optional().describe("Fire when value exceeds this (directional, no abs)"),
5508
- exitBelow: z7.number().optional().describe("Fire when value drops below this (directional, no abs)")
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)")
5509
5793
  });
5510
- var batchExitAnalysisSchema = z7.object({
5511
- block_id: z7.string().describe("Block ID to analyze trades from"),
5512
- strategy: z7.string().optional().describe("Filter trades by strategy name (case-insensitive ILIKE)"),
5513
- date_range: z7.object({
5514
- from: z7.string().optional().describe("Start date YYYY-MM-DD"),
5515
- 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")
5516
5800
  }).optional().describe("Filter trades by date range"),
5517
- candidate_policy: z7.array(triggerConfigSchema2).describe("Candidate exit policy triggers to evaluate -- same schema as analyze_exit_triggers"),
5518
- leg_groups: z7.array(
5519
- z7.object({
5520
- label: z7.string(),
5521
- leg_indices: z7.array(z7.number()),
5522
- triggers: z7.array(triggerConfigSchema2)
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)
5523
5807
  })
5524
5808
  ).optional().describe("Per-leg-group exit triggers for multi-structure strategies"),
5525
- baseline_mode: z7.enum(["actual", "holdToEnd"]).default("actual").describe(
5809
+ baseline_mode: z8.enum(["actual", "holdToEnd"]).default("actual").describe(
5526
5810
  "'actual' compares candidate vs trade's actual P&L; 'holdToEnd' compares vs last replay timestamp"
5527
5811
  ),
5528
- limit: z7.number().min(1).max(200).default(50).describe("Max trades to analyze. Most recent trades selected"),
5529
- min_pl: z7.number().optional().describe("Only include trades with actual P&L >= this value"),
5530
- max_pl: z7.number().optional().describe("Only include trades with actual P&L <= this value"),
5531
- multiplier: z7.number().default(100).describe("Contract multiplier (default 100)"),
5532
- format: z7.enum(["summary", "full"]).default("summary").describe(
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(
5533
5817
  "'summary' returns aggregate stats + trigger attribution; 'full' adds per-trade breakdown"
5534
5818
  )
5535
5819
  });
@@ -5741,7 +6025,7 @@ function expandDateRange(fromDate, toDate) {
5741
6025
 
5742
6026
  // src/tools/sql.ts
5743
6027
  import * as path2 from "path";
5744
- import { z as z8 } from "zod";
6028
+ import { z as z9 } from "zod";
5745
6029
  var BLOCKED_PATTERNS = [
5746
6030
  // External access
5747
6031
  { pattern: /\bCOPY\b/i, operation: "COPY" },
@@ -6005,15 +6289,15 @@ function collectBackfillConcreteFallbacks(input) {
6005
6289
  return fallbacks;
6006
6290
  }
6007
6291
  async function appendBackfillManifestLineDurable(manifestPath, line) {
6008
- const path12 = requireNonEmpty("manifestPath", manifestPath);
6292
+ const path11 = requireNonEmpty("manifestPath", manifestPath);
6009
6293
  const text = String(line);
6010
6294
  if (!text.endsWith("\n")) {
6011
6295
  throw new Error("manifest line must end with a newline");
6012
6296
  }
6013
- const parentDir = dirname(path12);
6297
+ const parentDir = dirname(path11);
6014
6298
  await mkdir(parentDir, { recursive: true });
6015
- const existedBeforeOpen = await pathExists(path12);
6016
- const handle = await open(path12, "a");
6299
+ const existedBeforeOpen = await pathExists(path11);
6300
+ const handle = await open(path11, "a");
6017
6301
  try {
6018
6302
  await handle.writeFile(text);
6019
6303
  await handle.sync();
@@ -6079,9 +6363,9 @@ function validateManifestStatus(value) {
6079
6363
  }
6080
6364
  throw new Error("status must be prepared, committed, failed, or committed_manifest_failed");
6081
6365
  }
6082
- async function pathExists(path12) {
6366
+ async function pathExists(path11) {
6083
6367
  try {
6084
- const handle = await open(path12, "r");
6368
+ const handle = await open(path11, "r");
6085
6369
  await handle.close();
6086
6370
  return true;
6087
6371
  } catch {
@@ -6144,7 +6428,7 @@ function formatIsoDate(date) {
6144
6428
  }
6145
6429
 
6146
6430
  // src/tools/greeks-attribution.ts
6147
- import { z as z9 } from "zod";
6431
+ import { z as z10 } from "zod";
6148
6432
 
6149
6433
  // src/utils/flatfile-importer.ts
6150
6434
  function tradingDays(from, to) {
@@ -6162,17 +6446,17 @@ function tradingDays(from, to) {
6162
6446
  }
6163
6447
 
6164
6448
  // src/tools/greeks-attribution.ts
6165
- var getGreeksAttributionSchema = z9.object({
6166
- block_id: z9.string().describe("Block ID to analyze"),
6167
- mode: z9.enum(["summary", "instance"]).default("summary").describe("summary: block-level attribution. instance: single trade time-series."),
6168
- trade_index: z9.number().int().min(0).optional().describe(
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(
6169
6453
  "Trade index (required for instance mode). Use get_block_info to find trade indices."
6170
6454
  ),
6171
- skip_quotes: z9.boolean().default(true).describe(
6455
+ skip_quotes: z10.boolean().default(true).describe(
6172
6456
  "Use cached bar data only (fast). Set false to fetch NBBO quotes for higher precision."
6173
6457
  ),
6174
- detailed: z9.boolean().default(false).describe("false: 5 factors (delta, gamma, theta, vega, residual). true: adds charm, vanna."),
6175
- strategy: z9.string().optional().describe("Filter to trades matching this strategy name (case-insensitive).")
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).")
6176
6460
  });
6177
6461
  var COLLAPSE_MAP = {
6178
6462
  charm: "delta",
@@ -8137,27 +8421,27 @@ function createMarketStores(ctx) {
8137
8421
  }
8138
8422
 
8139
8423
  // src/market/tickers/schemas.ts
8140
- import { z as z10 } from "zod";
8424
+ import { z as z11 } from "zod";
8141
8425
  var TICKER_RE = /^[A-Z][A-Z0-9^_-]*$/;
8142
- var UnderlyingsFileSchema = z10.object({
8143
- version: z10.literal(1),
8144
- underlyings: z10.array(
8145
- z10.object({
8146
- underlying: z10.string().min(1).max(16).regex(TICKER_RE),
8147
- 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)
8148
8432
  })
8149
8433
  )
8150
8434
  });
8151
- var registerUnderlyingSchema = z10.object({
8152
- underlying: z10.string().min(1).max(16).regex(TICKER_RE).describe("Canonical underlying symbol, e.g. SPX"),
8153
- roots: z10.array(z10.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']")
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']")
8154
8438
  });
8155
- var unregisterUnderlyingSchema = z10.object({
8156
- 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.")
8157
8441
  });
8158
- var listUnderlyingsSchema = z10.object({});
8159
- var resolveRootSchema = z10.object({
8160
- input: z10.string().min(1).max(32).describe("Bare root ('SPXW') or full OCC ticker ('SPXW251219C05000000')")
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')")
8161
8445
  });
8162
8446
 
8163
8447
  // src/market/tickers/registry.ts
@@ -9686,11 +9970,10 @@ function compareRow(oldRow, newRow, kind, ticker, date) {
9686
9970
  }
9687
9971
 
9688
9972
  // src/utils/calibration-probe.ts
9689
- import * as path11 from "path";
9690
9973
  import { DuckDBInstance } from "@duckdb/node-api";
9691
9974
  async function calibrateProviderFetch(ticker, probeDates, dataRoot) {
9692
9975
  const provider = getProvider();
9693
- const dbPath = path11.join(dataRoot, "database", "market.duckdb");
9976
+ const dbPath = resolveDbPath(dataRoot, "market");
9694
9977
  const instance = await DuckDBInstance.create(dbPath);
9695
9978
  const conn = await instance.connect();
9696
9979
  const deltas = [];
@@ -9805,6 +10088,7 @@ export {
9805
10088
  appendBackfillManifestLineDurable,
9806
10089
  applyQuoteGreeks,
9807
10090
  applyQuoteGreeksParallel,
10091
+ armHoldingPeriods,
9808
10092
  assessPrecision,
9809
10093
  bachelierDelta,
9810
10094
  bachelierGamma,
@@ -9821,6 +10105,8 @@ export {
9821
10105
  bsPrice,
9822
10106
  bsTheta,
9823
10107
  bsVega,
10108
+ buildArmDaySeries,
10109
+ buildBlockTradingDayIndex,
9824
10110
  buildEnrichmentPlan,
9825
10111
  buildFilterPredicate,
9826
10112
  buildLookaheadFreeQuery,
@@ -9988,6 +10274,7 @@ export {
9988
10274
  optionHistoryQuote,
9989
10275
  optionHistoryQuoteBand,
9990
10276
  optionListContracts,
10277
+ pairedComparisonInputSchema,
9991
10278
  parseBackfillOccTicker,
9992
10279
  parseCsvToBars,
9993
10280
  parseDatabaseRowsToBars,
@@ -10002,6 +10289,7 @@ export {
10002
10289
  readJsonFile,
10003
10290
  readParquetFilesSql,
10004
10291
  regimeAllocationAdvisorSchema,
10292
+ registerPairedComparisonTool,
10005
10293
  registerTickerTools,
10006
10294
  registerUnderlyingSchema,
10007
10295
  replayTradeSchema,
@@ -10019,6 +10307,7 @@ export {
10019
10307
  rthDailyAggregateSubquery,
10020
10308
  runContextEnrichment,
10021
10309
  runEnrichment,
10310
+ runPairedBootstrapComparison,
10022
10311
  saveUserOverride,
10023
10312
  scoreDataQuality,
10024
10313
  selectVerificationSampleDates,