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,47 +7,28 @@
7
7
  import { z } from "zod";
8
8
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
9
  import { loadBlock } from "../../utils/block-loader.ts";
10
- import {
11
- createToolOutput,
12
- formatCurrency,
13
- } from "../../utils/output-formatter.ts";
10
+ import { createToolOutput, formatCurrency } from "../../utils/output-formatter.ts";
14
11
  import { PortfolioStatsCalculator } from "@tradeblocks/lib";
15
12
  import { resolveTradeTicker } from "../../utils/ticker.ts";
16
- import {
17
- filterByDateRange,
18
- filterDailyLogsByDateRange,
19
- } from "../shared/filters.ts";
20
- import {
21
- withSyncedBlock,
22
- withSyncedBlocks,
23
- } from "../middleware/sync-middleware.ts";
13
+ import { filterByDateRange, filterDailyLogsByDateRange } from "../shared/filters.ts";
14
+ import { withSyncedBlock, withSyncedBlocks } from "../middleware/sync-middleware.ts";
24
15
 
25
16
  /**
26
17
  * Register comparison block tools
27
18
  */
28
- export function registerComparisonBlockTools(
29
- server: McpServer,
30
- baseDir: string
31
- ): void {
19
+ export function registerComparisonBlockTools(server: McpServer, baseDir: string): void {
32
20
  const calculator = new PortfolioStatsCalculator();
33
21
 
34
22
  // Tool 4: get_strategy_comparison
35
23
  server.registerTool(
36
24
  "get_strategy_comparison",
37
25
  {
38
- description:
39
- "Compare all strategies within a block with optional filtering and sorting",
26
+ description: "Compare all strategies within a block with optional filtering and sorting",
40
27
  inputSchema: z.object({
41
28
  blockId: z.string().describe("Block folder name"),
42
- startDate: z
43
- .string()
44
- .optional()
45
- .describe("Start date filter (YYYY-MM-DD)"),
29
+ startDate: z.string().optional().describe("Start date filter (YYYY-MM-DD)"),
46
30
  endDate: z.string().optional().describe("End date filter (YYYY-MM-DD)"),
47
- tickerFilter: z
48
- .string()
49
- .optional()
50
- .describe("Filter trades by underlying ticker symbol"),
31
+ tickerFilter: z.string().optional().describe("Filter trades by underlying ticker symbol"),
51
32
  minTrades: z
52
33
  .number()
53
34
  .min(1)
@@ -56,18 +37,12 @@ export function registerComparisonBlockTools(
56
37
  sortBy: z
57
38
  .enum(["netPl", "pl", "winRate", "trades", "profitFactor", "name"])
58
39
  .default("netPl")
59
- .describe(
60
- "Sort strategies by metric (default: netPl). 'pl' is an alias for 'netPl'."
61
- ),
40
+ .describe("Sort strategies by metric (default: netPl). 'pl' is an alias for 'netPl'."),
62
41
  sortOrder: z
63
42
  .enum(["asc", "desc"])
64
43
  .default("desc")
65
44
  .describe("Sort direction (default: desc for highest first)"),
66
- limit: z
67
- .number()
68
- .min(1)
69
- .optional()
70
- .describe("Limit number of strategies shown"),
45
+ limit: z.number().min(1).optional().describe("Limit number of strategies shown"),
71
46
  }),
72
47
  },
73
48
  withSyncedBlock(
@@ -84,93 +59,87 @@ export function registerComparisonBlockTools(
84
59
  }) => {
85
60
  try {
86
61
  const block = await loadBlock(baseDir, blockId);
87
- let trades = block.trades;
88
-
89
- // Apply date filter
90
- trades = filterByDateRange(trades, startDate, endDate);
62
+ let trades = block.trades;
91
63
 
92
- // Apply ticker filter (supports both explicit ticker columns and legs-derived symbols)
93
- if (tickerFilter) {
94
- const tickerLower = tickerFilter.toLowerCase();
95
- trades = trades.filter(
96
- (t) => resolveTradeTicker(t).toLowerCase() === tickerLower
97
- );
98
- }
64
+ // Apply date filter
65
+ trades = filterByDateRange(trades, startDate, endDate);
99
66
 
100
- if (trades.length === 0) {
101
- return {
102
- content: [
103
- { type: "text", text: "No trades found matching the filters." },
104
- ],
105
- };
106
- }
67
+ // Apply ticker filter (supports both explicit ticker columns and legs-derived symbols)
68
+ if (tickerFilter) {
69
+ const tickerLower = tickerFilter.toLowerCase();
70
+ trades = trades.filter((t) => resolveTradeTicker(t).toLowerCase() === tickerLower);
71
+ }
107
72
 
108
- // Calculate stats per strategy - always use trade-based calculations
109
- // because daily logs represent full portfolio
110
- const strategyStats = calculator.calculateStrategyStats(trades);
73
+ if (trades.length === 0) {
74
+ return {
75
+ content: [{ type: "text", text: "No trades found matching the filters." }],
76
+ };
77
+ }
111
78
 
112
- // Convert to array for filtering and sorting
113
- let strategies = Object.values(strategyStats);
79
+ // Calculate stats per strategy - always use trade-based calculations
80
+ // because daily logs represent full portfolio
81
+ const strategyStats = calculator.calculateStrategyStats(trades);
114
82
 
115
- // Apply minTrades filter
116
- if (minTrades !== undefined) {
117
- strategies = strategies.filter((s) => s.tradeCount >= minTrades);
118
- }
83
+ // Convert to array for filtering and sorting
84
+ let strategies = Object.values(strategyStats);
119
85
 
120
- // Apply sorting
121
- const multiplier = sortOrder === "asc" ? 1 : -1;
122
- strategies.sort((a, b) => {
123
- switch (sortBy) {
124
- case "winRate":
125
- return (a.winRate - b.winRate) * multiplier;
126
- case "trades":
127
- return (a.tradeCount - b.tradeCount) * multiplier;
128
- case "profitFactor":
129
- return (
130
- ((a.profitFactor ?? 0) - (b.profitFactor ?? 0)) * multiplier
131
- );
132
- case "name":
133
- return a.strategyName.localeCompare(b.strategyName) * multiplier;
134
- case "netPl":
135
- case "pl":
136
- default:
137
- return (a.totalPl - b.totalPl) * multiplier;
86
+ // Apply minTrades filter
87
+ if (minTrades !== undefined) {
88
+ strategies = strategies.filter((s) => s.tradeCount >= minTrades);
138
89
  }
139
- });
140
-
141
- // Apply limit
142
- const totalBeforeLimit = strategies.length;
143
- if (limit !== undefined && limit < strategies.length) {
144
- strategies = strategies.slice(0, limit);
145
- }
146
90
 
147
- // Brief summary for user display
148
- const summary = `Strategy Comparison: ${blockId} | ${strategies.length} strategies${totalBeforeLimit > strategies.length ? ` (of ${totalBeforeLimit})` : ""} | Sorted by ${sortBy}`;
91
+ // Apply sorting
92
+ const multiplier = sortOrder === "asc" ? 1 : -1;
93
+ strategies.sort((a, b) => {
94
+ switch (sortBy) {
95
+ case "winRate":
96
+ return (a.winRate - b.winRate) * multiplier;
97
+ case "trades":
98
+ return (a.tradeCount - b.tradeCount) * multiplier;
99
+ case "profitFactor":
100
+ return ((a.profitFactor ?? 0) - (b.profitFactor ?? 0)) * multiplier;
101
+ case "name":
102
+ return a.strategyName.localeCompare(b.strategyName) * multiplier;
103
+ case "netPl":
104
+ case "pl":
105
+ default:
106
+ return (a.totalPl - b.totalPl) * multiplier;
107
+ }
108
+ });
109
+
110
+ // Apply limit
111
+ const totalBeforeLimit = strategies.length;
112
+ if (limit !== undefined && limit < strategies.length) {
113
+ strategies = strategies.slice(0, limit);
114
+ }
149
115
 
150
- // Build structured data for Claude reasoning
151
- const structuredData = {
152
- blockId,
153
- options: {
154
- startDate: startDate ?? null,
155
- endDate: endDate ?? null,
156
- tickerFilter: tickerFilter ?? null,
157
- minTrades: minTrades ?? null,
158
- sortBy,
159
- sortOrder,
160
- limit: limit ?? null,
161
- },
162
- strategies: strategies.map((s) => ({
163
- name: s.strategyName,
164
- trades: s.tradeCount,
165
- winRate: s.winRate,
166
- netPl: s.totalPl,
167
- avgWin: s.avgWin,
168
- avgLoss: s.avgLoss,
169
- profitFactor: s.profitFactor,
170
- })),
171
- totalStrategies: totalBeforeLimit,
172
- count: strategies.length,
173
- };
116
+ // Brief summary for user display
117
+ const summary = `Strategy Comparison: ${blockId} | ${strategies.length} strategies${totalBeforeLimit > strategies.length ? ` (of ${totalBeforeLimit})` : ""} | Sorted by ${sortBy}`;
118
+
119
+ // Build structured data for Claude reasoning
120
+ const structuredData = {
121
+ blockId,
122
+ options: {
123
+ startDate: startDate ?? null,
124
+ endDate: endDate ?? null,
125
+ tickerFilter: tickerFilter ?? null,
126
+ minTrades: minTrades ?? null,
127
+ sortBy,
128
+ sortOrder,
129
+ limit: limit ?? null,
130
+ },
131
+ strategies: strategies.map((s) => ({
132
+ name: s.strategyName,
133
+ trades: s.tradeCount,
134
+ winRate: s.winRate,
135
+ netPl: s.totalPl,
136
+ avgWin: s.avgWin,
137
+ avgLoss: s.avgLoss,
138
+ profitFactor: s.profitFactor,
139
+ })),
140
+ totalStrategies: totalBeforeLimit,
141
+ count: strategies.length,
142
+ };
174
143
 
175
144
  return createToolOutput(summary, structuredData);
176
145
  } catch (error) {
@@ -184,8 +153,8 @@ export function registerComparisonBlockTools(
184
153
  isError: true,
185
154
  };
186
155
  }
187
- }
188
- )
156
+ },
157
+ ),
189
158
  );
190
159
 
191
160
  // Tool 5: compare_blocks
@@ -211,11 +180,11 @@ export function registerComparisonBlockTools(
211
180
  "maxDrawdown",
212
181
  "profitFactor",
213
182
  "calmarRatio",
214
- ])
183
+ ]),
215
184
  )
