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
@@ -20,10 +20,7 @@ import {
20
20
  type ExitTriggerConfig,
21
21
  type LegGroupConfig,
22
22
  } from "../utils/exit-triggers.ts";
23
- import {
24
- decomposeGreeks,
25
- type LegGroupDef,
26
- } from "../utils/greeks-decomposition.ts";
23
+ import { decomposeGreeks, type LegGroupDef } from "../utils/greeks-decomposition.ts";
27
24
  import { markPrice } from "../utils/trade-replay.ts";
28
25
 
29
26
  // ---------------------------------------------------------------------------
@@ -31,34 +28,55 @@ import { markPrice } from "../utils/trade-replay.ts";
31
28
  // ---------------------------------------------------------------------------
32
29
 
33
30
  const triggerTypeEnum = z.enum([
34
- 'profitTarget', 'stopLoss', 'trailingStop', 'profitAction',
35
- 'dteExit', 'ditExit', 'clockTimeExit',
36
- 'underlyingPriceMove', 'positionDelta', 'perLegDelta',
37
- 'vixMove', 'vix9dMove', 'vix9dVixRatio',
38
- 'slRatioThreshold', 'slRatioMove',
31
+ "profitTarget",
32
+ "stopLoss",
33
+ "trailingStop",
34
+ "profitAction",
35
+ "dteExit",
36
+ "ditExit",
37
+ "clockTimeExit",
38
+ "underlyingPriceMove",
39
+ "positionDelta",
40
+ "perLegDelta",
41
+ "vixMove",
42
+ "vix9dMove",
43
+ "vix9dVixRatio",
44
+ "slRatioThreshold",
45
+ "slRatioMove",
39
46
  ]);
40
47
 
41
48
  const triggerConfigSchema = z.object({
42
49
  type: triggerTypeEnum,
43
50
  threshold: z.number(),
44
- unit: z.enum(['percent', 'dollar']).default('dollar').optional(),
51
+ unit: z.enum(["percent", "dollar"]).default("dollar").optional(),
45
52
  expiry: z.string().optional(),
46
53
  openDate: z.string().optional(),
47
54
  clockTime: z.string().optional(),
48
55
  trailAmount: z.number().optional(),
49
- steps: z.array(z.object({
50
- armAt: z.number(),
51
- stopAt: z.number(),
52
- closeAllocationPct: z.number().min(0).max(1).optional()
53
- .describe("Fraction of REMAINING position to close at this milestone (0-1)"),
54
- })).optional(),
56
+ steps: z
57
+ .array(
58
+ z.object({
59
+ armAt: z.number(),
60
+ stopAt: z.number(),
61
+ closeAllocationPct: z
62
+ .number()
63
+ .min(0)
64
+ .max(1)
65
+ .optional()
66
+ .describe("Fraction of REMAINING position to close at this milestone (0-1)"),
67
+ }),
68
+ )
69
+ .optional(),
55
70
  spreadWidth: z.number().optional(),
56
71
  contracts: z.number().optional(),
57
- legIndex: z.number().optional()
72
+ legIndex: z
73
+ .number()
74
+ .optional()
58
75
  .describe("0-based leg index for perLegDelta — targets specific leg"),
59
- exitAbove: z.number().optional()
60
- .describe("Fire when value exceeds this (directional, no abs)"),
61
- exitBelow: z.number().optional()
76
+ exitAbove: z.number().optional().describe("Fire when value exceeds this (directional, no abs)"),
77
+ exitBelow: z
78
+ .number()
79
+ .optional()
62
80
  .describe("Fire when value drops below this (directional, no abs)"),
63
81
  });
64
82
 
