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
@@ -23,10 +23,7 @@ import { withSyncedBlock } from "../middleware/sync-middleware.ts";
23
23
  /**
24
24
  * Register the analyze_slippage_trends tool
25
25
  */
26
- export function registerSlippageTrendsTool(
27
- server: McpServer,
28
- baseDir: string
29
- ): void {
26
+ export function registerSlippageTrendsTool(server: McpServer, baseDir: string): void {
30
27
  server.registerTool(
31
28
  "analyze_slippage_trends",
32
29
  {
@@ -34,10 +31,7 @@ export function registerSlippageTrendsTool(
34
31
  "Analyze slippage trends over time with statistical significance testing. Detects improvement/degradation patterns using linear regression on time-aggregated slippage data. Provides slope, R-squared, and p-value. Requires both tradelog.csv (backtest) and reportinglog.csv (actual). Limitation: Trade matching uses minute precision; if multiple trades share the same date+strategy+minute, matching is order-dependent.",
35
32
  inputSchema: z.object({
36
33
  blockId: z.string().describe("Block folder name"),
37
- strategy: z
38
- .string()
39
- .optional()
40
- .describe("Filter to specific strategy name"),
34
+ strategy: z.string().optional().describe("Filter to specific strategy name"),
41
35
  dateRange: z
42
36
  .object({
43
37
  from: z.string().optional().describe("Start date YYYY-MM-DD"),
@@ -56,9 +50,7 @@ export function registerSlippageTrendsTool(
56
50
  includeTimeSeries: z
57
51
  .boolean()
58
52
  .default(false)
59
- .describe(
60
- "Include raw time series data points in output (for charting)"
61
- ),
53
+ .describe("Include raw time series data points in output (for charting)"),
62
54
  correlationMethod: z
63
55
  .enum(["pearson", "kendall"])
64
56
  .default("pearson")
@@ -84,376 +76,354 @@ export function registerSlippageTrendsTool(
84
76
  }) => {
85
77
  try {
86
78
  const block = await loadBlock(baseDir, blockId);
87
- let backtestTrades = block.trades;
88
-
89
- // Load reporting log (actual trades)
90
- let actualTrades: ReportingTrade[];
91
- try {
92
- actualTrades = await loadReportingLog(baseDir, blockId);
93
- } catch {
94
- return {
95
- content: [
96
- {
97
- type: "text",
98
- text: `No reportinglog.csv found in block "${blockId}". This tool requires both tradelog.csv (backtest) and reportinglog.csv (actual).`,
99
- },
100
- ],
101
- isError: true,
102
- };
103
- }
104
-
105
- // Apply filters
106
- backtestTrades = applyStrategyFilter(backtestTrades, strategy);
107
- actualTrades = applyStrategyFilter(actualTrades, strategy);
108
- backtestTrades = applyDateRangeFilter(backtestTrades, dateRange);
109
- actualTrades = applyDateRangeFilter(actualTrades, dateRange);
110
-
111
- if (backtestTrades.length === 0) {
112
- return {
113
- content: [
114
- {
115
- type: "text",
116
- text: "No backtest trades found in tradelog.csv matching filters.",
117
- },
118
- ],
119
- isError: true,
120
- };
121
- }
122
-
123
- if (actualTrades.length === 0) {
124
- return {
125
- content: [
126
- {
127
- type: "text",
128
- text: "No actual trades found in reportinglog.csv matching filters.",
129
- },
130
- ],
131
- isError: true,
132
- };
133
- }
134
-
135
- // Match trades
136
- const { matchedTrades } = matchTrades(
137
- backtestTrades,
138
- actualTrades,
139
- scaling
140
- );
141
-
142
- if (matchedTrades.length === 0) {
143
- return {
144
- content: [
145
- {
146
- type: "text",
147
- text: "No matching trades found between backtest and actual data. Cannot perform trend analysis.",
148
- },
149
- ],
150
- isError: true,
151
- };
152
- }
79
+ let backtestTrades = block.trades;
80
+
81
+ // Load reporting log (actual trades)
82
+ let actualTrades: ReportingTrade[];
83
+ try {
84
+ actualTrades = await loadReportingLog(baseDir, blockId);
85
+ } catch {
86
+ return {
87
+ content: [
88
+ {
89
+ type: "text",
90
+ text: `No reportinglog.csv found in block "${blockId}". This tool requires both tradelog.csv (backtest) and reportinglog.csv (actual).`,
91
+ },
92
+ ],
93
+ isError: true,
94
+ };
95
+ }
153
96
 
154
- // Helper to get period key based on granularity
155
- const getPeriodKey = (dateStr: string): string => {
156
- if (granularity === "daily") return dateStr;
157
- if (granularity === "weekly") return getIsoWeekKey(dateStr);
158
- return getMonthKey(dateStr);
159
- };
160
-
161
- // Period slippage interface
162
- interface PeriodSlippage {
163
- period: string;
164
- totalSlippage: number;
165
- avgSlippage: number;
166
- tradeCount: number;
167
- avgMagnitude: number;
168
- }
97
+ // Apply filters
98
+ backtestTrades = applyStrategyFilter(backtestTrades, strategy);
99
+ actualTrades = applyStrategyFilter(actualTrades, strategy);
100
+ backtestTrades = applyDateRangeFilter(backtestTrades, dateRange);
101
+ actualTrades = applyDateRangeFilter(actualTrades, dateRange);
169
102
 
170
- // Aggregate matched trades by period
171
- const aggregateByPeriod = (
172
- trades: MatchedTradeData[]
173
- ): PeriodSlippage[] => {
174
- const periodMap = new Map<
175
- string,
176
- { slippages: number[]; count: number }
177
- >();
178
-
179
- for (const trade of trades) {
180
- const periodKey = getPeriodKey(trade.date);
181
- const existing = periodMap.get(periodKey) || {
182
- slippages: [],
183
- count: 0,
103
+ if (backtestTrades.length === 0) {
104
+ return {
105
+ content: [
106
+ {
107
+ type: "text",
108
+ text: "No backtest trades found in tradelog.csv matching filters.",
109
+ },
110
+ ],
111
+ isError: true,
184
112
  };
185
- existing.slippages.push(trade.totalSlippage);
186
- existing.count++;
187
- periodMap.set(periodKey, existing);
188
113
  }
189
114
 
190
- const periods: PeriodSlippage[] = [];
191
- for (const [period, data] of periodMap) {
192
- const totalSlippage = data.slippages.reduce((sum, s) => sum + s, 0);
193
- const avgSlippage = totalSlippage / data.count;
194
- const avgMagnitude =
195
- data.slippages.reduce((sum, s) => sum + Math.abs(s), 0) /
196
- data.count;
197
-
198
- periods.push({
199
- period,
200
- totalSlippage,
201
- avgSlippage,
202
- tradeCount: data.count,
203
- avgMagnitude,
204
- });
115
+ if (actualTrades.length === 0) {
116
+ return {
117
+ content: [
118
+ {
119
+ type: "text",
120
+ text: "No actual trades found in reportinglog.csv matching filters.",
121
+ },
122
+ ],
123
+ isError: true,
124
+ };
205
125
  }
206
126
 
207
- // Sort by period chronologically
208
- periods.sort((a, b) => a.period.localeCompare(b.period));
209
-
210
- return periods;
211
- };
127
+ // Match trades
128
+ const { matchedTrades } = matchTrades(backtestTrades, actualTrades, scaling);
212
129
 
213
- // Trend result interface
214
- interface TrendResult {
215
- slope: number;
216
- intercept: number;
217
- rSquared: number;
218
- pValue: number;
219
- stderr: number;
220
- }
130
+ if (matchedTrades.length === 0) {
131
+ return {
132
+ content: [
133
+ {
134
+ type: "text",
135
+ text: "No matching trades found between backtest and actual data. Cannot perform trend analysis.",
136
+ },
137
+ ],
138
+ isError: true,
139
+ };
140
+ }
221
141
 
222
- // Linear regression with statistics
223
- const linearRegression = (y: number[]): TrendResult | null => {
224
- const n = y.length;
225
- if (n < 2) return null;
142
+ // Helper to get period key based on granularity
143
+ const getPeriodKey = (dateStr: string): string => {
144
+ if (granularity === "daily") return dateStr;
145
+ if (granularity === "weekly") return getIsoWeekKey(dateStr);
146
+ return getMonthKey(dateStr);
147
+ };
226
148
 
227
- // X values are period indices (0, 1, 2, ...)
228
- const x = y.map((_, i) => i);
149
+ // Period slippage interface
150
+ interface PeriodSlippage {
151
+ period: string;
152
+ totalSlippage: number;
153
+ avgSlippage: number;
154
+ tradeCount: number;
155
+ avgMagnitude: number;
156
+ }
229
157
 
230
- // Calculate means
231
- const meanX = x.reduce((a, b) => a + b, 0) / n;
232
- const meanY = y.reduce((a, b) => a + b, 0) / n;
158
+ // Aggregate matched trades by period
159
+ const aggregateByPeriod = (trades: MatchedTradeData[]): PeriodSlippage[] => {
160
+ const periodMap = new Map<string, { slippages: number[]; count: number }>();
161
+
162
+ for (const trade of trades) {
163
+ const periodKey = getPeriodKey(trade.date);
164
+ const existing = periodMap.get(periodKey) || {
165
+ slippages: [],
166
+ count: 0,
167
+ };
168
+ existing.slippages.push(trade.totalSlippage);
169
+ existing.count++;
170
+ periodMap.set(periodKey, existing);
171
+ }
172
+
173
+ const periods: PeriodSlippage[] = [];
174
+ for (const [period, data] of periodMap) {
175
+ const totalSlippage = data.slippages.reduce((sum, s) => sum + s, 0);
176
+ const avgSlippage = totalSlippage / data.count;
177
+ const avgMagnitude =
178
+ data.slippages.reduce((sum, s) => sum + Math.abs(s), 0) / data.count;
179
+
180
+ periods.push({
181
+ period,
182
+ totalSlippage,
183
+ avgSlippage,
184
+ tradeCount: data.count,
185
+ avgMagnitude,
186
+ });
187
+ }
188
+
189
+ // Sort by period chronologically
190
+ periods.sort((a, b) => a.period.localeCompare(b.period));
191
+
192
+ return periods;
193
+ };
233
194
 
234
- // OLS: slope = sum((xi-meanX)(yi-meanY)) / sum((xi-meanX)^2)
235
- let sumXY = 0;
236
- let sumX2 = 0;
237
- for (let i = 0; i < n; i++) {
238
- sumXY += (x[i] - meanX) * (y[i] - meanY);
239
- sumX2 += (x[i] - meanX) ** 2;
195
+ // Trend result interface
196
+ interface TrendResult {
197
+ slope: number;
198
+ intercept: number;
199
+ rSquared: number;
200
+ pValue: number;
201
+ stderr: number;
240
202
  }
241
- const slope = sumX2 > 0 ? sumXY / sumX2 : 0;
242
- const intercept = meanY - slope * meanX;
243
-
244
- // R-squared = 1 - SSres/SStot
245
- const predicted = x.map((xi) => slope * xi + intercept);
246
- const ssRes = y.reduce(
247
- (sum, yi, i) => sum + (yi - predicted[i]) ** 2,
248
- 0
249
- );
250
- const ssTot = y.reduce((sum, yi) => sum + (yi - meanY) ** 2, 0);
251
- const rSquared = ssTot > 0 ? 1 - ssRes / ssTot : 0;
252
203
 
253
- // Standard error and t-statistic for p-value
254
- const mse = n > 2 ? ssRes / (n - 2) : 0;
255
- const stderr = sumX2 > 0 ? Math.sqrt(mse / sumX2) : 0;
256
- const tStat = stderr > 0 ? slope / stderr : 0;
204
+ // Linear regression with statistics
205
+ const linearRegression = (y: number[]): TrendResult | null => {
206
+ const n = y.length;
207
+ if (n < 2) return null;
208
+
209
+ // X values are period indices (0, 1, 2, ...)
210
+ const x = y.map((_, i) => i);
211
+
212
+ // Calculate means
213
+ const meanX = x.reduce((a, b) => a + b, 0) / n;
214
+ const meanY = y.reduce((a, b) => a + b, 0) / n;
215
+
216
+ // OLS: slope = sum((xi-meanX)(yi-meanY)) / sum((xi-meanX)^2)
217
+ let sumXY = 0;
218
+ let sumX2 = 0;
219
+ for (let i = 0; i < n; i++) {
220
+ sumXY += (x[i] - meanX) * (y[i] - meanY);
221
+ sumX2 += (x[i] - meanX) ** 2;
222
+ }
223
+ const slope = sumX2 > 0 ? sumXY / sumX2 : 0;
224
+ const intercept = meanY - slope * meanX;
225
+
226
+ // R-squared = 1 - SSres/SStot
227
+ const predicted = x.map((xi) => slope * xi + intercept);
228
+ const ssRes = y.reduce((sum, yi, i) => sum + (yi - predicted[i]) ** 2, 0);
229
+ const ssTot = y.reduce((sum, yi) => sum + (yi - meanY) ** 2, 0);
230
+ const rSquared = ssTot > 0 ? 1 - ssRes / ssTot : 0;
231
+
232
+ // Standard error and t-statistic for p-value
233
+ const mse = n > 2 ? ssRes / (n - 2) : 0;
234
+ const stderr = sumX2 > 0 ? Math.sqrt(mse / sumX2) : 0;
235
+ const tStat = stderr > 0 ? slope / stderr : 0;
236
+
237
+ // Two-tailed p-value using normal approximation
238
+ const pValue = 2 * (1 - normalCDF(Math.abs(tStat)));
239
+
240
+ return {
241
+ slope: Math.round(slope * 10000) / 10000,
242
+ intercept: Math.round(intercept * 100) / 100,
243
+ rSquared: Math.round(rSquared * 10000) / 10000,
244
+ pValue: Math.round(pValue * 10000) / 10000,
245
+ stderr: Math.round(stderr * 10000) / 10000,
246
+ };
247
+ };
257
248
 
258
- // Two-tailed p-value using normal approximation
259
- const pValue = 2 * (1 - normalCDF(Math.abs(tStat)));
249
+ // Aggregate all matched trades by period
250
+ const periodSlippages = aggregateByPeriod(matchedTrades);
260
251
 
261
- return {
262
- slope: Math.round(slope * 10000) / 10000,
263
- intercept: Math.round(intercept * 100) / 100,
264
- rSquared: Math.round(rSquared * 10000) / 10000,
265
- pValue: Math.round(pValue * 10000) / 10000,
266
- stderr: Math.round(stderr * 10000) / 10000,
252
+ // Calculate date range from matched trades
253
+ const dates = matchedTrades.map((t) => t.date).sort();
254
+ const dateRangeResult = {
255
+ from: dates[0],
256
+ to: dates[dates.length - 1],
267
257
  };
268
- };
269
-
270
- // Aggregate all matched trades by period
271
- const periodSlippages = aggregateByPeriod(matchedTrades);
272
-
273
- // Calculate date range from matched trades
274
- const dates = matchedTrades.map((t) => t.date).sort();
275
- const dateRangeResult = {
276
- from: dates[0],
277
- to: dates[dates.length - 1],
278
- };
279
-
280
- // Calculate summary statistics
281
- const totalSlippage = matchedTrades.reduce(
282
- (sum, t) => sum + t.totalSlippage,
283
- 0
284
- );
285
- const avgSlippagePerTrade = totalSlippage / matchedTrades.length;
286
- const avgSlippagePerPeriod =
287
- periodSlippages.length > 0
288
- ? periodSlippages.reduce((sum, p) => sum + p.totalSlippage, 0) /
289
- periodSlippages.length
290
- : 0;
291
-
292
- // Calculate block-level trend (only if enough samples)
293
- const periodAvgSlippages = periodSlippages.map((p) => p.avgSlippage);
294
- const blockTrend =
295
- matchedTrades.length >= minSamples
296
- ? linearRegression(periodAvgSlippages)
297
- : null;
298
-
299
- // Per-strategy breakdown
300
- const byStrategy = new Map<string, MatchedTradeData[]>();
301
- for (const trade of matchedTrades) {
302
- const existing = byStrategy.get(trade.strategy) ?? [];
303
- existing.push(trade);
304
- byStrategy.set(trade.strategy, existing);
305
- }
306
258
 
307
- const perStrategy: Array<{
308
- strategy: string;
309
- matchedTrades: number;
310
- periodsAnalyzed: number;
311
- totalSlippage: number;
312
- trend: TrendResult | null;
313
- }> = [];
259
+ // Calculate summary statistics
260
+ const totalSlippage = matchedTrades.reduce((sum, t) => sum + t.totalSlippage, 0);
261
+ const avgSlippagePerTrade = totalSlippage / matchedTrades.length;
262
+ const avgSlippagePerPeriod =
263
+ periodSlippages.length > 0
264
+ ? periodSlippages.reduce((sum, p) => sum + p.totalSlippage, 0) /
265
+ periodSlippages.length
266
+ : 0;
267
+
268
+ // Calculate block-level trend (only if enough samples)
269
+ const periodAvgSlippages = periodSlippages.map((p) => p.avgSlippage);
270
+ const blockTrend =
271
+ matchedTrades.length >= minSamples ? linearRegression(periodAvgSlippages) : null;
272
+
273
+ // Per-strategy breakdown
274
+ const byStrategy = new Map<string, MatchedTradeData[]>();
275
+ for (const trade of matchedTrades) {
276
+ const existing = byStrategy.get(trade.strategy) ?? [];
277
+ existing.push(trade);
278
+ byStrategy.set(trade.strategy, existing);
279
+ }
280
+
281
+ const perStrategy: Array<{
282
+ strategy: string;
283
+ matchedTrades: number;
284
+ periodsAnalyzed: number;
285
+ totalSlippage: number;
286
+ trend: TrendResult | null;
287
+ }> = [];
288
+
289
+ for (const [strategyName, trades] of byStrategy) {
290
+ if (trades.length < minSamples) {
291
+ perStrategy.push({
292
+ strategy: strategyName,
293
+ matchedTrades: trades.length,
294
+ periodsAnalyzed: 0,
295
+ totalSlippage: trades.reduce((sum, t) => sum + t.totalSlippage, 0),
296
+ trend: null,
297
+ });
298
+ continue;
299
+ }
300
+
301
+ const strategyPeriods = aggregateByPeriod(trades);
302
+ const strategyTrend =
303
+ strategyPeriods.length >= 2
304
+ ? linearRegression(strategyPeriods.map((p) => p.avgSlippage))
305
+ : null;
314
306
 
315
- for (const [strategyName, trades] of byStrategy) {
316
- if (trades.length < minSamples) {
317
307
  perStrategy.push({
318
308
  strategy: strategyName,
319
309
  matchedTrades: trades.length,
320
- periodsAnalyzed: 0,
310
+ periodsAnalyzed: strategyPeriods.length,
321
311
  totalSlippage: trades.reduce((sum, t) => sum + t.totalSlippage, 0),
322
- trend: null,
312
+ trend: strategyTrend,
323
313
  });
324
- continue;
325
314
  }
326
315
 
327
- const strategyPeriods = aggregateByPeriod(trades);
328
- const strategyTrend =
329
- strategyPeriods.length >= 2
330
- ? linearRegression(strategyPeriods.map((p) => p.avgSlippage))
331
- : null;
332
-
333
- perStrategy.push({
334
- strategy: strategyName,
335
- matchedTrades: trades.length,
336
- periodsAnalyzed: strategyPeriods.length,
337
- totalSlippage: trades.reduce((sum, t) => sum + t.totalSlippage, 0),
338
- trend: strategyTrend,
339
- });
340
- }
341
-
342
- // Sort by absolute total slippage descending
343
- perStrategy.sort(
344
- (a, b) => Math.abs(b.totalSlippage) - Math.abs(a.totalSlippage)
345
- );
346
-
347
- // External factor correlation (VIX)
348
- interface ExternalFactorResult {
349
- factor: string;
350
- coefficient: number;
351
- sampleSize: number;
352
- }
316
+ // Sort by absolute total slippage descending
317
+ perStrategy.sort((a, b) => Math.abs(b.totalSlippage) - Math.abs(a.totalSlippage));
353
318
 
354
- let externalFactors:
355
- | { method: string; results: ExternalFactorResult[] }
356
- | undefined;
357
-
358
- const vixTrades = matchedTrades.filter(
359
- (t) => t.openingVix !== undefined && t.openingVix !== null
360
- );
319
+ // External factor correlation (VIX)
320
+ interface ExternalFactorResult {
321
+ factor: string;
322
+ coefficient: number;
323
+ sampleSize: number;
324
+ }
361
325
 
362
- if (vixTrades.length >= minSamples) {
363
- const vixValues = vixTrades.map((t) => t.openingVix!);
364
- const slippageValues = vixTrades.map((t) => t.totalSlippage);
326
+ let externalFactors: { method: string; results: ExternalFactorResult[] } | undefined;
365
327
 
366
- const coefficient =
367
- correlationMethod === "pearson"
368
- ? pearsonCorrelation(slippageValues, vixValues)
369
- : kendallTau(slippageValues, vixValues);
328
+ const vixTrades = matchedTrades.filter(
329
+ (t) => t.openingVix !== undefined && t.openingVix !== null,
330
+ );
370
331
 
371
- // Only include if meaningful (|r| >= 0.1)
372
- if (Math.abs(coefficient) >= 0.1) {
373
- externalFactors = {
374
- method: correlationMethod,
375
- results: [
376
- {
377
- factor: "openingVix",
378
- coefficient: Math.round(coefficient * 10000) / 10000,
379
- sampleSize: vixTrades.length,
380
- },
381
- ],
382
- };
332
+ if (vixTrades.length >= minSamples) {
333
+ const vixValues = vixTrades.map((t) => t.openingVix!);
334
+ const slippageValues = vixTrades.map((t) => t.totalSlippage);
335
+
336
+ const coefficient =
337
+ correlationMethod === "pearson"
338
+ ? pearsonCorrelation(slippageValues, vixValues)
339
+ : kendallTau(slippageValues, vixValues);
340
+
341
+ // Only include if meaningful (|r| >= 0.1)
342
+ if (Math.abs(coefficient) >= 0.1) {
343
+ externalFactors = {
344
+ method: correlationMethod,
345
+ results: [
346
+ {
347
+ factor: "openingVix",
348
+ coefficient: Math.round(coefficient * 10000) / 10000,
349
+ sampleSize: vixTrades.length,
350
+ },
351
+ ],
352
+ };
353
+ }
383
354
  }
384
- }
385
355
 
386
- // Build summary text
387
- const summaryParts = [
388
- `Slippage trends (${granularity}): ${periodSlippages.length} periods, ${matchedTrades.length} trades`,
389
- `Total: ${formatCurrency(totalSlippage)}`,
390
- ];
356
+ // Build summary text
357
+ const summaryParts = [
358
+ `Slippage trends (${granularity}): ${periodSlippages.length} periods, ${matchedTrades.length} trades`,
359
+ `Total: ${formatCurrency(totalSlippage)}`,
360
+ ];
391
361
 
392
- if (blockTrend) {
393
- summaryParts.push(
394
- `Trend: slope=${blockTrend.slope} (p=${blockTrend.pValue.toFixed(3)})`
395
- );
396
- }
362
+ if (blockTrend) {
363
+ summaryParts.push(
364
+ `Trend: slope=${blockTrend.slope} (p=${blockTrend.pValue.toFixed(3)})`,
365
+ );
366
+ }
397
367
 
398
- const summary = summaryParts.join(" | ");
368
+ const summary = summaryParts.join(" | ");
399
369
 
400
- // Build structured output
401
- const structuredData: {
402
- blockId: string;
403
- filters: {
404
- strategy: string | null;
405
- dateRange: { from?: string; to?: string } | null;
406
- };
407
- scaling: string;
408
- granularity: string;
409
- dateRange: { from: string; to: string };
410
- summary: {
411
- matchedTrades: number;
412
- periodsAnalyzed: number;
413
- totalSlippage: number;
414
- avgSlippagePerTrade: number;
415
- avgSlippagePerPeriod: number;
370
+ // Build structured output
371
+ const structuredData: {
372
+ blockId: string;
373
+ filters: {
374
+ strategy: string | null;
375
+ dateRange: { from?: string; to?: string } | null;
376
+ };
377
+ scaling: string;
378
+ granularity: string;
379
+ dateRange: { from: string; to: string };
380
+ summary: {
381
+ matchedTrades: number;
382
+ periodsAnalyzed: number;
383
+ totalSlippage: number;
384
+ avgSlippagePerTrade: number;
385
+ avgSlippagePerPeriod: number;
386
+ };
387
+ trend: TrendResult | null;
388
+ timeSeries?: PeriodSlippage[];
389
+ perStrategy: typeof perStrategy;
390
+ externalFactors?: typeof externalFactors;
391
+ } = {
392
+ blockId,
393
+ filters: {
394
+ strategy: strategy ?? null,
395
+ dateRange: dateRange ?? null,
396
+ },
397
+ scaling,
398
+ granularity,
399
+ dateRange: dateRangeResult,
400
+ summary: {
401
+ matchedTrades: matchedTrades.length,
402
+ periodsAnalyzed: periodSlippages.length,
403
+ totalSlippage: Math.round(totalSlippage * 100) / 100,
404
+ avgSlippagePerTrade: Math.round(avgSlippagePerTrade * 100) / 100,
405
+ avgSlippagePerPeriod: Math.round(avgSlippagePerPeriod * 100) / 100,
406
+ },
407
+ trend: blockTrend,
408
+ perStrategy,
416
409
  };
417
- trend: TrendResult | null;
418
- timeSeries?: PeriodSlippage[];
419
- perStrategy: typeof perStrategy;
420
- externalFactors?: typeof externalFactors;
421
- } = {
422
- blockId,
423
- filters: {
424
- strategy: strategy ?? null,
425
- dateRange: dateRange ?? null,
426
- },
427
- scaling,
428
- granularity,
429
- dateRange: dateRangeResult,
430
- summary: {
431
- matchedTrades: matchedTrades.length,
432
- periodsAnalyzed: periodSlippages.length,
433
- totalSlippage: Math.round(totalSlippage * 100) / 100,
434
- avgSlippagePerTrade: Math.round(avgSlippagePerTrade * 100) / 100,
435
- avgSlippagePerPeriod: Math.round(avgSlippagePerPeriod * 100) / 100,
436
- },
437
- trend: blockTrend,
438
- perStrategy,
439
- };
440
-
441
- // Add optional time series data
442
- if (includeTimeSeries) {
443
- structuredData.timeSeries = periodSlippages.map((p) => ({
444
- ...p,
445
- totalSlippage: Math.round(p.totalSlippage * 100) / 100,
446
- avgSlippage: Math.round(p.avgSlippage * 100) / 100,
447
- avgMagnitude: Math.round(p.avgMagnitude * 100) / 100,
448
- }));
449
- }
450
410
 
451
- // Add external factors if available
452
- if (externalFactors) {
453
- structuredData.externalFactors = externalFactors;
454
- }
411
+ // Add optional time series data
412
+ if (includeTimeSeries) {
413
+ structuredData.timeSeries = periodSlippages.map((p) => ({
414
+ ...p,
415
+ totalSlippage: Math.round(p.totalSlippage * 100) / 100,
416
+ avgSlippage: Math.round(p.avgSlippage * 100) / 100,
417
+ avgMagnitude: Math.round(p.avgMagnitude * 100) / 100,
418
+ }));
419
+ }
420
+
421
+ // Add external factors if available
422
+ if (externalFactors) {
423
+ structuredData.externalFactors = externalFactors;
424
+ }
455
425
 
456
- return createToolOutput(summary, structuredData);
426
+ return createToolOutput(summary, structuredData);
457
427
  } catch (error) {
458
428
  return {
459
429
  content: [
@@ -465,7 +435,7 @@ export function registerSlippageTrendsTool(
465
435
  isError: true,
466
436
  };
467
437
  }
468
- }
469
- )
438
+ },
439
+ ),
470
440
  );
471
441
  }