tradeblocks-mcp 3.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +82 -68
  2. package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
  3. package/dist/chunk-27S67XW3.js.map +1 -0
  4. package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
  5. package/dist/chunk-2E63THNI.js.map +1 -0
  6. package/dist/{chunk-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
  7. package/dist/chunk-6S37CXUA.js.map +1 -0
  8. package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
  9. package/dist/chunk-AP7IHUUR.js.map +1 -0
  10. package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
  11. package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
  12. package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
  13. package/dist/chunk-SEUZYQGQ.js.map +1 -0
  14. package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
  15. package/dist/chunk-UBUC5A66.js.map +1 -0
  16. package/dist/{chunk-XXYOUIZY.js → chunk-VDU25Z6X.js} +63 -80
  17. package/dist/chunk-VDU25Z6X.js.map +1 -0
  18. package/dist/daily-log-processor-BY3ISY6K.js +9 -0
  19. package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
  20. package/dist/iv-solver-worker.js +9 -1
  21. package/dist/market-provider-H3ARIVZ4.js +16 -0
  22. package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
  23. package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
  24. package/dist/test-exports.js +358 -254
  25. package/dist/test-exports.js.map +1 -1
  26. package/dist/trade-processor-EYA3I3XB.js +9 -0
  27. package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
  28. package/manifest.json +3 -11
  29. package/package.json +1 -1
  30. package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
  31. package/server/chunk-4P7D7YZP.js.map +1 -0
  32. package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
  33. package/server/chunk-BOPHW5M6.js.map +1 -0
  34. package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
  35. package/server/chunk-GH2552SE.js.map +1 -0
  36. package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
  37. package/server/chunk-OBYKFW2B.js.map +1 -0
  38. package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
  39. package/server/chunk-T66KH2XH.js.map +1 -0
  40. package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
  41. package/server/chunk-W5E7FHC4.js.map +1 -0
  42. package/server/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
  43. package/server/chunk-YUCOAJ4Z.js.map +1 -0
  44. package/server/{chunk-PNKG7RY7.js → chunk-ZBJCF4ZG.js} +63 -80
  45. package/server/chunk-ZBJCF4ZG.js.map +1 -0
  46. package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
  47. package/server/config-DK7KOMNL.js.map +1 -0
  48. package/server/daily-log-processor-ENEUT22A.js +10 -0
  49. package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
  50. package/server/http-server.js +2 -5
  51. package/server/index.js +1771 -1538
  52. package/server/index.js.map +1 -1
  53. package/server/iv-solver-worker.js +9 -1
  54. package/server/market-provider-B437HKLW.js +17 -0
  55. package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
  56. package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
  57. package/server/trade-processor-L3PIQ5TG.js +10 -0
  58. package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
  59. package/src/auth/clients-store.ts +4 -4
  60. package/src/auth/code-store.ts +1 -1
  61. package/src/auth/config.ts +11 -12
  62. package/src/auth/login-page.ts +8 -10
  63. package/src/auth/provider.ts +35 -30
  64. package/src/auth/token.ts +17 -15
  65. package/src/db/backtest-schemas.ts +12 -4
  66. package/src/db/connection.ts +107 -40
  67. package/src/db/index.ts +37 -5
  68. package/src/db/json-adapters.ts +22 -31
  69. package/src/db/json-migration.ts +11 -15
  70. package/src/db/json-store.ts +5 -3
  71. package/src/db/market-datasets.ts +48 -15
  72. package/src/db/market-schemas.ts +11 -5
  73. package/src/db/market-views.ts +119 -32
  74. package/src/db/parquet-writer.ts +7 -7
  75. package/src/db/profile-schemas.ts +39 -22
  76. package/src/db/schemas.ts +2 -2
  77. package/src/http-server.ts +7 -12
  78. package/src/index.ts +8 -9
  79. package/src/market/ingestor/index.ts +1 -4
  80. package/src/market/ingestor/market-ingestor.ts +140 -67
  81. package/src/market/ingestor/types.ts +1 -3
  82. package/src/market/stores/chain-sql.ts +2 -8
  83. package/src/market/stores/chain-store.ts +1 -4
  84. package/src/market/stores/duckdb-chain-store.ts +3 -14
  85. package/src/market/stores/duckdb-enriched-store.ts +3 -13
  86. package/src/market/stores/duckdb-quote-store.ts +10 -27
  87. package/src/market/stores/duckdb-spot-store.ts +4 -20
  88. package/src/market/stores/enriched-sql.ts +3 -9
  89. package/src/market/stores/enriched-store.ts +2 -2
  90. package/src/market/stores/parquet-chain-store.ts +5 -22
  91. package/src/market/stores/parquet-enriched-store.ts +4 -16
  92. package/src/market/stores/parquet-oi-daily-store.ts +3 -14
  93. package/src/market/stores/parquet-quote-store.ts +9 -29
  94. package/src/market/stores/parquet-spot-store.ts +14 -33
  95. package/src/market/stores/quote-store.ts +2 -10
  96. package/src/market/stores/spot-sql.ts +3 -15
  97. package/src/market/stores/spot-store.ts +1 -1
  98. package/src/market/tickers/defaults.json +6 -6
  99. package/src/market/tickers/loader.ts +2 -7
  100. package/src/market/tickers/registry.ts +5 -15
  101. package/src/market/tickers/schemas.ts +2 -6
  102. package/src/models/strategy-profile.ts +39 -39
  103. package/src/sync/block-sync.ts +44 -65
  104. package/src/sync/index.ts +1 -4
  105. package/src/sync/metadata.ts +14 -20
  106. package/src/test-exports.ts +127 -123
  107. package/src/tools/analysis.ts +180 -159
  108. package/src/tools/batch-exit-analysis.ts +100 -59
  109. package/src/tools/blocks/analysis.ts +116 -101
  110. package/src/tools/blocks/comparison.ts +281 -364
  111. package/src/tools/blocks/core.ts +208 -253
  112. package/src/tools/blocks/health.ts +571 -563
  113. package/src/tools/blocks/similarity.ts +316 -283
  114. package/src/tools/edge-decay.ts +124 -197
  115. package/src/tools/exit-analysis.ts +130 -77
  116. package/src/tools/greeks-attribution.ts +84 -35
  117. package/src/tools/guides.ts +4 -6
  118. package/src/tools/imports.ts +9 -12
  119. package/src/tools/market-data.ts +1067 -757
  120. package/src/tools/market-enrichment.ts +3 -3
  121. package/src/tools/market-fetch.ts +148 -67
  122. package/src/tools/market-imports.ts +12 -12
  123. package/src/tools/middleware/sync-middleware.ts +5 -6
  124. package/src/tools/performance.ts +185 -302
  125. package/src/tools/profile-analysis.ts +52 -66
  126. package/src/tools/profiles.ts +106 -69
  127. package/src/tools/regime-advisor.ts +20 -45
  128. package/src/tools/replay.ts +81 -77
  129. package/src/tools/reports/discrepancies.ts +298 -328
  130. package/src/tools/reports/fields.ts +7 -25
  131. package/src/tools/reports/helpers.ts +18 -49
  132. package/src/tools/reports/predictive.ts +27 -70
  133. package/src/tools/reports/slippage-trends.ts +315 -345
  134. package/src/tools/reports/slippage.ts +1 -4
  135. package/src/tools/reports/strategy-matches.ts +399 -441
  136. package/src/tools/schema.ts +43 -40
  137. package/src/tools/shared/filters.ts +3 -9
  138. package/src/tools/snapshot.ts +9 -30
  139. package/src/tools/sql.ts +15 -14
  140. package/src/tools/tickers.ts +1 -4
  141. package/src/utils/batch-exit-analysis.ts +31 -42
  142. package/src/utils/black-scholes.ts +39 -29
  143. package/src/utils/block-loader.ts +68 -82
  144. package/src/utils/calibration-probe.ts +1 -2
  145. package/src/utils/chain-loader.ts +3 -3
  146. package/src/utils/csv-discovery.ts +16 -22
  147. package/src/utils/data-quality.ts +24 -36
  148. package/src/utils/exit-triggers.ts +91 -96
  149. package/src/utils/field-timing.ts +94 -79
  150. package/src/utils/filter-predicates.ts +13 -9
  151. package/src/utils/flatfile-importer.ts +94 -64
  152. package/src/utils/greeks-decomposition.ts +152 -100
  153. package/src/utils/iv-solver-pool.ts +55 -25
  154. package/src/utils/iv-solver-worker.ts +5 -5
  155. package/src/utils/market-enricher.ts +528 -497
  156. package/src/utils/market-importer.ts +31 -12
  157. package/src/utils/market-provider.ts +21 -23
  158. package/src/utils/massive-tier.ts +5 -7
  159. package/src/utils/migrate-option-data-helpers.ts +2 -8
  160. package/src/utils/option-quote-greeks.ts +25 -31
  161. package/src/utils/option-time.ts +4 -8
  162. package/src/utils/output-formatter.ts +1 -4
  163. package/src/utils/provider-capabilities.ts +1 -4
  164. package/src/utils/providers/massive.ts +59 -93
  165. package/src/utils/providers/thetadata/backfill.ts +14 -23
  166. package/src/utils/providers/thetadata/client.ts +12 -8
  167. package/src/utils/providers/thetadata/decode.ts +2 -20
  168. package/src/utils/providers/thetadata/endpoints.ts +32 -40
  169. package/src/utils/providers/thetadata/join.ts +11 -10
  170. package/src/utils/providers/thetadata/proto.ts +12 -10
  171. package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
  172. package/src/utils/providers/thetadata.ts +11 -10
  173. package/src/utils/quote-enricher.ts +4 -4
  174. package/src/utils/quote-parquet-projection.ts +3 -11
  175. package/src/utils/sample-date-selector.ts +3 -5
  176. package/src/utils/schema-metadata.ts +102 -70
  177. package/src/utils/ticker.ts +5 -9
  178. package/src/utils/trade-replay.ts +77 -68
  179. package/dist/chunk-4BLCXNQ6.js.map +0 -1
  180. package/dist/chunk-BKQ4PM4Y.js.map +0 -1
  181. package/dist/chunk-FGZH632F.js.map +0 -1
  182. package/dist/chunk-LDKTV7GW.js.map +0 -1
  183. package/dist/chunk-PRAYH3RT.js.map +0 -1
  184. package/dist/chunk-W2PP3LEH.js.map +0 -1
  185. package/dist/chunk-XXYOUIZY.js.map +0 -1
  186. package/dist/daily-log-processor-6MWJ23JK.js +0 -9
  187. package/dist/market-provider-VDRJUEF2.js +0 -16
  188. package/dist/trade-processor-NHU4VWRX.js +0 -9
  189. package/server/chunk-5EBXHT6C.js.map +0 -1
  190. package/server/chunk-72GKJE2U.js.map +0 -1
  191. package/server/chunk-CSDVJPBB.js.map +0 -1
  192. package/server/chunk-FBNDMCT5.js.map +0 -1
  193. package/server/chunk-NRFXAJF7.js.map +0 -1
  194. package/server/chunk-NZO6PT64.js.map +0 -1
  195. package/server/chunk-PNKG7RY7.js.map +0 -1
  196. package/server/chunk-WA5AAPCH.js.map +0 -1
  197. package/server/config-6IZXEFEX.js.map +0 -1
  198. package/server/daily-log-processor-Y3PVSVBM.js +0 -10
  199. package/server/market-provider-VOYYVYWT.js +0 -17
  200. package/server/trade-processor-JWVS37KM.js +0 -10
  201. /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-BY3ISY6K.js.map} +0 -0
  202. /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
  203. /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-H3ARIVZ4.js.map} +0 -0
  204. /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
  205. /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
  206. /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.js.map} +0 -0
  207. /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
  208. /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-ENEUT22A.js.map} +0 -0
  209. /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
  210. /package/server/{market-provider-VOYYVYWT.js.map → market-provider-B437HKLW.js.map} +0 -0
  211. /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
  212. /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
  213. /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.js.map} +0 -0
  214. /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