@@ -88,19 +106,27 @@ export const analyzeExitTriggersSchema = z.object({
88
106
  close_date: z.string().optional(),
89
107
  multiplier: z.number().default(100),
90
108
 
91
- triggers: z.array(triggerConfigSchema)
92
- .describe("Exit triggers to evaluate against the P&L path"),
109
+ triggers: z.array(triggerConfigSchema).describe("Exit triggers to evaluate against the P&L path"),
93
110
 
94
- actual_exit_timestamp: z.string().optional()
111
+ actual_exit_timestamp: z
112
+ .string()
113
+ .optional()
95
114
  .describe("Actual exit time for comparison (format: YYYY-MM-DD HH:MM)"),
96
115
 
97
- leg_groups: z.array(z.object({
98
- label: z.string(),
99
- leg_indices: z.array(z.number()),
100
- triggers: z.array(triggerConfigSchema),
101
- })).optional().describe("Per-leg-group exit triggers for multi-structure strategies"),
102
-
103
- format: z.enum(["summary", "full"]).default("summary")
116
+ leg_groups: z
117
+ .array(
118
+ z.object({
119
+ label: z.string(),
120
+ leg_indices: z.array(z.number()),
121
+ triggers: z.array(triggerConfigSchema),
122
+ }),
123
+ )
124
+ .optional()
125
+ .describe("Per-leg-group exit triggers for multi-structure strategies"),
126
+
127
+ format: z
128
+ .enum(["summary", "full"])
129
+ .default("summary")
104
130
  .describe("'summary' omits per-step trigger states, 'full' includes all fire events"),
105
131
  });
106
132
 
