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
@@ -34,21 +34,10 @@ export function registerFieldTools(server: McpServer, baseDir: string): void {
34
34
  blockId: z.string().describe("Block folder name"),
35
35
  field: z
36
36
  .string()
37
- .describe(
38
- "Field name to analyze (e.g., 'openingVix', 'pl', 'rom', 'mfePercent')"
39
- ),
40
- strategy: z
41
- .string()
42
- .optional()
43
- .describe("Filter by strategy name (case-insensitive)"),
44
- startDate: z
45
- .string()
46
- .optional()
47
- .describe("Filter by start date (YYYY-MM-DD)"),
48
- endDate: z
49
- .string()
50
- .optional()
51
- .describe("Filter by end date (YYYY-MM-DD)"),
37
+ .describe("Field name to analyze (e.g., 'openingVix', 'pl', 'rom', 'mfePercent')"),
38
+ strategy: z.string().optional().describe("Filter by strategy name (case-insensitive)"),
39
+ startDate: z.string().optional().describe("Filter by start date (YYYY-MM-DD)"),
40
+ endDate: z.string().optional().describe("Filter by end date (YYYY-MM-DD)"),
52
41
  histogramBuckets: z
53
42
  .number()
54
43
  .min(3)
@@ -59,14 +48,7 @@ export function registerFieldTools(server: McpServer, baseDir: string): void {
59
48
  },
60
49
  withSyncedBlock(
61
50
  baseDir,
62
- async ({
63
- blockId,
64
- field,
65
- strategy,
66
- startDate,
67
- endDate,
68
- histogramBuckets,
69
- }) => {
51
+ async ({ blockId, field, strategy, startDate, endDate, histogramBuckets }) => {
70
52
  try {
71
53
  const block = await loadBlock(baseDir, blockId);
72
54
  let trades = block.trades;
@@ -168,7 +150,7 @@ export function registerFieldTools(server: McpServer, baseDir: string): void {
168
150
  isError: true,
169
151
  };
170
152
  }
171
- }
172
- )
153
+ },
154
+ ),
173
155
  );
174
156
  }