@@ -7,11 +7,7 @@
7
7
  import { z } from "zod";
8
8
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
9
  import { loadBlock, loadReportingLog } from "../../utils/block-loader.ts";
10
- import {
11
- createToolOutput,
12
- formatPercent,
13
- formatCurrency,
14
- } from "../../utils/output-formatter.ts";
10
+ import { createToolOutput, formatPercent, formatCurrency } from "../../utils/output-formatter.ts";
15
11
  import type { ReportingTrade } from "@tradeblocks/lib";
16
12
  import { pearsonCorrelation, kendallTau } from "@tradeblocks/lib";
17
13
  import {
@@ -25,10 +21,7 @@ import { withSyncedBlock } from "../middleware/sync-middleware.ts";
25
21
  /**
26
22
  * Register the analyze_discrepancies tool
27
23
  */
28
- export function registerDiscrepancyTool(
29
- server: McpServer,
30
- baseDir: string
31
- ): void {
24
+ export function registerDiscrepancyTool(server: McpServer, baseDir: string): void {
32
25
  server.registerTool(
33
26
  "analyze_discrepancies",
34
27
  {
@@ -36,10 +29,7 @@ export function registerDiscrepancyTool(
36
29
  "Analyze slippage patterns between backtest and actual trades. Detects systematic biases (direction, time-of-day) and correlates slippage with market conditions (VIX, gap, movement). Matches trades by date+strategy+time (minute precision). Requires both tradelog.csv (backtest) and reportinglog.csv (actual). Limitation: If multiple trades share the same date+strategy+minute, matching is order-dependent.",
37
30
  inputSchema: z.object({
38
31
  blockId: z.string().describe("Block folder name"),
39
- strategy: z
40
- .string()
41
- .optional()
42
- .describe("Filter to specific strategy name"),
32
+ strategy: z.string().optional().describe("Filter to specific strategy name"),
43
33
  dateRange: z
44
34
  .object({
45
35
  from: z.string().optional().describe("Start date YYYY-MM-DD"),
@@ -65,9 +55,7 @@ export function registerDiscrepancyTool(
65
55
  .min(0.5)
66
56
  .max(0.95)
67
57
  .default(0.7)
68
- .describe(
69
- "Threshold for detecting systematic patterns (0.7 = 70% consistency)"
70
- ),
58
+ .describe("Threshold for detecting systematic patterns (0.7 = 70% consistency)"),
71
59
  }),
72
60
  },
73
61
  withSyncedBlock(
@@ -83,353 +71,335 @@ export function registerDiscrepancyTool(
83
71
  }) => {
84
72
  try {
85
73
  const block = await loadBlock(baseDir, blockId);
86
- let backtestTrades = block.trades;
74
+ let backtestTrades = block.trades;
75
+
76
+ // Load reporting log (actual trades)
77
+ let actualTrades: ReportingTrade[];
78
+ try {
79
+ actualTrades = await loadReportingLog(baseDir, blockId);
80
+ } catch {
81
+ return {
82
+ content: [
83
+ {
84
+ type: "text",
85
+ text: `No reportinglog.csv found in block "${blockId}". This tool requires both tradelog.csv (backtest) and reportinglog.csv (actual).`,
86
+ },
87
+ ],
88
+ isError: true,
89
+ };
90
+ }
87
91
 
88
- // Load reporting log (actual trades)
89
- let actualTrades: ReportingTrade[];
90
- try {
91
- actualTrades = await loadReportingLog(baseDir, blockId);
92
- } catch {
93
- return {
94
- content: [
95
- {
96
- type: "text",
97
- text: `No reportinglog.csv found in block "${blockId}". This tool requires both tradelog.csv (backtest) and reportinglog.csv (actual).`,
98
- },
99
- ],
100
- isError: true,
101
- };
102
- }
92
+ // Apply filters
93
+ backtestTrades = applyStrategyFilter(backtestTrades, strategy);
94
+ actualTrades = applyStrategyFilter(actualTrades, strategy);
95
+ backtestTrades = applyDateRangeFilter(backtestTrades, dateRange);
96
+ actualTrades = applyDateRangeFilter(actualTrades, dateRange);
97
+
98
+ if (backtestTrades.length === 0) {
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text",
103
+ text: "No backtest trades found in tradelog.csv matching filters.",
104
+ },
105
+ ],
106
+ isError: true,
107
+ };
108
+ }
103
109
 
104
- // Apply filters
105
- backtestTrades = applyStrategyFilter(backtestTrades, strategy);
106
- actualTrades = applyStrategyFilter(actualTrades, strategy);
107
- backtestTrades = applyDateRangeFilter(backtestTrades, dateRange);
108
- actualTrades = applyDateRangeFilter(actualTrades, dateRange);
110
+ if (actualTrades.length === 0) {
111
+ return {
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: "No actual trades found in reportinglog.csv matching filters.",
116
+ },
117
+ ],
118
+ isError: true,
119
+ };
120
+ }
109
121
 
110
- if (backtestTrades.length === 0) {
111
- return {
112
- content: [
113
- {
114
- type: "text",
115
- text: "No backtest trades found in tradelog.csv matching filters.",
116
- },
117
- ],
118
- isError: true,
119
- };
120
- }
122
+ // Match trades
123
+ const { matchedTrades, unmatchedBacktestCount, unmatchedActualCount } = matchTrades(
124
+ backtestTrades,
125
+ actualTrades,
126
+ scaling,
127
+ );
121
128
 
122
- if (actualTrades.length === 0) {
123
- return {
124
- content: [
125
- {
126
- type: "text",
127
- text: "No actual trades found in reportinglog.csv matching filters.",
128
- },
129
- ],
130
- isError: true,
129
+ if (matchedTrades.length === 0) {
130
+ return {
131
+ content: [
132
+ {
133
+ type: "text",
134
+ text: "No matching trades found between backtest and actual data. Cannot perform slippage analysis.",
135
+ },
136
+ ],
137
+ isError: true,
138
+ };
139
+ }
140
+
141
+ // Calculate date range from matched trades
142
+ const dates = matchedTrades.map((t) => t.date).sort();
143
+ const dateRangeResult = {
144
+ from: dates[0],
145
+ to: dates[dates.length - 1],
131
146
  };
132
- }
133
147
 
134
- // Match trades
135
- const { matchedTrades, unmatchedBacktestCount, unmatchedActualCount } =
136
- matchTrades(backtestTrades, actualTrades, scaling);
148
+ // Calculate summary statistics
149
+ const slippages = matchedTrades.map((t) => t.totalSlippage);
150
+ const totalSlippage = slippages.reduce((sum, s) => sum + s, 0);
151
+ const avgSlippagePerTrade = totalSlippage / matchedTrades.length;
137
152
 
138
- if (matchedTrades.length === 0) {
139
- return {
140
- content: [
141
- {
142
- type: "text",
143
- text: "No matching trades found between backtest and actual data. Cannot perform slippage analysis.",
144
- },
145
- ],
146
- isError: true,
147
- };
148
- }
153
+ // Pattern insight interface
154
+ interface PatternInsight {
155
+ pattern: string;
156
+ metric: string;
157
+ value: number;
158
+ sampleSize: number;
159
+ }
149
160
 
150
- // Calculate date range from matched trades
151
- const dates = matchedTrades.map((t) => t.date).sort();
152
- const dateRangeResult = {
153
- from: dates[0],
154
- to: dates[dates.length - 1],
155
- };
156
-
157
- // Calculate summary statistics
158
- const slippages = matchedTrades.map((t) => t.totalSlippage);
159
- const totalSlippage = slippages.reduce((sum, s) => sum + s, 0);
160
- const avgSlippagePerTrade = totalSlippage / matchedTrades.length;
161
-
162
- // Pattern insight interface
163
- interface PatternInsight {
164
- pattern: string;
165
- metric: string;
166
- value: number;
167
- sampleSize: number;
168
- }
161
+ // Pattern detection function
162
+ const detectPatterns = (trades: MatchedTradeData[]): PatternInsight[] => {
163
+ const patterns: PatternInsight[] = [];
169
164
 
170
- // Pattern detection function
171
- const detectPatterns = (
172
- trades: MatchedTradeData[]
173
- ): PatternInsight[] => {
174
- const patterns: PatternInsight[] = [];
165
+ if (trades.length < minSamples) {
166
+ return patterns;
167
+ }
175
168
 
176
- if (trades.length < minSamples) {
177
- return patterns;
178
- }
169
+ // 1. Direction bias - if >patternThreshold of slippages are same sign
170
+ const tradeSlippages = trades.map((t) => t.totalSlippage);
171
+ const positiveCount = tradeSlippages.filter((s) => s > 0).length;
172
+ const negativeCount = tradeSlippages.filter((s) => s < 0).length;
173
+ const positiveRate = positiveCount / tradeSlippages.length;
174
+ const negativeRate = negativeCount / tradeSlippages.length;
179
175
 
180
- // 1. Direction bias - if >patternThreshold of slippages are same sign
181
- const tradeSlippages = trades.map((t) => t.totalSlippage);
182
- const positiveCount = tradeSlippages.filter((s) => s > 0).length;
183
- const negativeCount = tradeSlippages.filter((s) => s < 0).length;
184
- const positiveRate = positiveCount / tradeSlippages.length;
185
- const negativeRate = negativeCount / tradeSlippages.length;
186
-
187
- if (positiveRate >= patternThreshold) {
188
- patterns.push({
189
- pattern: `Direction bias: ${formatPercent(positiveRate * 100)} of trades have positive slippage (actual > backtest)`,
190
- metric: "positive_slippage_rate",
191
- value: positiveRate,
192
- sampleSize: tradeSlippages.length,
193
- });
194
- } else if (negativeRate >= patternThreshold) {
195
- patterns.push({
196
- pattern: `Direction bias: ${formatPercent(negativeRate * 100)} of trades have negative slippage (actual < backtest)`,
197
- metric: "negative_slippage_rate",
198
- value: negativeRate,
199
- sampleSize: tradeSlippages.length,
200
- });
201
- }
176
+ if (positiveRate >= patternThreshold) {
177
+ patterns.push({
178
+ pattern: `Direction bias: ${formatPercent(positiveRate * 100)} of trades have positive slippage (actual > backtest)`,
179
+ metric: "positive_slippage_rate",
180
+ value: positiveRate,
181
+ sampleSize: tradeSlippages.length,
182
+ });
183
+ } else if (negativeRate >= patternThreshold) {
184
+ patterns.push({
185
+ pattern: `Direction bias: ${formatPercent(negativeRate * 100)} of trades have negative slippage (actual < backtest)`,
186
+ metric: "negative_slippage_rate",
187
+ value: negativeRate,
188
+ sampleSize: tradeSlippages.length,
189
+ });
190
+ }
202
191
 
203
- // 2. Time-of-day clustering - if >patternThreshold of outlier trades occur in same time bucket
204
- const tradesWithHour = trades.filter((t) => t.hourOfDay !== null);
205
- if (tradesWithHour.length >= minSamples) {
206
- // Define time buckets: morning (9-11), midday (11-14), afternoon (14-16)
207
- const buckets = {
208
- morning: tradesWithHour.filter(
209
- (a) =>
210
- a.hourOfDay !== null && a.hourOfDay >= 9 && a.hourOfDay < 11
211
- ),
212
- midday: tradesWithHour.filter(
192
+ // 2. Time-of-day clustering - if >patternThreshold of outlier trades occur in same time bucket
193
+ const tradesWithHour = trades.filter((t) => t.hourOfDay !== null);
194
+ if (tradesWithHour.length >= minSamples) {
195
+ // Define time buckets: morning (9-11), midday (11-14), afternoon (14-16)
196
+ const buckets = {
197
+ morning: tradesWithHour.filter(
198
+ (a) => a.hourOfDay !== null && a.hourOfDay >= 9 && a.hourOfDay < 11,
199
+ ),
200
+ midday: tradesWithHour.filter(
201
+ (a) => a.hourOfDay !== null && a.hourOfDay >= 11 && a.hourOfDay < 14,
202
+ ),
203
+ afternoon: tradesWithHour.filter(
204
+ (a) => a.hourOfDay !== null && a.hourOfDay >= 14 && a.hourOfDay <= 16,
205
+ ),
206
+ };
207
+
208
+ // Find outliers (beyond 1.5 * IQR)
209
+ const sorted = [...slippages].sort((a, b) => a - b);
210
+ const q1 = sorted[Math.floor(sorted.length * 0.25)];
211
+ const q3 = sorted[Math.floor(sorted.length * 0.75)];
212
+ const iqr = q3 - q1;
213
+ const outlierThresholdLow = q1 - 1.5 * iqr;
214
+ const outlierThresholdHigh = q3 + 1.5 * iqr;
215
+
216
+ const outlierTrades = tradesWithHour.filter(
213
217
  (a) =>
214
- a.hourOfDay !== null && a.hourOfDay >= 11 && a.hourOfDay < 14
215
- ),
216
- afternoon: tradesWithHour.filter(
217
- (a) =>
218
- a.hourOfDay !== null && a.hourOfDay >= 14 && a.hourOfDay <= 16
219
- ),
220
- };
218
+ a.totalSlippage < outlierThresholdLow || a.totalSlippage > outlierThresholdHigh,
219
+ );
220
+
221
+ if (outlierTrades.length >= 3) {
222
+ for (const [bucketName, bucketTrades] of Object.entries(buckets)) {
223
+ const outlierInBucket = outlierTrades.filter((o) => bucketTrades.includes(o));
224
+ const bucketRate = outlierInBucket.length / outlierTrades.length;
225
+
226
+ if (bucketRate >= patternThreshold && outlierInBucket.length >= 3) {
227
+ patterns.push({
228
+ pattern: `Time clustering: ${formatPercent(bucketRate * 100)} of outlier trades occur during ${bucketName} hours`,
229
+ metric: "time_clustering_rate",
230
+ value: bucketRate,
231
+ sampleSize: outlierTrades.length,
232
+ });
233
+ break; // Only report strongest time pattern
234
+ }
235
+ }
236
+ }
237
+ }
221
238
 
222
- // Find outliers (beyond 1.5 * IQR)
223
- const sorted = [...slippages].sort((a, b) => a - b);
224
- const q1 = sorted[Math.floor(sorted.length * 0.25)];
225
- const q3 = sorted[Math.floor(sorted.length * 0.75)];
226
- const iqr = q3 - q1;
227
- const outlierThresholdLow = q1 - 1.5 * iqr;
228
- const outlierThresholdHigh = q3 + 1.5 * iqr;
229
-
230
- const outlierTrades = tradesWithHour.filter(
231
- (a) =>
232
- a.totalSlippage < outlierThresholdLow ||
233
- a.totalSlippage > outlierThresholdHigh
239
+ // 3. VIX sensitivity - correlation with openingVix if available
240
+ const vixTrades = trades.filter(
241
+ (t) => t.openingVix !== undefined && t.openingVix !== null,
234
242
  );
243
+ if (vixTrades.length >= minSamples) {
244
+ const vixValues = vixTrades.map((t) => t.openingVix!);
245
+ const vixSlippages = vixTrades.map((t) => t.totalSlippage);
235
246
 
236
- if (outlierTrades.length >= 3) {
237
- for (const [bucketName, bucketTrades] of Object.entries(
238
- buckets
239
- )) {
240
- const outlierInBucket = outlierTrades.filter((o) =>
241
- bucketTrades.includes(o)
242
- );
243
- const bucketRate = outlierInBucket.length / outlierTrades.length;
244
-
245
- if (
246
- bucketRate >= patternThreshold &&
247
- outlierInBucket.length >= 3
248
- ) {
249
- patterns.push({
250
- pattern: `Time clustering: ${formatPercent(bucketRate * 100)} of outlier trades occur during ${bucketName} hours`,
251
- metric: "time_clustering_rate",
252
- value: bucketRate,
253
- sampleSize: outlierTrades.length,
254
- });
255
- break; // Only report strongest time pattern
256
- }
247
+ const correlation =
248
+ correlationMethod === "pearson"
249
+ ? pearsonCorrelation(vixSlippages, vixValues)
250
+ : kendallTau(vixSlippages, vixValues);
251
+
252
+ const absCorr = Math.abs(correlation);
253
+ if (absCorr >= 0.3) {
254
+ // Only report if moderate or stronger
255
+ const direction = correlation > 0 ? "positive" : "negative";
256
+ patterns.push({
257
+ pattern: `VIX sensitivity: ${direction} correlation (${correlation.toFixed(3)}) between slippage and opening VIX`,
258
+ metric: "vix_correlation",
259
+ value: correlation,
260
+ sampleSize: vixTrades.length,
261
+ });
257
262
  }
258
263
  }
259
- }
260
264
 
261
- // 3. VIX sensitivity - correlation with openingVix if available
262
- const vixTrades = trades.filter(
263
- (t) => t.openingVix !== undefined && t.openingVix !== null
264
- );
265
- if (vixTrades.length >= minSamples) {
266
- const vixValues = vixTrades.map((t) => t.openingVix!);
267
- const vixSlippages = vixTrades.map((t) => t.totalSlippage);
268
-
269
- const correlation =
270
- correlationMethod === "pearson"
271
- ? pearsonCorrelation(vixSlippages, vixValues)
272
- : kendallTau(vixSlippages, vixValues);
273
-
274
- const absCorr = Math.abs(correlation);
275
- if (absCorr >= 0.3) {
276
- // Only report if moderate or stronger
277
- const direction = correlation > 0 ? "positive" : "negative";
278
- patterns.push({
279
- pattern: `VIX sensitivity: ${direction} correlation (${correlation.toFixed(3)}) between slippage and opening VIX`,
280
- metric: "vix_correlation",
281
- value: correlation,
282
- sampleSize: vixTrades.length,
283
- });
284
- }
285
- }
265
+ return patterns;
266
+ };
286
267
 
287
- return patterns;
288
- };
289
-
290
- // Calculate correlations (only returns significant correlations with |r| >= 0.3)
291
- const calculateCorrelations = (
292
- trades: MatchedTradeData[]
293
- ): Array<{
294
- field: string;
295
- coefficient: number;
296
- sampleSize: number;
297
- }> => {
298
- const results: Array<{
268
+ // Calculate correlations (only returns significant correlations with |r| >= 0.3)
269
+ const calculateCorrelations = (
270
+ trades: MatchedTradeData[],
271
+ ): Array<{
299
272
  field: string;
300
273
  coefficient: number;
301
274
  sampleSize: number;
302
- }> = [];
303
-
304
- const correlationFields: Array<{
305
- name: string;
306
- getValue: (t: MatchedTradeData) => number | undefined | null;
307
- }> = [
308
- { name: "openingVix", getValue: (t) => t.openingVix },
309
- { name: "closingVix", getValue: (t) => t.closingVix },
310
- { name: "gap", getValue: (t) => t.gap },
311
- { name: "movement", getValue: (t) => t.movement },
312
- { name: "hourOfDay", getValue: (t) => t.hourOfDay },
313
- { name: "contracts", getValue: (t) => t.contracts },
314
- ];
275
+ }> => {
276
+ const results: Array<{
277
+ field: string;
278
+ coefficient: number;
279
+ sampleSize: number;
280
+ }> = [];
281
+
282
+ const correlationFields: Array<{
283
+ name: string;
284
+ getValue: (t: MatchedTradeData) => number | undefined | null;
285
+ }> = [
286
+ { name: "openingVix", getValue: (t) => t.openingVix },
287
+ { name: "closingVix", getValue: (t) => t.closingVix },
288
+ { name: "gap", getValue: (t) => t.gap },
289
+ { name: "movement", getValue: (t) => t.movement },
290
+ { name: "hourOfDay", getValue: (t) => t.hourOfDay },
291
+ { name: "contracts", getValue: (t) => t.contracts },
292
+ ];
293
+
294
+ for (const { name, getValue } of correlationFields) {
295
+ const validPairs: Array<{ slippage: number; field: number }> = [];
296
+
297
+ for (const trade of trades) {
298
+ const fieldValue = getValue(trade);
299
+ if (fieldValue !== undefined && fieldValue !== null && isFinite(fieldValue)) {
300
+ validPairs.push({
301
+ slippage: trade.totalSlippage,
302
+ field: fieldValue,
303
+ });
304
+ }
305
+ }
315
306
 
316
- for (const { name, getValue } of correlationFields) {
317
- const validPairs: Array<{ slippage: number; field: number }> = [];
318
-
319
- for (const trade of trades) {
320
- const fieldValue = getValue(trade);
321
- if (
322
- fieldValue !== undefined &&
323
- fieldValue !== null &&
324
- isFinite(fieldValue)
325
- ) {
326
- validPairs.push({
327
- slippage: trade.totalSlippage,
328
- field: fieldValue,
329
- });
307
+ if (validPairs.length >= minSamples) {
308
+ const slippagesArr = validPairs.map((p) => p.slippage);
309
+ const fieldValues = validPairs.map((p) => p.field);
310
+
311
+ const coefficient =
312
+ correlationMethod === "pearson"
313
+ ? pearsonCorrelation(slippagesArr, fieldValues)
314
+ : kendallTau(slippagesArr, fieldValues);
315
+
316
+ // Only include significant correlations (|r| >= 0.3)
317
+ if (Math.abs(coefficient) >= 0.3) {
318
+ results.push({
319
+ field: name,
320
+ coefficient: Math.round(coefficient * 10000) / 10000,
321
+ sampleSize: validPairs.length,
322
+ });
323
+ }
330
324
  }
331
325
  }
332
326
 
333
- if (validPairs.length >= minSamples) {
334
- const slippagesArr = validPairs.map((p) => p.slippage);
335
- const fieldValues = validPairs.map((p) => p.field);
327
+ // Sort by absolute coefficient descending
328
+ results.sort((a, b) => Math.abs(b.coefficient) - Math.abs(a.coefficient));
336
329
 
337
- const coefficient =
338
- correlationMethod === "pearson"
339
- ? pearsonCorrelation(slippagesArr, fieldValues)
340
- : kendallTau(slippagesArr, fieldValues);
341
-
342
- // Only include significant correlations (|r| >= 0.3)
343
- if (Math.abs(coefficient) >= 0.3) {
344
- results.push({
345
- field: name,
346
- coefficient: Math.round(coefficient * 10000) / 10000,
347
- sampleSize: validPairs.length,
348
- });
349
- }
350
- }
351
- }
330
+ return results;
331
+ };
352
332
 
353
- // Sort by absolute coefficient descending
354
- results.sort(
355
- (a, b) => Math.abs(b.coefficient) - Math.abs(a.coefficient)
356
- );
333
+ // Portfolio-wide patterns and correlations
334
+ const portfolioPatterns = detectPatterns(matchedTrades);
335
+ const portfolioCorrelations = calculateCorrelations(matchedTrades);
357
336
 
358
- return results;
359
- };
337
+ // Per-strategy breakdown (always included, simplified output)
338
+ const byStrategy = new Map<string, MatchedTradeData[]>();
339
+ for (const trade of matchedTrades) {
340
+ const existing = byStrategy.get(trade.strategy) ?? [];
341
+ existing.push(trade);
342
+ byStrategy.set(trade.strategy, existing);
343
+ }
360
344
 
361
- // Portfolio-wide patterns and correlations
362
- const portfolioPatterns = detectPatterns(matchedTrades);
363
- const portfolioCorrelations = calculateCorrelations(matchedTrades);
345
+ const perStrategy: Array<{
346
+ strategy: string;
347
+ tradeCount: number;
348
+ totalSlippage: number;
349
+ avgSlippage: number;
350
+ }> = [];
364
351
 
365
- // Per-strategy breakdown (always included, simplified output)
366
- const byStrategy = new Map<string, MatchedTradeData[]>();
367
- for (const trade of matchedTrades) {
368
- const existing = byStrategy.get(trade.strategy) ?? [];
369
- existing.push(trade);
370
- byStrategy.set(trade.strategy, existing);
371
- }
352
+ for (const [strategyName, trades] of byStrategy) {
353
+ const stratSlippages = trades.map((t) => t.totalSlippage);
354
+ const stratTotal = stratSlippages.reduce((sum, s) => sum + s, 0);
355
+ const stratAvg = trades.length > 0 ? stratTotal / trades.length : 0;
372
356
 
373
- const perStrategy: Array<{
374
- strategy: string;
375
- tradeCount: number;
376
- totalSlippage: number;
377
- avgSlippage: number;
378
- }> = [];
379
-
380
- for (const [strategyName, trades] of byStrategy) {
381
- const stratSlippages = trades.map((t) => t.totalSlippage);
382
- const stratTotal = stratSlippages.reduce((sum, s) => sum + s, 0);
383
- const stratAvg = trades.length > 0 ? stratTotal / trades.length : 0;
384
-
385
- perStrategy.push({
386
- strategy: strategyName,
387
- tradeCount: trades.length,
388
- totalSlippage: stratTotal,
389
- avgSlippage: stratAvg,
390
- });
391
- }
357
+ perStrategy.push({
358
+ strategy: strategyName,
359
+ tradeCount: trades.length,
360
+ totalSlippage: stratTotal,
361
+ avgSlippage: stratAvg,
362
+ });
363
+ }
392
364
 
393
- // Sort by absolute total slippage descending
394
- perStrategy.sort(
395
- (a, b) => Math.abs(b.totalSlippage) - Math.abs(a.totalSlippage)
396
- );
365
+ // Sort by absolute total slippage descending
366
+ perStrategy.sort((a, b) => Math.abs(b.totalSlippage) - Math.abs(a.totalSlippage));
397
367
 
398
- // Build summary string
399
- const summaryParts = [
400
- `Slippage analysis: ${matchedTrades.length} matched trades`,
401
- `Total slippage: ${formatCurrency(totalSlippage)}`,
402
- `Avg per trade: ${formatCurrency(avgSlippagePerTrade)}`,
403
- ];
368
+ // Build summary string
369
+ const summaryParts = [
370
+ `Slippage analysis: ${matchedTrades.length} matched trades`,
371
+ `Total slippage: ${formatCurrency(totalSlippage)}`,
372
+ `Avg per trade: ${formatCurrency(avgSlippagePerTrade)}`,
373
+ ];
404
374
 
405
- if (portfolioPatterns.length > 0) {
406
- summaryParts.push(`${portfolioPatterns.length} patterns detected`);
407
- }
375
+ if (portfolioPatterns.length > 0) {
376
+ summaryParts.push(`${portfolioPatterns.length} patterns detected`);
377
+ }
378
+
379
+ const summary = summaryParts.join(" | ");
380
+
381
+ const structuredData = {
382
+ summary: {
383
+ matchedTrades: matchedTrades.length,
384
+ unmatchedBacktest: unmatchedBacktestCount,
385
+ unmatchedActual: unmatchedActualCount,
386
+ totalSlippage,
387
+ avgSlippagePerTrade,
388
+ dateRange: dateRangeResult,
389
+ },
390
+ patterns: portfolioPatterns,
391
+ correlations: {
392
+ method: correlationMethod,
393
+ results: portfolioCorrelations,
394
+ note:
395
+ portfolioCorrelations.length === 0
396
+ ? "No significant correlations found (|r| >= 0.3)"
397
+ : undefined,
398
+ },
399
+ perStrategy,
400
+ };
408
401
 
409
- const summary = summaryParts.join(" | ");
410
-
411
- const structuredData = {
412
- summary: {
413
- matchedTrades: matchedTrades.length,
414
- unmatchedBacktest: unmatchedBacktestCount,
415
- unmatchedActual: unmatchedActualCount,
416
- totalSlippage,
417
- avgSlippagePerTrade,
418
- dateRange: dateRangeResult,
419
- },
420
- patterns: portfolioPatterns,
421
- correlations: {
422
- method: correlationMethod,
423
- results: portfolioCorrelations,
424
- note:
425
- portfolioCorrelations.length === 0
426
- ? "No significant correlations found (|r| >= 0.3)"
427
- : undefined,
428
- },
429
- perStrategy,
430
- };
431
-
432
- return createToolOutput(summary, structuredData);
402
+ return createToolOutput(summary, structuredData);
433
403
  } catch (error) {
434
404
  return {
435
405
  content: [
@@ -441,7 +411,7 @@ export function registerDiscrepancyTool(
441
411
  isError: true,
442
412
  };
443
413
  }
444
- }
445
- )
414
+ },
415
+ ),
446
416
  );
447
417
  }