216
185
  .optional()
217
186
  .describe(
218
- "Specific metrics to include in comparison (default: all). Use to focus on key metrics."
187
+ "Specific metrics to include in comparison (default: all). Use to focus on key metrics.",
219
188
  ),
220
189
  sortBy: z
221
190
  .enum([
@@ -231,28 +200,20 @@ export function registerComparisonBlockTools(
231
200
  ])
232
201
  .default("name")
233
202
  .describe("Sort blocks by metric (default: name)"),
234
- sortOrder: z
235
- .enum(["asc", "desc"])
236
- .default("asc")
237
- .describe("Sort direction (default: asc)"),
203
+ sortOrder: z.enum(["asc", "desc"]).default("asc").describe("Sort direction (default: asc)"),
238
204
  }),
239
205
  },
240
- withSyncedBlocks(
241
- baseDir,
242
- async ({ blockIds, metrics, sortBy, sortOrder }) => {
243
- try {
244
- const blockStats: Array<{
245
- blockId: string;
246
- stats: ReturnType<typeof calculator.calculatePortfolioStats>;
247
- }> = [];
248
-
249
- for (const blockId of blockIds!) {
206
+ withSyncedBlocks(baseDir, async ({ blockIds, metrics, sortBy, sortOrder }) => {
207
+ try {
208
+ const blockStats: Array<{
209
+ blockId: string;
210
+ stats: ReturnType<typeof calculator.calculatePortfolioStats>;
211
+ }> = [];
212
+
213
+ for (const blockId of blockIds!) {
250
214
  try {
251
215
  const block = await loadBlock(baseDir, blockId);
252
- const stats = calculator.calculatePortfolioStats(
253
- block.trades,
254
- block.dailyLogs
255
- );
216
+ const stats = calculator.calculatePortfolioStats(block.trades, block.dailyLogs);
256
217
  blockStats.push({ blockId, stats });
257
218
  } catch (error) {
258
219
  // Include error info in output but continue with other blocks
@@ -279,36 +240,19 @@ export function registerComparisonBlockTools(
279
240
  case "totalTrades":
280
241
  return (a.stats.totalTrades - b.stats.totalTrades) * multiplier;
281
242
  case "winRate":
282
- return (
283
- ((a.stats.winRate ?? 0) - (b.stats.winRate ?? 0)) * multiplier
284
- );
243
+ return ((a.stats.winRate ?? 0) - (b.stats.winRate ?? 0)) * multiplier;
285
244
  case "netPl":
286
245
  return ((a.stats.netPl ?? 0) - (b.stats.netPl ?? 0)) * multiplier;
287
246
  case "sharpeRatio":
288
- return (
289
- ((a.stats.sharpeRatio ?? 0) - (b.stats.sharpeRatio ?? 0)) *
290
- multiplier
291
- );
247
+ return ((a.stats.sharpeRatio ?? 0) - (b.stats.sharpeRatio ?? 0)) * multiplier;
292
248
  case "sortinoRatio":
293
- return (
294
- ((a.stats.sortinoRatio ?? 0) - (b.stats.sortinoRatio ?? 0)) *
295
- multiplier
296
- );
249
+ return ((a.stats.sortinoRatio ?? 0) - (b.stats.sortinoRatio ?? 0)) * multiplier;
297
250
  case "maxDrawdown":
298
- return (
299
- ((a.stats.maxDrawdown ?? 0) - (b.stats.maxDrawdown ?? 0)) *
300
- multiplier
301
- );
251
+ return ((a.stats.maxDrawdown ?? 0) - (b.stats.maxDrawdown ?? 0)) * multiplier;
302
252
  case "profitFactor":
303
- return (
304
- ((a.stats.profitFactor ?? 0) - (b.stats.profitFactor ?? 0)) *
305
- multiplier
306
- );
253
+ return ((a.stats.profitFactor ?? 0) - (b.stats.profitFactor ?? 0)) * multiplier;
307
254
  case "calmarRatio":
308
- return (
309
- ((a.stats.calmarRatio ?? 0) - (b.stats.calmarRatio ?? 0)) *
310
- multiplier
311
- );
255
+ return ((a.stats.calmarRatio ?? 0) - (b.stats.calmarRatio ?? 0)) * multiplier;
312
256
  case "name":
313
257
  default:
314
258
  return a.blockId.localeCompare(b.blockId) * multiplier;
@@ -346,21 +290,15 @@ export function registerComparisonBlockTools(
346
290
  },
347
291
  comparisons: blockStats.map(({ blockId, stats }) => {
348
292
  const filteredStats: Record<string, number | null> = {};
349
- if (requestedMetrics.totalTrades)
350
- filteredStats.totalTrades = stats.totalTrades;
351
- if (requestedMetrics.winRate)
352
- filteredStats.winRate = stats.winRate;
293
+ if (requestedMetrics.totalTrades) filteredStats.totalTrades = stats.totalTrades;
294
+ if (requestedMetrics.winRate) filteredStats.winRate = stats.winRate;
353
295
  if (requestedMetrics.netPl) filteredStats.netPl = stats.netPl;
354
- if (requestedMetrics.sharpeRatio)
355
- filteredStats.sharpeRatio = stats.sharpeRatio ?? null;
296
+ if (requestedMetrics.sharpeRatio) filteredStats.sharpeRatio = stats.sharpeRatio ?? null;
356
297
  if (requestedMetrics.sortinoRatio)
357
298
  filteredStats.sortinoRatio = stats.sortinoRatio ?? null;
358
- if (requestedMetrics.maxDrawdown)
359
- filteredStats.maxDrawdown = stats.maxDrawdown;
360
- if (requestedMetrics.profitFactor)
361
- filteredStats.profitFactor = stats.profitFactor;
362
- if (requestedMetrics.calmarRatio)
363
- filteredStats.calmarRatio = stats.calmarRatio ?? null;
299
+ if (requestedMetrics.maxDrawdown) filteredStats.maxDrawdown = stats.maxDrawdown;
300
+ if (requestedMetrics.profitFactor) filteredStats.profitFactor = stats.profitFactor;
301
+ if (requestedMetrics.calmarRatio) filteredStats.calmarRatio = stats.calmarRatio ?? null;
364
302
  return {
365
303
  blockId,
366
304
  stats: filteredStats,
@@ -369,20 +307,19 @@ export function registerComparisonBlockTools(
369
307
  failedBlocks: failedIds,
370
308
  };
371
309
 
372
- return createToolOutput(summary, structuredData);
373
- } catch (error) {
374
- return {
375
- content: [
376
- {
377
- type: "text",
378
- text: `Error comparing blocks: ${(error as Error).message}`,
379
- },
380
- ],
381
- isError: true,
382
- };
383
- }
310
+ return createToolOutput(summary, structuredData);
311
+ } catch (error) {
312
+ return {
313
+ content: [
314
+ {
315
+ type: "text",
316
+ text: `Error comparing blocks: ${(error as Error).message}`,
317
+ },
318
+ ],
319
+ isError: true,
320
+ };
384
321
  }
385
- )
322
+ }),
386
323
  );
387
324
 
388
325
  // Tool 6: block_diff
@@ -392,18 +329,10 @@ export function registerComparisonBlockTools(
392
329
  description:
393
330
  "Compare two blocks with strategy overlap analysis and P/L attribution. Shows which strategies are shared vs unique between blocks, and calculates performance deltas for shared strategies.",
394
331
  inputSchema: z.object({
395
- blockIdA: z
396
- .string()
397
- .describe("First block (baseline) for comparison"),
332
+ blockIdA: z.string().describe("First block (baseline) for comparison"),
398
333
  blockIdB: z.string().describe("Second block (comparison target)"),
399
- startDate: z
400
- .string()
401
- .optional()
402
- .describe("Optional start date filter (YYYY-MM-DD)"),
403
- endDate: z
404
- .string()
405
- .optional()
406
- .describe("Optional end date filter (YYYY-MM-DD)"),
334
+ startDate: z.string().optional().describe("Optional start date filter (YYYY-MM-DD)"),
335
+ endDate: z.string().optional().describe("Optional end date filter (YYYY-MM-DD)"),
407
336
  metricsToCompare: z
408
337
  .array(
409
338
  z.enum([
@@ -414,11 +343,11 @@ export function registerComparisonBlockTools(
414
343
  "profitFactor",
415
344
  "sharpeRatio",
416
345
  "maxDrawdown",
417
- ])
346
+ ]),
418
347
  )
419
348
  .optional()
420
349
  .describe(
421
- "Specific metrics to include in comparison (default: all). 'netPl' and 'pl' are equivalent. Use to focus output."
350
+ "Specific metrics to include in comparison (default: all). 'netPl' and 'pl' are equivalent. Use to focus output.",
422
351
  ),
423
352
  }),
424
353
  },
@@ -432,189 +361,177 @@ export function registerComparisonBlockTools(
432
361
  loadBlock(baseDir, blockIdB!),
433
362
  ]);
434
363
 
435
- // Apply date filters
436
- const tradesA = filterByDateRange(blockA.trades, startDate, endDate);
437
- const tradesB = filterByDateRange(blockB.trades, startDate, endDate);
364
+ // Apply date filters
365
+ const tradesA = filterByDateRange(blockA.trades, startDate, endDate);
366
+ const tradesB = filterByDateRange(blockB.trades, startDate, endDate);
367
+
368
+ // Extract unique strategy names from each block
369
+ const strategiesA = new Set(tradesA.map((t) => t.strategy));
370
+ const strategiesB = new Set(tradesB.map((t) => t.strategy));
371
+
372
+ // Categorize strategies
373
+ const shared: string[] = [];
374
+ const uniqueToA: string[] = [];
375
+ const uniqueToB: string[] = [];
376
+
377
+ for (const strategy of strategiesA) {
378
+ if (strategiesB.has(strategy)) {
379
+ shared.push(strategy);
380
+ } else {
381
+ uniqueToA.push(strategy);
382
+ }
383
+ }
438
384
 
439
- // Extract unique strategy names from each block
440
- const strategiesA = new Set(tradesA.map((t) => t.strategy));
441
- const strategiesB = new Set(tradesB.map((t) => t.strategy));
385
+ for (const strategy of strategiesB) {
386
+ if (!strategiesA.has(strategy)) {
387
+ uniqueToB.push(strategy);
388
+ }
389
+ }
442
390
 
443
- // Categorize strategies
444
- const shared: string[] = [];
445
- const uniqueToA: string[] = [];
446
- const uniqueToB: string[] = [];
391
+ // Sort for consistent output
392
+ shared.sort();
393
+ uniqueToA.sort();
394
+ uniqueToB.sort();
447
395
 
448
- for (const strategy of strategiesA) {
449
- if (strategiesB.has(strategy)) {
450
- shared.push(strategy);
451
- } else {
452
- uniqueToA.push(strategy);
453
- }
454
- }
396
+ // Calculate overlap percentage
397
+ const totalUniqueStrategies = new Set([...strategiesA, ...strategiesB]).size;
398
+ const overlapPercent =
399
+ totalUniqueStrategies > 0 ? (shared.length / totalUniqueStrategies) * 100 : 0;
455
400
 
456
- for (const strategy of strategiesB) {
457
- if (!strategiesA.has(strategy)) {
458
- uniqueToB.push(strategy);
459
- }
460
- }
401
+ // Calculate per-strategy stats using trade-based calculations only
402
+ const statsA = calculator.calculateStrategyStats(tradesA);
403
+ const statsB = calculator.calculateStrategyStats(tradesB);
461
404
 
462
- // Sort for consistent output
463
- shared.sort();
464
- uniqueToA.sort();
465
- uniqueToB.sort();
466
-
467
- // Calculate overlap percentage
468
- const totalUniqueStrategies = new Set([
469
- ...strategiesA,
470
- ...strategiesB,
471
- ]).size;
472
- const overlapPercent =
473
- totalUniqueStrategies > 0
474
- ? (shared.length / totalUniqueStrategies) * 100
475
- : 0;
476
-
477
- // Calculate per-strategy stats using trade-based calculations only
478
- const statsA = calculator.calculateStrategyStats(tradesA);
479
- const statsB = calculator.calculateStrategyStats(tradesB);
480
-
481
- // Helper to build strategy comparison entry
482
- const buildStrategyEntry = (strategy: string) => {
483
- const blockAStats = statsA[strategy];
484
- const blockBStats = statsB[strategy];
485
-
486
- const entryA = blockAStats
487
- ? {
488
- trades: blockAStats.tradeCount,
489
- netPl: blockAStats.totalPl,
490
- winRate: blockAStats.winRate,
491
- profitFactor: blockAStats.profitFactor,
492
- }
493
- : null;
494
-
495
- const entryB = blockBStats
496
- ? {
497
- trades: blockBStats.tradeCount,
498
- netPl: blockBStats.totalPl,
499
- winRate: blockBStats.winRate,
500
- profitFactor: blockBStats.profitFactor,
501
- }
502
- : null;
503
-
504
- // Calculate delta only for shared strategies
505
- const delta =
506
- entryA && entryB
405
+ // Helper to build strategy comparison entry
406
+ const buildStrategyEntry = (strategy: string) => {
407
+ const blockAStats = statsA[strategy];
408
+ const blockBStats = statsB[strategy];
409
+
410
+ const entryA = blockAStats
507
411
  ? {
508
- trades: entryB.trades - entryA.trades,
509
- netPl: entryB.netPl - entryA.netPl,
510
- winRate: entryB.winRate - entryA.winRate,
412
+ trades: blockAStats.tradeCount,
413
+ netPl: blockAStats.totalPl,
414
+ winRate: blockAStats.winRate,
415
+ profitFactor: blockAStats.profitFactor,
511
416
  }
512
417
  : null;
513
418
 
514
- return {
515
- strategy,
516
- blockA: entryA,
517
- blockB: entryB,
518
- delta,
419
+ const entryB = blockBStats
420
+ ? {
421
+ trades: blockBStats.tradeCount,
422
+ netPl: blockBStats.totalPl,
423
+ winRate: blockBStats.winRate,
424
+ profitFactor: blockBStats.profitFactor,
425
+ }
426
+ : null;
427
+
428
+ // Calculate delta only for shared strategies
429
+ const delta =
430
+ entryA && entryB
431
+ ? {
432
+ trades: entryB.trades - entryA.trades,
433
+ netPl: entryB.netPl - entryA.netPl,
434
+ winRate: entryB.winRate - entryA.winRate,
435
+ }
436
+ : null;
437
+
438
+ return {
439
+ strategy,
440
+ blockA: entryA,
441
+ blockB: entryB,
442
+ delta,
443
+ };
519
444
  };
520
- };
521
445
 
522
- // Build per-strategy comparison for all strategies
523
- const perStrategyComparison = [
524
- ...shared.map(buildStrategyEntry),
525
- ...uniqueToA.map(buildStrategyEntry),
526
- ...uniqueToB.map(buildStrategyEntry),
527
- ];
528
-
529
- // Calculate portfolio-level totals
530
- // Use daily logs for portfolio-level stats when available (consistent with get_statistics)
531
- // Per-strategy stats remain trade-based since daily logs are portfolio-wide
532
- const dailyLogsA = blockA.dailyLogs && blockA.dailyLogs.length > 0
533
- ? filterDailyLogsByDateRange(blockA.dailyLogs, startDate, endDate)
534
- : undefined;
535
- const dailyLogsB = blockB.dailyLogs && blockB.dailyLogs.length > 0
536
- ? filterDailyLogsByDateRange(blockB.dailyLogs, startDate, endDate)
537
- : undefined;
538
- const portfolioStatsA = calculator.calculatePortfolioStats(
539
- tradesA,
540
- dailyLogsA && dailyLogsA.length > 0 ? dailyLogsA : undefined,
541
- );
542
- const portfolioStatsB = calculator.calculatePortfolioStats(
543
- tradesB,
544
- dailyLogsB && dailyLogsB.length > 0 ? dailyLogsB : undefined,
545
- );
546
-
547
- // Build portfolio totals with all or filtered metrics
548
- const allMetrics =
549
- !metricsToCompare || metricsToCompare.length === 0;
550
- const includeMetric = (m: string) =>
551
- allMetrics ||
552
- metricsToCompare?.includes(
553
- m as (typeof metricsToCompare)[number]
446
+ // Build per-strategy comparison for all strategies
447
+ const perStrategyComparison = [
448
+ ...shared.map(buildStrategyEntry),
449
+ ...uniqueToA.map(buildStrategyEntry),
450
+ ...uniqueToB.map(buildStrategyEntry),
451
+ ];
452
+
453
+ // Calculate portfolio-level totals
454
+ // Use daily logs for portfolio-level stats when available (consistent with get_statistics)
455
+ // Per-strategy stats remain trade-based since daily logs are portfolio-wide
456
+ const dailyLogsA =
457
+ blockA.dailyLogs && blockA.dailyLogs.length > 0
458
+ ? filterDailyLogsByDateRange(blockA.dailyLogs, startDate, endDate)
459
+ : undefined;
460
+ const dailyLogsB =
461
+ blockB.dailyLogs && blockB.dailyLogs.length > 0
462
+ ? filterDailyLogsByDateRange(blockB.dailyLogs, startDate, endDate)
463
+ : undefined;
464
+ const portfolioStatsA = calculator.calculatePortfolioStats(
465
+ tradesA,
466
+ dailyLogsA && dailyLogsA.length > 0 ? dailyLogsA : undefined,
467
+ );
468
+ const portfolioStatsB = calculator.calculatePortfolioStats(
469
+ tradesB,
470
+ dailyLogsB && dailyLogsB.length > 0 ? dailyLogsB : undefined,
554
471
  );
555
472
 
556
- const buildPortfolioEntry = (
557
- stats: ReturnType<typeof calculator.calculatePortfolioStats>
558
- ) => {
559
- const entry: Record<string, number | null> = {};
560
- if (includeMetric("trades")) entry.totalTrades = stats.totalTrades;
561
- if (includeMetric("pl") || includeMetric("netPl"))
562
- entry.netPl = stats.netPl;
563
- if (includeMetric("winRate")) entry.winRate = stats.winRate;
564
- if (includeMetric("profitFactor"))
565
- entry.profitFactor = stats.profitFactor;
566
- if (includeMetric("sharpeRatio"))
567
- entry.sharpeRatio = stats.sharpeRatio ?? null;
568
- if (includeMetric("maxDrawdown"))
569
- entry.maxDrawdown = stats.maxDrawdown;
570
- return entry;
571
- };
572
-
573
- const portfolioA = buildPortfolioEntry(portfolioStatsA);
574
- const portfolioB = buildPortfolioEntry(portfolioStatsB);
473
+ // Build portfolio totals with all or filtered metrics
474
+ const allMetrics = !metricsToCompare || metricsToCompare.length === 0;
475
+ const includeMetric = (m: string) =>
476
+ allMetrics || metricsToCompare?.includes(m as (typeof metricsToCompare)[number]);
477
+
478
+ const buildPortfolioEntry = (
479
+ stats: ReturnType<typeof calculator.calculatePortfolioStats>,
480
+ ) => {
481
+ const entry: Record<string, number | null> = {};
482
+ if (includeMetric("trades")) entry.totalTrades = stats.totalTrades;
483
+ if (includeMetric("pl") || includeMetric("netPl")) entry.netPl = stats.netPl;
484
+ if (includeMetric("winRate")) entry.winRate = stats.winRate;
485
+ if (includeMetric("profitFactor")) entry.profitFactor = stats.profitFactor;
486
+ if (includeMetric("sharpeRatio")) entry.sharpeRatio = stats.sharpeRatio ?? null;
487
+ if (includeMetric("maxDrawdown")) entry.maxDrawdown = stats.maxDrawdown;
488
+ return entry;
489
+ };
575
490
 
576
- // Calculate deltas for portfolio totals
577
- const portfolioDelta: Record<string, number | null> = {};
578
- for (const key of Object.keys(portfolioA)) {
579
- const valA = portfolioA[key];
580
- const valB = portfolioB[key];
581
- portfolioDelta[key] =
582
- valA !== null && valB !== null ? valB - valA : null;
583
- }
491
+ const portfolioA = buildPortfolioEntry(portfolioStatsA);
492
+ const portfolioB = buildPortfolioEntry(portfolioStatsB);
584
493
 
585
- // Brief summary for user display
586
- const summary = `Block Diff: ${blockIdA} vs ${blockIdB} | ${shared.length} shared, ${uniqueToA.length} unique to A, ${uniqueToB.length} unique to B | P/L delta: ${formatCurrency(portfolioStatsB.netPl - portfolioStatsA.netPl)}`;
494
+ // Calculate deltas for portfolio totals
495
+ const portfolioDelta: Record<string, number | null> = {};
496
+ for (const key of Object.keys(portfolioA)) {
497
+ const valA = portfolioA[key];
498
+ const valB = portfolioB[key];
499
+ portfolioDelta[key] = valA !== null && valB !== null ? valB - valA : null;
500
+ }
587
501
 
588
- // Build structured output
589
- const structuredData = {
590
- blockA: {
591
- id: blockIdA,
592
- tradeCount: tradesA.length,
593
- strategies: Array.from(strategiesA).sort(),
594
- },
595
- blockB: {
596
- id: blockIdB,
597
- tradeCount: tradesB.length,
598
- strategies: Array.from(strategiesB).sort(),
599
- },
600
- strategyOverlap: {
601
- shared,
602
- uniqueToA,
603
- uniqueToB,
604
- overlapPercent,
605
- },
606
- perStrategyComparison,
607
- portfolioTotals: {
608
- blockA: portfolioA,
609
- blockB: portfolioB,
610
- delta: portfolioDelta,
611
- },
612
- filters: {
613
- startDate: startDate ?? null,
614
- endDate: endDate ?? null,
615
- metricsToCompare: metricsToCompare ?? null,
616
- },
617
- };
502
+ // Brief summary for user display
503
+ const summary = `Block Diff: ${blockIdA} vs ${blockIdB} | ${shared.length} shared, ${uniqueToA.length} unique to A, ${uniqueToB.length} unique to B | P/L delta: ${formatCurrency(portfolioStatsB.netPl - portfolioStatsA.netPl)}`;
504
+
505
+ // Build structured output
506
+ const structuredData = {
507
+ blockA: {
508
+ id: blockIdA,
509
+ tradeCount: tradesA.length,
510
+ strategies: Array.from(strategiesA).sort(),
511
+ },
512
+ blockB: {
513
+ id: blockIdB,
514
+ tradeCount: tradesB.length,
515
+ strategies: Array.from(strategiesB).sort(),
516
+ },
517
+ strategyOverlap: {
518
+ shared,
519
+ uniqueToA,
520
+ uniqueToB,
521
+ overlapPercent,
522
+ },
523
+ perStrategyComparison,
524
+ portfolioTotals: {
525
+ blockA: portfolioA,
526
+ blockB: portfolioB,
527
+ delta: portfolioDelta,
528
+ },
529
+ filters: {
530
+ startDate: startDate ?? null,
531
+ endDate: endDate ?? null,
532
+ metricsToCompare: metricsToCompare ?? null,
533
+ },
534
+ };
618
535
 
619
536
  return createToolOutput(summary, structuredData);
620
537
  } catch (error) {
@@ -628,7 +545,7 @@ export function registerComparisonBlockTools(
628
545
  isError: true,
629
546
  };
630
547
  }
631
- }
632
- )
548
+ },
549
+ ),
633
550
  );
634
551
  }