@@ -52,15 +52,11 @@ function computeDurationHours(trade: Trade): number | undefined {
52
52
  if (!trade.dateClosed || !trade.timeClosed) return undefined;
53
53
  try {
54
54
  const openingDate = new Date(trade.dateOpened);
55
- const [openHours, openMinutes, openSeconds] = trade.timeOpened
56
- .split(":")
57
- .map(Number);
55
+ const [openHours, openMinutes, openSeconds] = trade.timeOpened.split(":").map(Number);
58
56
  openingDate.setHours(openHours, openMinutes, openSeconds || 0, 0);
59
57
 
60
58
  const closingDate = new Date(trade.dateClosed);
61
- const [closeHours, closeMinutes, closeSeconds] = trade.timeClosed
62
- .split(":")
63
- .map(Number);
59
+ const [closeHours, closeMinutes, closeSeconds] = trade.timeClosed.split(":").map(Number);
64
60
  closingDate.setHours(closeHours, closeMinutes, closeSeconds || 0, 0);
65
61
 
66
62
  const diffMs = closingDate.getTime() - openingDate.getTime();
@@ -105,9 +101,7 @@ function getISOWeekNumber(date: Date): number {
105
101
  const dayNum = d.getDay() || 7;
106
102
  d.setDate(d.getDate() + 4 - dayNum);
107
103
  const yearStart = new Date(d.getFullYear(), 0, 1);
108
- const weekNo = Math.ceil(
109
- ((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7
110
- );
104
+ const weekNo = Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
111
105
  return weekNo;
112
106
  }
113
107
 
@@ -119,31 +113,22 @@ function getISOWeekNumber(date: Date): number {
119
113
  export function enrichTrades(trades: Trade[]): EnrichedTrade[] {
120
114
  return trades.map((trade, index) => {
121
115
  const dateOpened = new Date(trade.dateOpened);
122
- const totalFees =
123
- trade.openingCommissionsFees + (trade.closingCommissionsFees ?? 0);
116
+ const totalFees = trade.openingCommissionsFees + (trade.closingCommissionsFees ?? 0);
124
117
  const netPl = trade.pl - totalFees;
125
118
 
126
119
  // VIX changes
127
- const hasVixData =
128
- trade.openingVix != null && trade.closingVix != null;
129
- const vixChange = hasVixData
130
- ? trade.closingVix! - trade.openingVix!
131
- : undefined;
120
+ const hasVixData = trade.openingVix != null && trade.closingVix != null;
121
+ const vixChange = hasVixData ? trade.closingVix! - trade.openingVix! : undefined;
132
122
  const vixChangePct =
133
123
  hasVixData && trade.openingVix !== 0
134
124
  ? ((trade.closingVix! - trade.openingVix!) / trade.openingVix!) * 100
135
125
  : undefined;
136
126
 
137
127
  // Return metrics
138
- const rom =
139
- trade.marginReq > 0 ? (trade.pl / trade.marginReq) * 100 : undefined;
128
+ const rom = trade.marginReq > 0 ? (trade.pl / trade.marginReq) * 100 : undefined;
140
129
  const totalPremium = trade.premium * trade.numContracts;
141
- const plPct =
142
- totalPremium !== 0
143
- ? (trade.pl / Math.abs(totalPremium)) * 100
144
- : undefined;
145
- const netPlPct =
146
- totalPremium !== 0 ? (netPl / Math.abs(totalPremium)) * 100 : undefined;
130
+ const plPct = totalPremium !== 0 ? (trade.pl / Math.abs(totalPremium)) * 100 : undefined;
131
+ const netPlPct = totalPremium !== 0 ? (netPl / Math.abs(totalPremium)) * 100 : undefined;
147
132
 
148
133
  // MFE/MAE approximation from maxProfit/maxLoss (if available in trade data)
149
134
  let mfePercent: number | undefined;
@@ -158,18 +143,10 @@ export function enrichTrades(trades: Trade[]): EnrichedTrade[] {
158
143
  if (trade.maxLoss !== undefined && totalPremium !== 0) {
159
144
  maePercent = (Math.abs(trade.maxLoss) / Math.abs(totalPremium)) * 100;
160
145
  }
161
- if (
162
- mfePercent !== undefined &&
163
- trade.maxProfit &&
164
- trade.maxProfit > 0
165
- ) {
146
+ if (mfePercent !== undefined && trade.maxProfit && trade.maxProfit > 0) {
166
147
  profitCapturePercent = (trade.pl / trade.maxProfit) * 100;
167
148
  }
168
- if (
169
- mfePercent !== undefined &&
170
- maePercent !== undefined &&
171
- maePercent > 0
172
- ) {
149
+ if (mfePercent !== undefined && maePercent !== undefined && maePercent > 0) {
173
150
  excursionRatio = mfePercent / maePercent;
174
151
  }
175
152
  if (trade.maxLoss !== undefined && Math.abs(trade.maxLoss) > 0) {
@@ -214,10 +191,7 @@ export function enrichTrades(trades: Trade[]): EnrichedTrade[] {
214
191
  * Get the value of a field from an enriched trade
215
192
  * Returns null if the field doesn't exist or has no value
216
193
  */
217
- export function getTradeFieldValue(
218
- trade: EnrichedTrade,
219
- field: string
220
- ): number | null {
194
+ export function getTradeFieldValue(trade: EnrichedTrade, field: string): number | null {
221
195
  // Guard against undefined or non-string field
222
196
  if (typeof field !== "string") {
223
197
  return null;
@@ -260,7 +234,7 @@ export function evaluateOperator(
260
234
  value: number,
261
235
  operator: FilterOperator,
262
236
  compareValue: number,
263
- compareValue2?: number
237
+ compareValue2?: number,
264
238
  ): boolean {
265
239
  switch (operator) {
266
240
  case "eq":
@@ -299,7 +273,7 @@ export interface FilterCondition {
299
273
  export function applyFilterConditions(
300
274
  trades: EnrichedTrade[],
301
275
  conditions: FilterCondition[],
302
- logic: "and" | "or"
276
+ logic: "and" | "or",
303
277
  ): EnrichedTrade[] {
304
278
  if (conditions.length === 0) {
305
279
  return trades;
@@ -340,8 +314,7 @@ export function percentile(sorted: number[], p: number): number {
340
314
  export function stdDev(values: number[], avg: number): number {
341
315
  if (values.length < 2) return 0;
342
316
  const squaredDiffs = values.map((v) => Math.pow(v - avg, 2));
343
- const variance =
344
- squaredDiffs.reduce((a, b) => a + b, 0) / (values.length - 1);
317
+ const variance = squaredDiffs.reduce((a, b) => a + b, 0) / (values.length - 1);
345
318
  return Math.sqrt(variance);
346
319
  }
347
320
 
@@ -350,7 +323,7 @@ export function stdDev(values: number[], avg: number): number {
350
323
  */
351
324
  export function generateHistogram(
352
325
  values: number[],
353
- bucketCount: number = 10
326
+ bucketCount: number = 10,
354
327
  ): Array<{ min: number; max: number; count: number }> {
355
328
  if (values.length === 0) return [];
356
329
 
@@ -362,16 +335,12 @@ export function generateHistogram(
362
335
  const buckets: Array<{ min: number; max: number; count: number }> = [];
363
336
  for (let i = 0; i < bucketCount; i++) {
364
337
  const bucketMin = min + i * bucketSize;
365
- const bucketMax =
366
- i === bucketCount - 1 ? max + 0.001 : min + (i + 1) * bucketSize;
338
+ const bucketMax = i === bucketCount - 1 ? max + 0.001 : min + (i + 1) * bucketSize;
367
339
  buckets.push({ min: bucketMin, max: bucketMax, count: 0 });
368
340
  }
369
341
 
370
342
  for (const value of values) {
371
- const bucketIndex = Math.min(
372
- Math.floor((value - min) / bucketSize),
373
- bucketCount - 1
374
- );
343
+ const bucketIndex = Math.min(Math.floor((value - min) / bucketSize), bucketCount - 1);
375
344
  if (bucketIndex >= 0 && bucketIndex < buckets.length) {
376
345
  buckets[bucketIndex].count++;
377
346
  }
@@ -10,12 +10,7 @@ import { loadBlock } from "../../utils/block-loader.ts";
10
10
  import { createToolOutput } from "../../utils/output-formatter.ts";
11
11
  import { REPORT_FIELDS, pearsonCorrelation } from "@tradeblocks/lib";
12
12
  import { filterByStrategy, filterByDateRange } from "../shared/filters.ts";
13
- import {
14
- enrichTrades,
15
- getTradeFieldValue,
16
- percentile,
17
- type EnrichedTrade,
18
- } from "./helpers.ts";
13
+ import { enrichTrades, getTradeFieldValue, percentile, type EnrichedTrade } from "./helpers.ts";
19
14
  import { withSyncedBlock } from "../middleware/sync-middleware.ts";
20
15
  import { getConnection } from "../../db/connection.ts";
21
16
  import { getProfile } from "../../db/profile-schemas.ts";
@@ -23,10 +18,7 @@ import { getProfile } from "../../db/profile-schemas.ts";
23
18
  /**
24
19
  * Register predictive-related report tools
25
20
  */
26
- export function registerPredictiveTools(
27
- server: McpServer,
28
- baseDir: string
29
- ): void {
21
+ export function registerPredictiveTools(server: McpServer, baseDir: string): void {
30
22
  // Tool 5: find_predictive_fields
31
23
  server.registerTool(
32
24
  "find_predictive_fields",
@@ -35,22 +27,15 @@ export function registerPredictiveTools(
35
27
  "Identify which trade entry conditions predict profitability by calculating Pearson correlations between all numeric fields and a target field (usually P/L). Returns fields ranked by predictive strength.",
36
28
  inputSchema: z.object({
37
29
  blockId: z.string().describe("Block folder name"),
38
- strategy: z
39
- .string()
40
- .optional()
41
- .describe("Pre-filter by strategy name (case-insensitive)"),
30
+ strategy: z.string().optional().describe("Pre-filter by strategy name (case-insensitive)"),
42
31
  strategyName: z
43
32
  .string()
44
33
  .optional()
45
- .describe("Strategy profile name. When provided, auto-filters to that strategy's trades and adds profile context to output."),
46
- startDate: z
47
- .string()
48
- .optional()
49
- .describe("Pre-filter by start date (YYYY-MM-DD)"),
50
- endDate: z
51
- .string()
52
- .optional()
53
- .describe("Pre-filter by end date (YYYY-MM-DD)"),
34
+ .describe(
35
+ "Strategy profile name. When provided, auto-filters to that strategy's trades and adds profile context to output.",
36
+ ),
37
+ startDate: z.string().optional().describe("Pre-filter by start date (YYYY-MM-DD)"),
38
+ endDate: z.string().optional().describe("Pre-filter by end date (YYYY-MM-DD)"),
54
39
  targetField: z
55
40
  .string()
56
41
  .default("pl")
@@ -60,7 +45,7 @@ export function registerPredictiveTools(
60
45
  .min(10)
61
46
  .default(30)
62
47
  .describe(
63
- "Minimum trades with valid values for reliable correlation (default: 30, min: 10)"
48
+ "Minimum trades with valid values for reliable correlation (default: 30, min: 10)",
64
49
  ),
65
50
  includeCustomFields: z
66
51
  .boolean()
@@ -256,9 +241,7 @@ export function registerPredictiveTools(
256
241
  const conn = await getConnection(baseDir);
257
242
  const profile = await getProfile(conn, blockId, strategyName, baseDir);
258
243
  if (profile && profile.entryFilters.length > 0) {
259
- const profileFilterFields = new Set(
260
- profile.entryFilters.map((f) => f.field)
261
- );
244
+ const profileFilterFields = new Set(profile.entryFilters.map((f) => f.field));
262
245
  const alignedFields = rankedFields
263
246
  .filter((rf) => profileFilterFields.has(rf.field))
264
247
  .map((rf) => ({
@@ -292,8 +275,8 @@ export function registerPredictiveTools(
292
275
  isError: true,
293
276
  };
294
277
  }
295
- }
296
- )
278
+ },
279
+ ),
297
280
  );
298
281
 
299
282
  // Tool 6: filter_curve
@@ -306,39 +289,28 @@ export function registerPredictiveTools(
306
289
  blockId: z.string().describe("Block folder name"),
307
290
  field: z
308
291
  .string()
309
- .describe(
310
- "Field to sweep thresholds on (e.g., 'openingVix', 'durationHours')"
311
- ),
292
+ .describe("Field to sweep thresholds on (e.g., 'openingVix', 'durationHours')"),
312
293
  mode: z
313
294
  .enum(["lt", "gt", "both"])
314
295
  .default("both")
315
296
  .describe(
316
- "Direction of filter: 'lt' (field < threshold), 'gt' (field > threshold), 'both' (show both directions)"
297
+ "Direction of filter: 'lt' (field < threshold), 'gt' (field > threshold), 'both' (show both directions)",
317
298
  ),
318
299
  thresholds: z
319
300
  .array(z.number())
320
301
  .optional()
321
302
  .describe(
322
- "Custom threshold values to test. If omitted, auto-generates from field percentiles."
303
+ "Custom threshold values to test. If omitted, auto-generates from field percentiles.",
323
304
  ),
324
305
  percentileSteps: z
325
306
  .array(z.number())
326
307
  .default([5, 10, 25, 50, 75, 90, 95])
327
308
  .describe(
328
- "Percentiles to use for auto-generated thresholds (default: [5, 10, 25, 50, 75, 90, 95])"
309
+ "Percentiles to use for auto-generated thresholds (default: [5, 10, 25, 50, 75, 90, 95])",
329
310
  ),
330
- strategy: z
331
- .string()
332
- .optional()
333
- .describe("Pre-filter by strategy name (case-insensitive)"),
334
- startDate: z
335
- .string()
336
- .optional()
337
- .describe("Pre-filter by start date (YYYY-MM-DD)"),
338
- endDate: z
339
- .string()
340
- .optional()
341
- .describe("Pre-filter by end date (YYYY-MM-DD)"),
311
+ strategy: z.string().optional().describe("Pre-filter by strategy name (case-insensitive)"),
312
+ startDate: z.string().optional().describe("Pre-filter by start date (YYYY-MM-DD)"),
313
+ endDate: z.string().optional().describe("Pre-filter by end date (YYYY-MM-DD)"),
342
314
  }),
343
315
  },
344
316
  withSyncedBlock(
@@ -402,9 +374,7 @@ export function registerPredictiveTools(
402
374
  const baselinePls = tradesWithField.map((t) => t.pl);
403
375
  const baselineTotalPl = baselinePls.reduce((a, b) => a + b, 0);
404
376
  const baselineAvgPl = baselineTotalPl / tradesWithField.length;
405
- const baselineWinners = tradesWithField.filter(
406
- (t) => t.pl > 0
407
- ).length;
377
+ const baselineWinners = tradesWithField.filter((t) => t.pl > 0).length;
408
378
  const baselineWinRate = baselineWinners / tradesWithField.length;
409
379
 
410
380
  const baseline = {
@@ -469,8 +439,7 @@ export function registerPredictiveTools(
469
439
  const avgPl = totalPl / filteredTrades.length;
470
440
  const winners = filteredTrades.filter((t) => t.pl > 0).length;
471
441
  const winRate = winners / filteredTrades.length;
472
- const percentOfTrades =
473
- (filteredTrades.length / tradesWithField.length) * 100;
442
+ const percentOfTrades = (filteredTrades.length / tradesWithField.length) * 100;
474
443
 
475
444
  const result: ReturnType<typeof calculateMetrics> = {
476
445
  count: filteredTrades.length,
@@ -478,16 +447,12 @@ export function registerPredictiveTools(
478
447
  winRate: Math.round(winRate * 10000) / 10000,
479
448
  avgPl: Math.round(avgPl * 100) / 100,
480
449
  totalPl: Math.round(totalPl * 100) / 100,
481
- winRateDelta:
482
- Math.round((winRate - baseline.winRate) * 10000) / 10000,
450
+ winRateDelta: Math.round((winRate - baseline.winRate) * 10000) / 10000,
483
451
  avgPlDelta: Math.round((avgPl - baseline.avgPl) * 100) / 100,
484
452
  };
485
453
 
486
454
  // Add warning for small sample sizes
487
- if (
488
- filteredTrades.length < MIN_SAMPLE_SIZE &&
489
- filteredTrades.length > 0
490
- ) {
455
+ if (filteredTrades.length < MIN_SAMPLE_SIZE && filteredTrades.length > 0) {
491
456
  result.lowSampleWarning = `${filteredTrades.length} trades may be insufficient for reliable statistics (recommend >= ${MIN_SAMPLE_SIZE})`;
492
457
  }
493
458
 
@@ -541,11 +506,7 @@ export function registerPredictiveTools(
541
506
  // Check lt direction
542
507
  if (result.lt) {
543
508
  const { winRateDelta, avgPlDelta, percentOfTrades } = result.lt;
544
- if (
545
- winRateDelta > 0 &&
546
- avgPlDelta > 0 &&
547
- percentOfTrades >= 20
548
- ) {
509
+ if (winRateDelta > 0 && avgPlDelta > 0 && percentOfTrades >= 20) {
549
510
  const score = winRateDelta * avgPlDelta;
550
511
  sweetSpots.push({
551
512
  threshold: result.threshold,
@@ -561,11 +522,7 @@ export function registerPredictiveTools(
561
522
  // Check gt direction
562
523
  if (result.gt) {
563
524
  const { winRateDelta, avgPlDelta, percentOfTrades } = result.gt;
564
- if (
565
- winRateDelta > 0 &&
566
- avgPlDelta > 0 &&
567
- percentOfTrades >= 20
568
- ) {
525
+ if (winRateDelta > 0 && avgPlDelta > 0 && percentOfTrades >= 20) {
569
526
  const score = winRateDelta * avgPlDelta;
570
527
  sweetSpots.push({
571
528
  threshold: result.threshold,
@@ -618,7 +575,7 @@ export function registerPredictiveTools(
618
575
  isError: true,
619
576
  };
620
577
  }
621
- }
622
- )
578
+ },
579
+ ),
623
580
  );
624
581
  }