@@ -117,12 +143,19 @@ export const decomposeGreeksSchema = z.object({
117
143
  close_date: z.string().optional(),
118
144
  multiplier: z.number().default(100),
119
145
 
120
- leg_groups: z.array(z.object({
121
- label: z.string(),
122
- leg_indices: z.array(z.number()),
123
- })).optional().describe("Leg grouping for per-group vega attribution (e.g., front_month vs back_month)"),
124
-
125
- format: z.enum(["summary", "full"]).default("summary")
146
+ leg_groups: z
147
+ .array(
148
+ z.object({
149
+ label: z.string(),
150
+ leg_indices: z.array(z.number()),
151
+ }),
152
+ )
153
+ .optional()
154
+ .describe("Leg grouping for per-group vega attribution (e.g., front_month vs back_month)"),
155
+
156
+ format: z
157
+ .enum(["summary", "full"])
158
+ .default("summary")
126
159
  .describe("'summary' shows ranked factors, 'full' includes per-step contributions"),
127
160
  skip_quotes: z
128
161
  .boolean()
@@ -136,7 +169,9 @@ export const decomposeGreeksSchema = z.object({
136
169
 
137
170
  // Reverse-map weekly roots to standard root for underlying/VIX fetching
138
171
  const REVERSE_ROOT_MAP: Record<string, string> = {
139
- SPXW: 'SPX', NDXP: 'NDX', RUTW: 'RUT',
172
+ SPXW: "SPX",
173
+ NDXP: "NDX",
174
+ RUTW: "RUT",
140
175
  };
141
176
 
142
177
  /**
@@ -145,7 +180,7 @@ const REVERSE_ROOT_MAP: Record<string, string> = {
145
180
  */
146
181
  function extractUnderlyingTicker(occTicker: string): string {
147
182
  const rootMatch = occTicker.match(/^([A-Z]+)/);
148
- const rawRoot = rootMatch ? rootMatch[1] : '';
183
+ const rawRoot = rootMatch ? rootMatch[1] : "";
149
184
  return REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;
150
185
  }
151
186
 
@@ -182,12 +217,17 @@ async function fetchPriceMap(
182
217
  // applied at the underlying-consumer site.
183
218
  for (const b of bars) {
184
219
  if (
185
- !Number.isFinite(b.open) || b.open <= 0 ||
186
- !Number.isFinite(b.high) || b.high <= 0 ||
187
- !Number.isFinite(b.low) || b.low <= 0 ||
188
- !Number.isFinite(b.close) || b.close <= 0
189
- ) continue;
190
- const ts = `${b.date} ${b.time ?? ''}`.trim();
220
+ !Number.isFinite(b.open) ||
221
+ b.open <= 0 ||
222
+ !Number.isFinite(b.high) ||
223
+ b.high <= 0 ||
224
+ !Number.isFinite(b.low) ||
225
+ b.low <= 0 ||
226
+ !Number.isFinite(b.close) ||
227
+ b.close <= 0
228
+ )
229
+ continue;
230
+ const ts = `${b.date} ${b.time ?? ""}`.trim();
191
231
  map.set(ts, markPrice(b));
192
232
  }
193
233
  } catch {
@@ -207,9 +247,15 @@ export async function handleAnalyzeExitTriggers(
207
247
  injectedConn?: import("@duckdb/node-api").DuckDBConnection,
208
248
  ): Promise<ReturnType<typeof analyzeExitTriggers>> {
209
249
  const {
210
- legs: inputLegs, block_id, trade_index,
211
- open_date, close_date, multiplier,
212
- triggers, actual_exit_timestamp, leg_groups,
250
+ legs: inputLegs,
251
+ block_id,
252
+ trade_index,
253
+ open_date,
254
+ close_date,
255
+ multiplier,
256
+ triggers,
257
+ actual_exit_timestamp,
258
+ leg_groups,
213
259
  } = params;
214
260
 
215
261
  // 1. Run replay to get full P&L path with greeks
@@ -221,8 +267,8 @@ export async function handleAnalyzeExitTriggers(
221
267
  open_date,
222
268
  close_date,
223
269
  multiplier,
224
- format: 'full',
225
- close_at: 'trade',
270
+ format: "full",
271
+ close_at: "trade",
226
272
  skip_quotes: false,
227
273
  },
228
274
  baseDir,
@@ -243,7 +289,7 @@ export async function handleAnalyzeExitTriggers(
243
289
  overall: {
244
290
  triggers: [],
245
291
  firstToFire: null,
246
- summary: 'No P&L data available from replay.',
292
+ summary: "No P&L data available from replay.",
247
293
  },
248
294
  };
249
295
  }
@@ -253,9 +299,9 @@ export async function handleAnalyzeExitTriggers(
253
299
  const lastDate = pnlPath[pnlPath.length - 1].timestamp.slice(0, 10);
254
300
 
255
301
  // 3. Check which external data maps are needed
256
- const allTriggerTypes = new Set(triggers.map(t => t.type));
302
+ const allTriggerTypes = new Set(triggers.map((t) => t.type));
257
303
  const groupTriggerTypes = new Set(
258
- (leg_groups ?? []).flatMap(g => g.triggers.map(t => t.type))
304
+ (leg_groups ?? []).flatMap((g) => g.triggers.map((t) => t.type)),
259
305
  );
260
306
  for (const t of groupTriggerTypes) allTriggerTypes.add(t);
261
307
 
@@ -267,24 +313,22 @@ export async function handleAnalyzeExitTriggers(
267
313
  let vix9dPrices: Map<string, number> | undefined;
268
314
  let underlyingPrices: Map<string, number> | undefined;
269
315
 
270
- const needsVix = allTriggerTypes.has('vixMove') || allTriggerTypes.has('vix9dVixRatio');
271
- const needsVix9d = allTriggerTypes.has('vix9dMove') || allTriggerTypes.has('vix9dVixRatio');
272
- const needsUnderlying = allTriggerTypes.has('underlyingPriceMove');
316
+ const needsVix = allTriggerTypes.has("vixMove") || allTriggerTypes.has("vix9dVixRatio");
317
+ const needsVix9d = allTriggerTypes.has("vix9dMove") || allTriggerTypes.has("vix9dVixRatio");
318
+ const needsUnderlying = allTriggerTypes.has("underlyingPriceMove");
273
319
 
274
320
  if (needsVix) {
275
- vixPrices = await fetchPriceMap(stores, 'VIX', firstDate, lastDate);
321
+ vixPrices = await fetchPriceMap(stores, "VIX", firstDate, lastDate);
276
322
  }
277
323
  if (needsVix9d) {
278
- vix9dPrices = await fetchPriceMap(stores, 'VIX9D', firstDate, lastDate);
324
+ vix9dPrices = await fetchPriceMap(stores, "VIX9D", firstDate, lastDate);
279
325
  }
280
326
  if (needsUnderlying) {
281
- underlyingPrices = await fetchPriceMap(
282
- stores, underlyingTicker, firstDate, lastDate,
283
- );
327
+ underlyingPrices = await fetchPriceMap(stores, underlyingTicker, firstDate, lastDate);
284
328
  }
285
329
 
286
330
  // 4. Map tool trigger params to ExitTriggerConfig[] with data maps
287
- const exitTriggers: ExitTriggerConfig[] = triggers.map(t => ({
331
+ const exitTriggers: ExitTriggerConfig[] = triggers.map((t) => ({
288
332
  type: t.type,
289
333
  threshold: t.threshold,
290
334
  unit: t.unit,
@@ -303,10 +347,10 @@ export async function handleAnalyzeExitTriggers(
303
347
  }));
304
348
 
305
349
  // 5. Map leg groups with their triggers
306
- const legGroupConfigs: LegGroupConfig[] | undefined = leg_groups?.map(g => ({
350
+ const legGroupConfigs: LegGroupConfig[] | undefined = leg_groups?.map((g) => ({
307
351
  label: g.label,
308
352
  legIndices: g.leg_indices,
309
- triggers: g.triggers.map(t => ({
353
+ triggers: g.triggers.map((t) => ({
310
354
  type: t.type,
311
355
  threshold: t.threshold,
312
356
  unit: t.unit,
@@ -346,9 +390,15 @@ export async function handleDecomposeGreeks(
346
390
  injectedConn?: import("@duckdb/node-api").DuckDBConnection,
347
391
  ): Promise<import("../utils/greeks-decomposition.ts").GreeksDecompositionResult> {
348
392
  const {
349
- legs: inputLegs, block_id, trade_index,
350
- open_date, close_date, multiplier,
351
- leg_groups, format, skip_quotes,
393
+ legs: inputLegs,
394
+ block_id,
395
+ trade_index,
396
+ open_date,
397
+ close_date,
398
+ multiplier,
399
+ leg_groups,
400
+ format,
401
+ skip_quotes,
352
402
  } = params;
353
403
 
354
404
  // 1. Run replay to get full P&L path with greeks
@@ -360,8 +410,8 @@ export async function handleDecomposeGreeks(
360
410
  open_date,
361
411
  close_date,
362
412
  multiplier,
363
- format: 'full',
364
- close_at: 'trade',
413
+ format: "full",
414
+ close_at: "trade",
365
415
  skip_quotes,
366
416
  },
367
417
  baseDir,
@@ -375,7 +425,7 @@ export async function handleDecomposeGreeks(
375
425
  // 2. Check greeks data availability
376
426
  if (pnlPath.length > 0 && !pnlPath[0].legGreeks) {
377
427
  throw new Error(
378
- "No greeks data available. Use the data-pipeline tools to backfill the option-leg quotes and underlying price data into the local cache."
428
+ "No greeks data available. Use the data-pipeline tools to backfill the option-leg quotes and underlying price data into the local cache.",
379
429
  );
380
430
  }
381
431
 
@@ -388,25 +438,28 @@ export async function handleDecomposeGreeks(
388
438
  }
389
439
 
390
440
  // 4. Map leg groups
391
- const legGroupDefs: LegGroupDef[] | undefined = leg_groups?.map(g => ({
441
+ const legGroupDefs: LegGroupDef[] | undefined = leg_groups?.map((g) => ({
392
442
  label: g.label,
393
443
  legIndices: g.leg_indices,
394
444
  }));
395
445
 
396
446
  // 5. Build leg pricing inputs from OCC tickers for full revaluation
397
447
  const DIVIDEND_YIELDS: Record<string, number> = {
398
- SPX: 0.015, SPXW: 0.015, NDX: 0.015, NDXP: 0.015,
448
+ SPX: 0.015,
449
+ SPXW: 0.015,
450
+ NDX: 0.015,
451
+ NDXP: 0.015,
399
452
  };
400
453
  const rootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);
401
- const rawRoot = rootMatch ? rootMatch[1] : '';
454
+ const rawRoot = rootMatch ? rootMatch[1] : "";
402
455
  const divYield = DIVIDEND_YIELDS[rawRoot] ?? 0;
403
456
 
404
- const legPricingInputs = replayLegs.map(leg => {
457
+ const legPricingInputs = replayLegs.map((leg) => {
405
458
  const m = leg.occTicker.match(/^[A-Z]+(\d{6})([CP])(\d{8})$/);
406
- if (!m) return { strike: 0, type: 'C' as const, expiryDate: '' };
459
+ if (!m) return { strike: 0, type: "C" as const, expiryDate: "" };
407
460
  return {
408
461
  strike: parseInt(m[3], 10) / 1000,
409
- type: m[2] as 'C' | 'P',
462
+ type: m[2] as "C" | "P",
410
463
  expiryDate: `20${m[1].slice(0, 2)}-${m[1].slice(2, 4)}-${m[1].slice(4, 6)}`,
411
464
  };
412
465
  });
@@ -475,7 +528,7 @@ export function registerExitAnalysisTools(
475
528
  isError: true,
476
529
  };
477
530
  }
478
- }
531
+ },
479
532
  );
480
533
 
481
534
  server.registerTool(
@@ -507,6 +560,6 @@ export function registerExitAnalysisTools(
507
560
  isError: true,
508
561
  };
509
562
  }
510
- }
563
+ },
511
564
  );
512
565
  }
@@ -81,11 +81,15 @@ export const getGreeksAttributionSchema = z.object({
81
81
  .int()
82
82
  .min(0)
83
83
  .optional()
84
- .describe("Trade index (required for instance mode). Use get_block_info to find trade indices."),
84
+ .describe(
85
+ "Trade index (required for instance mode). Use get_block_info to find trade indices.",
86
+ ),
85
87
  skip_quotes: z
86
88
  .boolean()
87
89
  .default(true)
88
- .describe("Use cached bar data only (fast). Set false to fetch NBBO quotes for higher precision."),
90
+ .describe(
91
+ "Use cached bar data only (fast). Set false to fetch NBBO quotes for higher precision.",
92
+ ),
89
93
  detailed: z
90
94
  .boolean()
91
95
  .default(false)
@@ -105,7 +109,16 @@ const COLLAPSE_MAP: Record<string, string> = {
105
109
  vanna: "vega",
106
110
  };
107
111
 
108
- const FACTOR_ORDER: string[] = ["theta", "vega", "delta", "gamma", "residual", "time_and_vol", "charm", "vanna"];
112
+ const FACTOR_ORDER: string[] = [
113
+ "theta",
114
+ "vega",
115
+ "delta",
116
+ "gamma",
117
+ "residual",
118
+ "time_and_vol",
119
+ "charm",
120
+ "vanna",
121
+ ];
109
122
 
110
123
  export function collapseFactors(
111
124
  factors: FactorContribution[],
@@ -182,10 +195,26 @@ export async function handleGetGreeksAttribution(
182
195
  if (trade_index == null) {
183
196
  throw new Error("trade_index is required for instance mode");
184
197
  }
185
- return handleInstanceMode(block_id, trade_index, skip_quotes, detailed, baseDir, stores, injectedConn);
198
+ return handleInstanceMode(
199
+ block_id,
200
+ trade_index,
201
+ skip_quotes,
202
+ detailed,
203
+ baseDir,
204
+ stores,
205
+ injectedConn,
206
+ );
186
207
  }
187
208
 
188
- return handleSummaryMode(block_id, skip_quotes, detailed, strategy, baseDir, stores, injectedConn);
209
+ return handleSummaryMode(
210
+ block_id,
211
+ skip_quotes,
212
+ detailed,
213
+ strategy,
214
+ baseDir,
215
+ stores,
216
+ injectedConn,
217
+ );
189
218
  }
190
219
 
191
220
  async function handleSummaryMode(
@@ -197,7 +226,7 @@ async function handleSummaryMode(
197
226
  stores: MarketStores,
198
227
  injectedConn?: import("@duckdb/node-api").DuckDBConnection,
199
228
  ): Promise<AttributionSummaryResult> {
200
- const conn = injectedConn ?? await getConnection(baseDir);
229
+ const conn = injectedConn ?? (await getConnection(baseDir));
201
230
 
202
231
  const selectedTradesQuery = strategy
203
232
  ? `SELECT trade_index, pl
@@ -224,7 +253,7 @@ async function handleSummaryMode(
224
253
  throw new Error(
225
254
  strategy
226
255
  ? `No trades found for block "${block_id}" with strategy "${strategy}"`
227
- : `No trades found for block "${block_id}"`
256
+ : `No trades found for block "${block_id}"`,
228
257
  );
229
258
  }
230
259
 
@@ -254,16 +283,21 @@ async function handleSummaryMode(
254
283
  baseDir,
255
284
  stores,
256
285
  injectedConn,
257
- ).then(result => {
258
- for (const factor of result.factors) {
259
- accumulated.set(factor.factor, (accumulated.get(factor.factor) ?? 0) + factor.totalPnl);
260
- }
261
- actualTotalPnl += trade.actualPl;
262
- markTotalPnl += result.totalPnlChange;
263
- decomposed++;
264
- }).catch(() => {
265
- skipped++;
266
- })
286
+ )
287
+ .then((result) => {
288
+ for (const factor of result.factors) {
289
+ accumulated.set(
290
+ factor.factor,
291
+ (accumulated.get(factor.factor) ?? 0) + factor.totalPnl,
292
+ );
293
+ }
294
+ actualTotalPnl += trade.actualPl;
295
+ markTotalPnl += result.totalPnlChange;
296
+ decomposed++;
297
+ })
298
+ .catch(() => {
299
+ skipped++;
300
+ }),
267
301
  );
268
302
  }
269
303
  await Promise.allSettled(promises);
@@ -306,16 +340,15 @@ async function handleSummaryMode(
306
340
  // low-quality market data rather than genuine fill advantage.
307
341
  const hints: string[] = [];
308
342
  if (hint) hints.push(hint);
309
- const edgeRatio = Math.abs(actualTotalPnl) > 0.01
310
- ? Math.abs(executionEdge) / Math.abs(actualTotalPnl)
311
- : 0;
343
+ const edgeRatio =
344
+ Math.abs(actualTotalPnl) > 0.01 ? Math.abs(executionEdge) / Math.abs(actualTotalPnl) : 0;
312
345
  if (edgeRatio > 3) {
313
346
  hints.push(
314
347
  `Execution edge is ${Math.round(edgeRatio)}x the actual P&L — ` +
315
- `mark-to-market pricing may be based on sparse or low-quality data. ` +
316
- (skip_quotes
317
- ? `Re-run with skip_quotes=false for NBBO-based marks.`
318
- : `Consider whether intraday bar coverage is sufficient for this date range.`)
348
+ `mark-to-market pricing may be based on sparse or low-quality data. ` +
349
+ (skip_quotes
350
+ ? `Re-run with skip_quotes=false for NBBO-based marks.`
351
+ : `Consider whether intraday bar coverage is sufficient for this date range.`),
319
352
  );
320
353
  }
321
354
 
@@ -330,7 +363,7 @@ async function handleSummaryMode(
330
363
  gross_attribution_flow: Math.round(grossAttributionFlow * 100) / 100,
331
364
  attribution,
332
365
  precision,
333
- ...(hints.length > 0 ? { hint: hints.join(' ') } : {}),
366
+ ...(hints.length > 0 ? { hint: hints.join(" ") } : {}),
334
367
  };
335
368
  }
336
369
 
@@ -343,7 +376,7 @@ async function handleInstanceMode(
343
376
  stores: MarketStores,
344
377
  injectedConn?: import("@duckdb/node-api").DuckDBConnection,
345
378
  ): Promise<AttributionInstanceResult> {
346
- const conn = injectedConn ?? await getConnection(baseDir);
379
+ const conn = injectedConn ?? (await getConnection(baseDir));
347
380
 
348
381
  // Get trade date for the response
349
382
  const tradeResult = await conn.runAndReadAll(
@@ -351,7 +384,7 @@ async function handleInstanceMode(
351
384
  WHERE block_id = $1
352
385
  ORDER BY date_opened, rowid
353
386
  LIMIT 1 OFFSET $2`,
354
- [block_id, trade_index]
387
+ [block_id, trade_index],
355
388
  );
356
389
  const tradeRows = tradeResult.getRows();
357
390
  if (tradeRows.length === 0) {
@@ -398,10 +431,20 @@ async function handleInstanceMode(
398
431
  for (let i = 0; i <= stepCount; i++) {
399
432
  const entry: AttributionStepEntry = {
400
433
  date: getStepDate(i),
401
- delta: getStepValue(factorSteps, "delta", i, detailed ? 0 : (factorSteps.get("charm")?.[i] ?? 0)),
434
+ delta: getStepValue(
435
+ factorSteps,
436
+ "delta",
437
+ i,
438
+ detailed ? 0 : (factorSteps.get("charm")?.[i] ?? 0),
439
+ ),
402
440
  gamma: getStepValue(factorSteps, "gamma", i, 0),
403
441
  theta: getStepValue(factorSteps, "theta", i, 0),
404
- vega: getStepValue(factorSteps, "vega", i, detailed ? 0 : (factorSteps.get("vanna")?.[i] ?? 0)),
442
+ vega: getStepValue(
443
+ factorSteps,
444
+ "vega",
445
+ i,
446
+ detailed ? 0 : (factorSteps.get("vanna")?.[i] ?? 0),
447
+ ),
405
448
  residual: getStepValue(factorSteps, "residual", i, 0),
406
449
  };
407
450
  // time_and_vol: present when numerical fallback was used (theta/vega couldn't be separated)
@@ -441,10 +484,16 @@ async function handleInstanceMode(
441
484
  * Keeps the output compact and useful.
442
485
  */
443
486
  export function filterSparseSteps(steps: AttributionStepEntry[]): AttributionStepEntry[] {
444
- return steps.filter(s =>
445
- s.delta !== 0 || s.gamma !== 0 || s.theta !== 0 || s.vega !== 0 ||
446
- s.residual !== 0 || (s.time_and_vol ?? 0) !== 0 ||
447
- (s.charm ?? 0) !== 0 || (s.vanna ?? 0) !== 0
487
+ return steps.filter(
488
+ (s) =>
489
+ s.delta !== 0 ||
490
+ s.gamma !== 0 ||
491
+ s.theta !== 0 ||
492
+ s.vega !== 0 ||
493
+ s.residual !== 0 ||
494
+ (s.time_and_vol ?? 0) !== 0 ||
495
+ (s.charm ?? 0) !== 0 ||
496
+ (s.vanna ?? 0) !== 0,
448
497
  );
449
498
  }
450
499
 
@@ -482,8 +531,8 @@ export function registerGreeksAttributionTools(
482
531
 
483
532
  const isSummary = !("steps" in result);
484
533
  const summary = isSummary
485
- ? `Block "${params.block_id}" attribution (${(result as AttributionSummaryResult).trades_decomposed}/${(result as AttributionSummaryResult).trades_total} trades): ${(result as AttributionSummaryResult).attribution.map(a => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(", ")}, actual P&L ${(result as AttributionSummaryResult).total_pnl}, execution edge ${(result as AttributionSummaryResult).execution_edge}`
486
- : `Trade #${(result as AttributionInstanceResult).trade_index} attribution: ${(result as AttributionInstanceResult).attribution.map(a => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(", ")}, actual P&L ${(result as AttributionInstanceResult).total_pnl}, execution edge ${(result as AttributionInstanceResult).execution_edge}`;
534
+ ? `Block "${params.block_id}" attribution (${(result as AttributionSummaryResult).trades_decomposed}/${(result as AttributionSummaryResult).trades_total} trades): ${(result as AttributionSummaryResult).attribution.map((a) => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(", ")}, actual P&L ${(result as AttributionSummaryResult).total_pnl}, execution edge ${(result as AttributionSummaryResult).execution_edge}`
535
+ : `Trade #${(result as AttributionInstanceResult).trade_index} attribution: ${(result as AttributionInstanceResult).attribution.map((a) => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(", ")}, actual P&L ${(result as AttributionInstanceResult).total_pnl}, execution edge ${(result as AttributionInstanceResult).execution_edge}`;
487
536
 
488
537
  return createToolOutput(summary, result);
489
538
  } catch (error) {
@@ -348,7 +348,7 @@ export function registerGuideTools(server: McpServer): void {
348
348
  .string()
349
349
  .optional()
350
350
  .describe(
351
- "Optional keyword to narrow results within the topic (e.g., 'trailing' within exit_conditions)"
351
+ "Optional keyword to narrow results within the topic (e.g., 'trailing' within exit_conditions)",
352
352
  ),
353
353
  }),
354
354
  },
@@ -367,9 +367,7 @@ export function registerGuideTools(server: McpServer): void {
367
367
  if (subtopic) {
368
368
  const keyword = subtopic.toLowerCase();
369
369
  const sections = content.split(/\n(?=## )/);
370
- const matched = sections.filter((s) =>
371
- s.toLowerCase().includes(keyword)
372
- );
370
+ const matched = sections.filter((s) => s.toLowerCase().includes(keyword));
373
371
  if (matched.length > 0) {
374
372
  content = matched.join("\n\n");
375
373
  }
@@ -380,8 +378,8 @@ export function registerGuideTools(server: McpServer): void {
380
378
 
381
379
  return createToolOutput(
382
380
  `${entry.title} — use other topics for more detail: ${allTopics.filter((t) => t !== topic).join(", ")}`,
383
- { topic, title: entry.title, content, availableTopics: allTopics }
381
+ { topic, title: entry.title, content, availableTopics: allTopics },
384
382
  );
385
- }
383
+ },
386
384
  );
387
385
  }