tradeblocks-mcp 3.0.2 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +85 -80
  2. package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
  3. package/dist/chunk-27S67XW3.js.map +1 -0
  4. package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
  5. package/dist/chunk-2E63THNI.js.map +1 -0
  6. package/dist/{chunk-QTTR7AAW.js → chunk-2I3S2ZLT.js} +325 -84
  7. package/dist/chunk-2I3S2ZLT.js.map +1 -0
  8. package/dist/{chunk-4BLCXNQ6.js → chunk-4HZLVUOZ.js} +179 -183
  9. package/dist/chunk-4HZLVUOZ.js.map +1 -0
  10. package/dist/{chunk-XXYOUIZY.js → chunk-NWNSKALN.js} +63 -80
  11. package/dist/chunk-NWNSKALN.js.map +1 -0
  12. package/dist/{chunk-PRAYH3RT.js → chunk-WNTYA7ER.js} +29 -12
  13. package/dist/chunk-WNTYA7ER.js.map +1 -0
  14. package/dist/{chunk-BKQ4PM4Y.js → chunk-YZA4RS76.js} +24 -18
  15. package/dist/chunk-YZA4RS76.js.map +1 -0
  16. package/dist/{chunk-W2PP3LEH.js → chunk-ZTJXC3LS.js} +17 -8
  17. package/dist/chunk-ZTJXC3LS.js.map +1 -0
  18. package/dist/daily-log-processor-MZW2XBZY.js +9 -0
  19. package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
  20. package/dist/iv-solver-worker.js +9 -1
  21. package/dist/market-provider-6U33TQUT.js +16 -0
  22. package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
  23. package/dist/{sync-PO4IPCYV.js → sync-3UBC37VW.js} +7 -7
  24. package/dist/test-exports.js +876 -483
  25. package/dist/test-exports.js.map +1 -1
  26. package/dist/trade-processor-DKDCWGKD.js +9 -0
  27. package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
  28. package/package.json +2 -6
  29. package/server/{chunk-CSDVJPBB.js → chunk-536NSFED.js} +29 -12
  30. package/server/chunk-536NSFED.js.map +1 -0
  31. package/server/{chunk-WA5AAPCH.js → chunk-A3UKD64A.js} +17 -8
  32. package/server/chunk-A3UKD64A.js.map +1 -0
  33. package/server/{chunk-PNKG7RY7.js → chunk-OMUDNJBJ.js} +63 -80
  34. package/server/chunk-OMUDNJBJ.js.map +1 -0
  35. package/server/{chunk-72GKJE2U.js → chunk-RJDJHZ5Y.js} +934 -335
  36. package/server/chunk-RJDJHZ5Y.js.map +1 -0
  37. package/server/{chunk-NZO6PT64.js → chunk-RM4B2VKS.js} +167 -177
  38. package/server/chunk-RM4B2VKS.js.map +1 -0
  39. package/server/{chunk-5EBXHT6C.js → chunk-SY6GDNVI.js} +24 -18
  40. package/server/chunk-SY6GDNVI.js.map +1 -0
  41. package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
  42. package/server/chunk-T66KH2XH.js.map +1 -0
  43. package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
  44. package/server/chunk-W5E7FHC4.js.map +1 -0
  45. package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
  46. package/server/config-DK7KOMNL.js.map +1 -0
  47. package/server/daily-log-processor-B2VPU2FQ.js +10 -0
  48. package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
  49. package/server/http-server.js +2 -5
  50. package/server/index.js +2497 -2029
  51. package/server/index.js.map +1 -1
  52. package/server/iv-solver-worker.js +9 -1
  53. package/server/market-provider-L56RFHAC.js +17 -0
  54. package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
  55. package/server/{sync-QFI5L7S7.js → sync-CENKDN53.js} +7 -7
  56. package/server/trade-processor-3PI4LHZI.js +10 -0
  57. package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
  58. package/src/auth/clients-store.ts +4 -4
  59. package/src/auth/code-store.ts +1 -1
  60. package/src/auth/config.ts +11 -12
  61. package/src/auth/login-page.ts +8 -10
  62. package/src/auth/provider.ts +35 -30
  63. package/src/auth/token.ts +17 -15
  64. package/src/db/backtest-schemas.ts +38 -8
  65. package/src/db/connection.ts +107 -40
  66. package/src/db/data-root.ts +22 -0
  67. package/src/db/index.ts +37 -5
  68. package/src/db/json-adapters.ts +22 -31
  69. package/src/db/json-migration.ts +11 -15
  70. package/src/db/json-store.ts +5 -3
  71. package/src/db/market-datasets.ts +48 -15
  72. package/src/db/market-schemas.ts +11 -5
  73. package/src/db/market-views.ts +119 -32
  74. package/src/db/parquet-writer.ts +7 -7
  75. package/src/db/profile-schemas.ts +39 -22
  76. package/src/db/schemas.ts +2 -2
  77. package/src/http-server.ts +7 -12
  78. package/src/index.ts +8 -9
  79. package/src/market/ingestor/index.ts +1 -4
  80. package/src/market/ingestor/market-ingestor.ts +140 -67
  81. package/src/market/ingestor/types.ts +1 -3
  82. package/src/market/stores/chain-sql.ts +2 -8
  83. package/src/market/stores/chain-store.ts +1 -4
  84. package/src/market/stores/duckdb-chain-store.ts +3 -14
  85. package/src/market/stores/duckdb-enriched-store.ts +3 -13
  86. package/src/market/stores/duckdb-quote-store.ts +10 -27
  87. package/src/market/stores/duckdb-spot-store.ts +4 -20
  88. package/src/market/stores/enriched-sql.ts +3 -9
  89. package/src/market/stores/enriched-store.ts +2 -2
  90. package/src/market/stores/parquet-chain-store.ts +5 -22
  91. package/src/market/stores/parquet-enriched-store.ts +4 -16
  92. package/src/market/stores/parquet-oi-daily-store.ts +3 -14
  93. package/src/market/stores/parquet-quote-store.ts +9 -29
  94. package/src/market/stores/parquet-spot-store.ts +14 -33
  95. package/src/market/stores/quote-store.ts +2 -10
  96. package/src/market/stores/spot-sql.ts +3 -15
  97. package/src/market/stores/spot-store.ts +1 -1
  98. package/src/market/tickers/defaults.json +6 -6
  99. package/src/market/tickers/loader.ts +2 -7
  100. package/src/market/tickers/registry.ts +5 -15
  101. package/src/market/tickers/schemas.ts +2 -6
  102. package/src/models/strategy-profile.ts +39 -39
  103. package/src/sync/block-sync.ts +44 -65
  104. package/src/sync/index.ts +1 -4
  105. package/src/sync/metadata.ts +14 -20
  106. package/src/test-exports.ts +139 -123
  107. package/src/tools/analysis.ts +180 -159
  108. package/src/tools/batch-exit-analysis.ts +100 -59
  109. package/src/tools/blocks/analysis.ts +116 -101
  110. package/src/tools/blocks/comparison.ts +281 -364
  111. package/src/tools/blocks/core.ts +208 -253
  112. package/src/tools/blocks/health.ts +571 -563
  113. package/src/tools/blocks/index.ts +2 -0
  114. package/src/tools/blocks/paired-comparison.ts +425 -0
  115. package/src/tools/blocks/similarity.ts +316 -283
  116. package/src/tools/edge-decay.ts +124 -197
  117. package/src/tools/exit-analysis.ts +130 -77
  118. package/src/tools/greeks-attribution.ts +84 -35
  119. package/src/tools/guides.ts +4 -6
  120. package/src/tools/imports.ts +11 -14
  121. package/src/tools/market-data.ts +1067 -757
  122. package/src/tools/market-enrichment.ts +3 -3
  123. package/src/tools/market-fetch.ts +148 -67
  124. package/src/tools/market-imports.ts +12 -12
  125. package/src/tools/middleware/sync-middleware.ts +5 -6
  126. package/src/tools/performance.ts +185 -302
  127. package/src/tools/profile-analysis.ts +52 -66
  128. package/src/tools/profiles.ts +106 -69
  129. package/src/tools/regime-advisor.ts +20 -45
  130. package/src/tools/replay.ts +81 -77
  131. package/src/tools/reports/discrepancies.ts +298 -328
  132. package/src/tools/reports/fields.ts +7 -25
  133. package/src/tools/reports/helpers.ts +18 -49
  134. package/src/tools/reports/predictive.ts +27 -70
  135. package/src/tools/reports/slippage-trends.ts +315 -345
  136. package/src/tools/reports/slippage.ts +1 -4
  137. package/src/tools/reports/strategy-matches.ts +399 -441
  138. package/src/tools/schema.ts +43 -40
  139. package/src/tools/shared/filters.ts +3 -9
  140. package/src/tools/snapshot.ts +9 -30
  141. package/src/tools/sql.ts +15 -14
  142. package/src/tools/tickers.ts +1 -4
  143. package/src/utils/batch-exit-analysis.ts +31 -42
  144. package/src/utils/black-scholes.ts +39 -29
  145. package/src/utils/block-loader.ts +69 -83
  146. package/src/utils/calibration-probe.ts +3 -4
  147. package/src/utils/chain-loader.ts +3 -3
  148. package/src/utils/csv-discovery.ts +16 -22
  149. package/src/utils/data-quality.ts +24 -36
  150. package/src/utils/exit-triggers.ts +91 -96
  151. package/src/utils/field-timing.ts +94 -79
  152. package/src/utils/filter-predicates.ts +13 -9
  153. package/src/utils/flatfile-importer.ts +94 -64
  154. package/src/utils/greeks-decomposition.ts +152 -100
  155. package/src/utils/iv-solver-pool.ts +55 -25
  156. package/src/utils/iv-solver-worker.ts +5 -5
  157. package/src/utils/market-enricher.ts +528 -497
  158. package/src/utils/market-importer.ts +31 -12
  159. package/src/utils/market-provider.ts +21 -23
  160. package/src/utils/massive-tier.ts +5 -7
  161. package/src/utils/migrate-option-data-helpers.ts +2 -8
  162. package/src/utils/option-quote-greeks.ts +25 -31
  163. package/src/utils/option-time.ts +4 -8
  164. package/src/utils/output-formatter.ts +1 -4
  165. package/src/utils/provider-capabilities.ts +1 -4
  166. package/src/utils/providers/massive.ts +59 -93
  167. package/src/utils/providers/thetadata/backfill.ts +14 -23
  168. package/src/utils/providers/thetadata/client.ts +12 -8
  169. package/src/utils/providers/thetadata/decode.ts +2 -20
  170. package/src/utils/providers/thetadata/endpoints.ts +32 -40
  171. package/src/utils/providers/thetadata/join.ts +11 -10
  172. package/src/utils/providers/thetadata/proto.ts +12 -10
  173. package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
  174. package/src/utils/providers/thetadata.ts +11 -10
  175. package/src/utils/quote-enricher.ts +4 -4
  176. package/src/utils/quote-parquet-projection.ts +3 -11
  177. package/src/utils/sample-date-selector.ts +3 -5
  178. package/src/utils/schema-metadata.ts +102 -70
  179. package/src/utils/ticker.ts +5 -9
  180. package/src/utils/trade-replay.ts +77 -68
  181. package/dist/chunk-4BLCXNQ6.js.map +0 -1
  182. package/dist/chunk-BKQ4PM4Y.js.map +0 -1
  183. package/dist/chunk-FGZH632F.js.map +0 -1
  184. package/dist/chunk-LDKTV7GW.js.map +0 -1
  185. package/dist/chunk-PRAYH3RT.js.map +0 -1
  186. package/dist/chunk-QTTR7AAW.js.map +0 -1
  187. package/dist/chunk-W2PP3LEH.js.map +0 -1
  188. package/dist/chunk-XXYOUIZY.js.map +0 -1
  189. package/dist/daily-log-processor-6MWJ23JK.js +0 -9
  190. package/dist/market-provider-VDRJUEF2.js +0 -16
  191. package/dist/trade-processor-NHU4VWRX.js +0 -9
  192. package/manifest.json +0 -141
  193. package/server/chunk-5EBXHT6C.js.map +0 -1
  194. package/server/chunk-72GKJE2U.js.map +0 -1
  195. package/server/chunk-CSDVJPBB.js.map +0 -1
  196. package/server/chunk-FBNDMCT5.js.map +0 -1
  197. package/server/chunk-NRFXAJF7.js.map +0 -1
  198. package/server/chunk-NZO6PT64.js.map +0 -1
  199. package/server/chunk-PNKG7RY7.js.map +0 -1
  200. package/server/chunk-WA5AAPCH.js.map +0 -1
  201. package/server/config-6IZXEFEX.js.map +0 -1
  202. package/server/daily-log-processor-Y3PVSVBM.js +0 -10
  203. package/server/market-provider-VOYYVYWT.js +0 -17
  204. package/server/trade-processor-JWVS37KM.js +0 -10
  205. /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-MZW2XBZY.js.map} +0 -0
  206. /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
  207. /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-6U33TQUT.js.map} +0 -0
  208. /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
  209. /package/dist/{sync-PO4IPCYV.js.map → sync-3UBC37VW.js.map} +0 -0
  210. /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-DKDCWGKD.js.map} +0 -0
  211. /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
  212. /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-B2VPU2FQ.js.map} +0 -0
  213. /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
  214. /package/server/{market-provider-VOYYVYWT.js.map → market-provider-L56RFHAC.js.map} +0 -0
  215. /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
  216. /package/server/{sync-QFI5L7S7.js.map → sync-CENKDN53.js.map} +0 -0
  217. /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-3PI4LHZI.js.map} +0 -0
  218. /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
@@ -6,28 +6,22 @@
6
6
 
7
7
  import { z } from "zod";
8
8
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
- import {
10
- loadBlock,
11
- listBlocks,
12
- loadReportingLog,
13
- } from "../../utils/block-loader.ts";
9
+ import { loadBlock, listBlocks, loadReportingLog } from "../../utils/block-loader.ts";
14
10
  import {
15
11
  createToolOutput,
16
12
  formatCurrency,
17
13
  formatPercent,
18
14
  formatRatio,
19
15
  } from "../../utils/output-formatter.ts";
20
- import {
21
- PortfolioStatsCalculator,
22
- calculateDailyExposure,
23
- } from "@tradeblocks/lib";
16
+ import { PortfolioStatsCalculator, calculateDailyExposure } from "@tradeblocks/lib";
24
17
  import type { Trade, PeakExposure, EquityCurvePoint } from "@tradeblocks/lib";
25
18
  import { resolveTradeTicker } from "../../utils/ticker.ts";
26
- import { filterByStrategy, filterByDateRange, filterDailyLogsByDateRange } from "../shared/filters.ts";
27
19
  import {
28
- withSyncedBlock,
29
- withFullSync,
30
- } from "../middleware/sync-middleware.ts";
20
+ filterByStrategy,
21
+ filterByDateRange,
22
+ filterDailyLogsByDateRange,
23
+ } from "../shared/filters.ts";
24
+ import { withSyncedBlock, withFullSync } from "../middleware/sync-middleware.ts";
31
25
 
32
26
  /**
33
27
  * Calculate peak daily exposure using the shared sweep-line algorithm.
@@ -35,7 +29,7 @@ import {
35
29
  */
36
30
  function calculatePeakExposure(
37
31
  trades: Trade[],
38
- initialCapital: number
32
+ initialCapital: number,
39
33
  ): {
40
34
  peakByDollars: PeakExposure | null;
41
35
  peakByPercent: PeakExposure | null;
@@ -43,8 +37,7 @@ function calculatePeakExposure(
43
37
  // Build equity curve from trades - P&L is realized on close date
44
38
  const closedTrades = trades.filter((t) => t.dateClosed);
45
39
  const sortedByClose = [...closedTrades].sort(
46
- (a, b) =>
47
- new Date(a.dateClosed!).getTime() - new Date(b.dateClosed!).getTime()
40
+ (a, b) => new Date(a.dateClosed!).getTime() - new Date(b.dateClosed!).getTime(),
48
41
  );
49
42
 
50
43
  const equityCurve: EquityCurvePoint[] = [];
@@ -70,10 +63,7 @@ function calculatePeakExposure(
70
63
  /**
71
64
  * Register core block tools
72
65
  */
73
- export function registerCoreBlockTools(
74
- server: McpServer,
75
- baseDir: string
76
- ): void {
66
+ export function registerCoreBlockTools(server: McpServer, baseDir: string): void {
77
67
  const calculator = new PortfolioStatsCalculator();
78
68
 
79
69
  // Tool 1: list_blocks (formerly list_backtests)
@@ -87,16 +77,11 @@ export function registerCoreBlockTools(
87
77
  .enum(["name", "tradeCount", "netPl", "dateRange"])
88
78
  .default("name")
89
79
  .describe("Sort results by field (default: name)"),
90
- sortOrder: z
91
- .enum(["asc", "desc"])
92
- .default("asc")
93
- .describe("Sort direction (default: asc)"),
80
+ sortOrder: z.enum(["asc", "desc"]).default("asc").describe("Sort direction (default: asc)"),
94
81
  containsStrategy: z
95
82
  .string()
96
83
  .optional()
97
- .describe(
98
- "Filter to blocks containing this strategy name (case-insensitive)"
99
- ),
84
+ .describe("Filter to blocks containing this strategy name (case-insensitive)"),
100
85
  minTrades: z
101
86
  .number()
102
87
  .min(1)
@@ -105,14 +90,12 @@ export function registerCoreBlockTools(
105
90
  hasDailyLog: z
106
91
  .boolean()
107
92
  .optional()
108
- .describe(
109
- "Filter to blocks with (true) or without (false) daily log data"
110
- ),
93
+ .describe("Filter to blocks with (true) or without (false) daily log data"),
111
94
  hasReportingLog: z
112
95
  .boolean()
113
96
  .optional()
114
97
  .describe(
115
- "Filter to blocks with (true) or without (false) reporting log data (actual trade execution)"
98
+ "Filter to blocks with (true) or without (false) reporting log data (actual trade execution)",
116
99
  ),
117
100
  limit: z
118
101
  .number()
@@ -125,109 +108,99 @@ export function registerCoreBlockTools(
125
108
  withFullSync(
126
109
  baseDir,
127
110
  async (
128
- {
129
- sortBy,
130
- sortOrder,
131
- containsStrategy,
132
- minTrades,
133
- hasDailyLog,
134
- hasReportingLog,
135
- limit,
136
- },
137
- { blockSyncResult: syncResult }
111
+ { sortBy, sortOrder, containsStrategy, minTrades, hasDailyLog, hasReportingLog, limit },
112
+ { blockSyncResult: syncResult },
138
113
  ) => {
139
114
  try {
140
115
  let blocks = await listBlocks(baseDir);
141
116
 
142
- // Apply filters
143
- if (containsStrategy) {
144
- const strategyLower = containsStrategy.toLowerCase();
145
- blocks = blocks.filter((b) =>
146
- b.strategies.some((s) => s.toLowerCase().includes(strategyLower))
147
- );
148
- }
149
- if (minTrades !== undefined) {
150
- blocks = blocks.filter((b) => b.tradeCount >= minTrades);
151
- }
152
- if (hasDailyLog !== undefined) {
153
- blocks = blocks.filter((b) => b.hasDailyLog === hasDailyLog);
154
- }
155
- if (hasReportingLog !== undefined) {
156
- blocks = blocks.filter((b) => b.hasReportingLog === hasReportingLog);
157
- }
158
-
159
- // Sort blocks based on parameters
160
- const multiplier = sortOrder === "asc" ? 1 : -1;
161
- blocks = [...blocks].sort((a, b) => {
162
- switch (sortBy) {
163
- case "tradeCount":
164
- return (a.tradeCount - b.tradeCount) * multiplier;
165
- case "netPl":
166
- return ((a.netPl ?? 0) - (b.netPl ?? 0)) * multiplier;
167
- case "dateRange": {
168
- const aTime = a.dateRange.end?.getTime() ?? 0;
169
- const bTime = b.dateRange.end?.getTime() ?? 0;
170
- return (aTime - bTime) * multiplier;
171
- }
172
- case "name":
173
- default:
174
- return a.name.localeCompare(b.name) * multiplier;
117
+ // Apply filters
118
+ if (containsStrategy) {
119
+ const strategyLower = containsStrategy.toLowerCase();
120
+ blocks = blocks.filter((b) =>
121
+ b.strategies.some((s) => s.toLowerCase().includes(strategyLower)),
122
+ );
123
+ }
124
+ if (minTrades !== undefined) {
125
+ blocks = blocks.filter((b) => b.tradeCount >= minTrades);
126
+ }
127
+ if (hasDailyLog !== undefined) {
128
+ blocks = blocks.filter((b) => b.hasDailyLog === hasDailyLog);
129
+ }
130
+ if (hasReportingLog !== undefined) {
131
+ blocks = blocks.filter((b) => b.hasReportingLog === hasReportingLog);
175
132
  }
176
- });
177
-
178
- // Apply limit
179
- const totalBeforeLimit = blocks.length;
180
- if (limit !== undefined && limit < blocks.length) {
181
- blocks = blocks.slice(0, limit);
182
- }
183
133
 
184
- // Brief summary for user display
185
- const blocksWithReporting = blocks.filter(
186
- (b) => b.hasReportingLog
187
- ).length;
188
- const summary = `Found ${blocks.length} block(s)${totalBeforeLimit > blocks.length ? ` (showing ${blocks.length} of ${totalBeforeLimit})` : ""}${blocksWithReporting > 0 ? `, ${blocksWithReporting} with reporting logs` : ""}`;
134
+ // Sort blocks based on parameters
135
+ const multiplier = sortOrder === "asc" ? 1 : -1;
136
+ blocks = [...blocks].sort((a, b) => {
137
+ switch (sortBy) {
138
+ case "tradeCount":
139
+ return (a.tradeCount - b.tradeCount) * multiplier;
140
+ case "netPl":
141
+ return ((a.netPl ?? 0) - (b.netPl ?? 0)) * multiplier;
142
+ case "dateRange": {
143
+ const aTime = a.dateRange.end?.getTime() ?? 0;
144
+ const bTime = b.dateRange.end?.getTime() ?? 0;
145
+ return (aTime - bTime) * multiplier;
146
+ }
147
+ case "name":
148
+ default:
149
+ return a.name.localeCompare(b.name) * multiplier;
150
+ }
151
+ });
189
152
 
190
- // Collect sync errors
191
- const syncErrors = [...syncResult.errors];
153
+ // Apply limit
154
+ const totalBeforeLimit = blocks.length;
155
+ if (limit !== undefined && limit < blocks.length) {
156
+ blocks = blocks.slice(0, limit);
157
+ }
192
158
 
193
- // Build structured data for Claude reasoning
194
- const structuredData = {
195
- options: {
196
- sortBy,
197
- sortOrder,
198
- containsStrategy: containsStrategy ?? null,
199
- minTrades: minTrades ?? null,
200
- hasDailyLog: hasDailyLog ?? null,
201
- hasReportingLog: hasReportingLog ?? null,
202
- limit: limit ?? null,
203
- },
204
- totalMatching: totalBeforeLimit,
205
- blocks: blocks.map((b) => ({
206
- id: b.blockId,
207
- name: b.name,
208
- tradeCount: b.tradeCount,
209
- dateRange: {
210
- start: b.dateRange.start?.toISOString() ?? null,
211
- end: b.dateRange.end?.toISOString() ?? null,
159
+ // Brief summary for user display
160
+ const blocksWithReporting = blocks.filter((b) => b.hasReportingLog).length;
161
+ const summary = `Found ${blocks.length} block(s)${totalBeforeLimit > blocks.length ? ` (showing ${blocks.length} of ${totalBeforeLimit})` : ""}${blocksWithReporting > 0 ? `, ${blocksWithReporting} with reporting logs` : ""}`;
162
+
163
+ // Collect sync errors
164
+ const syncErrors = [...syncResult.errors];
165
+
166
+ // Build structured data for Claude reasoning
167
+ const structuredData = {
168
+ options: {
169
+ sortBy,
170
+ sortOrder,
171
+ containsStrategy: containsStrategy ?? null,
172
+ minTrades: minTrades ?? null,
173
+ hasDailyLog: hasDailyLog ?? null,
174
+ hasReportingLog: hasReportingLog ?? null,
175
+ limit: limit ?? null,
212
176
  },
213
- strategies: b.strategies,
214
- totalPl: b.totalPl,
215
- netPl: b.netPl,
216
- hasDailyLog: b.hasDailyLog,
217
- hasReportingLog: b.hasReportingLog,
218
- reportingLog: b.reportingLog ?? null,
219
- })),
220
- count: blocks.length,
221
- // Add sync info (informational for Claude)
222
- syncInfo: {
223
- blocksProcessed: syncResult.blocksProcessed,
224
- blocksSynced: syncResult.blocksSynced,
225
- blocksUnchanged: syncResult.blocksUnchanged,
226
- blocksDeleted: syncResult.blocksDeleted,
227
- },
228
- // Add sync errors if any occurred
229
- ...(syncErrors.length > 0 ? { syncErrors } : {}),
230
- };
177
+ totalMatching: totalBeforeLimit,
178
+ blocks: blocks.map((b) => ({
179
+ id: b.blockId,
180
+ name: b.name,
181
+ tradeCount: b.tradeCount,
182
+ dateRange: {
183
+ start: b.dateRange.start?.toISOString() ?? null,
184
+ end: b.dateRange.end?.toISOString() ?? null,
185
+ },
186
+ strategies: b.strategies,
187
+ totalPl: b.totalPl,
188
+ netPl: b.netPl,
189
+ hasDailyLog: b.hasDailyLog,
190
+ hasReportingLog: b.hasReportingLog,
191
+ reportingLog: b.reportingLog ?? null,
192
+ })),
193
+ count: blocks.length,
194
+ // Add sync info (informational for Claude)
195
+ syncInfo: {
196
+ blocksProcessed: syncResult.blocksProcessed,
197
+ blocksSynced: syncResult.blocksSynced,
198
+ blocksUnchanged: syncResult.blocksUnchanged,
199
+ blocksDeleted: syncResult.blocksDeleted,
200
+ },
201
+ // Add sync errors if any occurred
202
+ ...(syncErrors.length > 0 ? { syncErrors } : {}),
203
+ };
231
204
 
232
205
  return createToolOutput(summary, structuredData);
233
206
  } catch (error) {
@@ -241,8 +214,8 @@ export function registerCoreBlockTools(
241
214
  isError: true,
242
215
  };
243
216
  }
244
- }
245
- )
217
+ },
218
+ ),
246
219
  );
247
220
 
248
221
  // Tool 2: get_block_info
@@ -252,9 +225,7 @@ export function registerCoreBlockTools(
252
225
  description:
253
226
  "Get detailed metadata for a block including available strategies, date range, and daily log status. Use blockId from list_blocks.",
254
227
  inputSchema: z.object({
255
- blockId: z
256
- .string()
257
- .describe("Block ID from list_blocks (e.g., 'main-port')"),
228
+ blockId: z.string().describe("Block ID from list_blocks (e.g., 'main-port')"),
258
229
  }),
259
230
  },
260
231
  withSyncedBlock(baseDir, async ({ blockId }) => {
@@ -263,9 +234,7 @@ export function registerCoreBlockTools(
263
234
  const trades = block.trades;
264
235
  const dailyLogs = block.dailyLogs;
265
236
 
266
- const strategies = Array.from(
267
- new Set(trades.map((t) => t.strategy))
268
- ).sort();
237
+ const strategies = Array.from(new Set(trades.map((t) => t.strategy))).sort();
269
238
  const dates = trades.map((t) => new Date(t.dateOpened).getTime());
270
239
  const dateRange = {
271
240
  start: dates.length > 0 ? new Date(Math.min(...dates)) : null,
@@ -299,7 +268,7 @@ export function registerCoreBlockTools(
299
268
  isError: true,
300
269
  };
301
270
  }
302
- })
271
+ }),
303
272
  );
304
273
 
305
274
  // Tool 2b: get_reporting_log_stats
@@ -347,13 +316,16 @@ export function registerCoreBlockTools(
347
316
  strategyTrades.get(key)!.push(trade);
348
317
  }
349
318
 
350
- const byStrategy: Record<string, {
351
- tradeCount: number;
352
- winRate: number;
353
- totalPL: number;
354
- avgPL: number;
355
- contractCount: number;
356
- }> = {};
319
+ const byStrategy: Record<
320
+ string,
321
+ {
322
+ tradeCount: number;
323
+ winRate: number;
324
+ totalPL: number;
325
+ avgPL: number;
326
+ contractCount: number;
327
+ }
328
+ > = {};
357
329
 
358
330
  for (const [strategy, strategyTradeList] of strategyTrades) {
359
331
  const tradeCount = strategyTradeList.length;
@@ -361,9 +333,7 @@ export function registerCoreBlockTools(
361
333
  const winRate = tradeCount > 0 ? winningTrades / tradeCount : 0;
362
334
  const totalPL = strategyTradeList.reduce((sum, t) => sum + t.pl, 0);
363
335
  const avgPL = tradeCount > 0 ? totalPL / tradeCount : 0;
364
- const contractCount = strategyTradeList.reduce(
365
- (sum, t) => sum + t.numContracts, 0
366
- );
336
+ const contractCount = strategyTradeList.reduce((sum, t) => sum + t.numContracts, 0);
367
337
  byStrategy[strategy] = { tradeCount, winRate, totalPL, avgPL, contractCount };
368
338
  }
369
339
 
@@ -404,7 +374,7 @@ export function registerCoreBlockTools(
404
374
  isError: true,
405
375
  };
406
376
  }
407
- })
377
+ }),
408
378
  );
409
379
 
410
380
  // Tool 3: get_statistics
@@ -414,23 +384,13 @@ export function registerCoreBlockTools(
414
384
  description:
415
385
  "Get comprehensive portfolio statistics: win rate, Sharpe ratio, max drawdown, P&L metrics, and more. Use blockId from list_blocks. Optionally filter by strategy, ticker, or date range.",
416
386
  inputSchema: z.object({
417
- blockId: z
418
- .string()
419
- .describe("Block ID from list_blocks (e.g., 'main-port')"),
420
- strategy: z
421
- .string()
422
- .optional()
423
- .describe("Filter by strategy name (case-insensitive)"),
387
+ blockId: z.string().describe("Block ID from list_blocks (e.g., 'main-port')"),
388
+ strategy: z.string().optional().describe("Filter by strategy name (case-insensitive)"),
424
389
  tickerFilter: z
425
390
  .string()
426
391
  .optional()
427
- .describe(
428
- "Filter trades by underlying ticker symbol (e.g., 'SPY', 'AAPL')"
429
- ),
430
- startDate: z
431
- .string()
432
- .optional()
433
- .describe("Start date filter (YYYY-MM-DD)"),
392
+ .describe("Filter trades by underlying ticker symbol (e.g., 'SPY', 'AAPL')"),
393
+ startDate: z.string().optional().describe("Start date filter (YYYY-MM-DD)"),
434
394
  endDate: z.string().optional().describe("End date filter (YYYY-MM-DD)"),
435
395
  }),
436
396
  },
@@ -439,105 +399,101 @@ export function registerCoreBlockTools(
439
399
  async ({ blockId, strategy, tickerFilter, startDate, endDate }, { syncResult }) => {
440
400
  try {
441
401
  const block = await loadBlock(baseDir, blockId);
442
- let trades = block.trades;
443
- const dailyLogs = block.dailyLogs;
402
+ let trades = block.trades;
403
+ const dailyLogs = block.dailyLogs;
444
404
 
445
- // Apply filters
446
- trades = filterByStrategy(trades, strategy);
447
- trades = filterByDateRange(trades, startDate, endDate);
405
+ // Apply filters
406
+ trades = filterByStrategy(trades, strategy);
407
+ trades = filterByDateRange(trades, startDate, endDate);
448
408
 
449
- // Apply ticker filter (supports both explicit ticker columns and legs-derived symbols)
450
- if (tickerFilter) {
451
- const tickerLower = tickerFilter.toLowerCase();
452
- trades = trades.filter(
453
- (t) => resolveTradeTicker(t).toLowerCase() === tickerLower
454
- );
455
- }
409
+ // Apply ticker filter (supports both explicit ticker columns and legs-derived symbols)
410
+ if (tickerFilter) {
411
+ const tickerLower = tickerFilter.toLowerCase();
412
+ trades = trades.filter((t) => resolveTradeTicker(t).toLowerCase() === tickerLower);
413
+ }
456
414
 
457
- if (trades.length === 0) {
458
- return {
459
- content: [
460
- {
461
- type: "text",
462
- text: `No trades found matching the specified filters.`,
463
- },
464
- ],
465
- };
466
- }
415
+ if (trades.length === 0) {
416
+ return {
417
+ content: [
418
+ {
419
+ type: "text",
420
+ text: `No trades found matching the specified filters.`,
421
+ },
422
+ ],
423
+ };
424
+ }
467
425
 
468
- // Filter daily logs by date range when date filters are provided
469
- // Only applies when not strategy-filtered (daily logs represent full portfolio)
470
- const isStrategyFiltered = !!strategy;
471
- let filteredDailyLogs = dailyLogs;
472
- if (!isStrategyFiltered && (startDate || endDate) && dailyLogs) {
473
- filteredDailyLogs = filterDailyLogsByDateRange(dailyLogs, startDate, endDate);
474
- }
426
+ // Filter daily logs by date range when date filters are provided
427
+ // Only applies when not strategy-filtered (daily logs represent full portfolio)
428
+ const isStrategyFiltered = !!strategy;
429
+ let filteredDailyLogs = dailyLogs;
430
+ if (!isStrategyFiltered && (startDate || endDate) && dailyLogs) {
431
+ filteredDailyLogs = filterDailyLogsByDateRange(dailyLogs, startDate, endDate);
432
+ }
475
433
 
476
- // When strategy filter is applied, we MUST use trade-based calculations
477
- // because daily logs represent the FULL portfolio, not per-strategy
478
- const stats = calculator.calculatePortfolioStats(
479
- trades,
480
- isStrategyFiltered ? undefined : filteredDailyLogs,
481
- isStrategyFiltered
482
- );
434
+ // When strategy filter is applied, we MUST use trade-based calculations
435
+ // because daily logs represent the FULL portfolio, not per-strategy
436
+ const stats = calculator.calculatePortfolioStats(
437
+ trades,
438
+ isStrategyFiltered ? undefined : filteredDailyLogs,
439
+ isStrategyFiltered,
440
+ );
483
441
 
484
- // Calculate peak daily exposure
485
- const peakExposure = calculatePeakExposure(trades, stats.initialCapital);
442
+ // Calculate peak daily exposure
443
+ const peakExposure = calculatePeakExposure(trades, stats.initialCapital);
486
444
 
487
- // Brief summary for user display
488
- const summary = `Stats: ${blockId}${strategy ? ` (${strategy})` : ""} | ${stats.totalTrades} trades | Win: ${formatPercent(stats.winRate * 100)} | Net P&L: ${formatCurrency(stats.netPl)} | Sharpe: ${formatRatio(stats.sharpeRatio)}`;
445
+ // Brief summary for user display
446
+ const summary = `Stats: ${blockId}${strategy ? ` (${strategy})` : ""} | ${stats.totalTrades} trades | Win: ${formatPercent(stats.winRate * 100)} | Net P&L: ${formatCurrency(stats.netPl)} | Sharpe: ${formatRatio(stats.sharpeRatio)}`;
489
447
 
490
- // Build structured data for Claude reasoning - include full PortfolioStats
491
- const structuredData = {
492
- blockId,
493
- filters: {
494
- strategy: strategy ?? null,
495
- tickerFilter: tickerFilter ?? null,
496
- startDate: startDate ?? null,
497
- endDate: endDate ?? null,
498
- },
499
- stats: {
500
- totalTrades: stats.totalTrades,
501
- winningTrades: stats.winningTrades,
502
- losingTrades: stats.losingTrades,
503
- breakEvenTrades: stats.breakEvenTrades,
504
- winRate: stats.winRate,
505
- totalPl: stats.totalPl,
506
- netPl: stats.netPl,
507
- totalCommissions: stats.totalCommissions,
508
- avgWin: stats.avgWin,
509
- avgLoss: stats.avgLoss,
510
- maxWin: stats.maxWin,
511
- maxLoss: stats.maxLoss,
512
- profitFactor: stats.profitFactor,
513
- sharpeRatio: stats.sharpeRatio,
514
- sortinoRatio: stats.sortinoRatio,
515
- calmarRatio: stats.calmarRatio,
516
- maxDrawdown: stats.maxDrawdown,
517
- timeInDrawdown: stats.timeInDrawdown,
518
- kellyPercentage: stats.kellyPercentage,
519
- cagr: stats.cagr,
520
- initialCapital: stats.initialCapital,
521
- avgDailyPl: stats.avgDailyPl,
522
- maxWinStreak: stats.maxWinStreak,
523
- maxLossStreak: stats.maxLossStreak,
524
- currentStreak: stats.currentStreak,
525
- monthlyWinRate: stats.monthlyWinRate,
526
- weeklyWinRate: stats.weeklyWinRate,
527
- },
528
- peakExposure: {
529
- byDollars: peakExposure.peakByDollars,
530
- byPercent: peakExposure.peakByPercent,
531
- },
532
- // Add sync info if sync occurred
533
- ...(syncResult.status === "synced"
534
- ? { syncInfo: { status: "synced", tradeCount: syncResult.tradeCount } }
535
- : {}),
536
- // Add sync warning if sync errored (continuing with potentially stale data)
537
- ...(syncResult.status === "error"
538
- ? { syncWarning: syncResult.error }
539
- : {}),
540
- };
448
+ // Build structured data for Claude reasoning - include full PortfolioStats
449
+ const structuredData = {
450
+ blockId,
451
+ filters: {
452
+ strategy: strategy ?? null,
453
+ tickerFilter: tickerFilter ?? null,
454
+ startDate: startDate ?? null,
455
+ endDate: endDate ?? null,
456
+ },
457
+ stats: {
458
+ totalTrades: stats.totalTrades,
459
+ winningTrades: stats.winningTrades,
460
+ losingTrades: stats.losingTrades,
461
+ breakEvenTrades: stats.breakEvenTrades,
462
+ winRate: stats.winRate,
463
+ totalPl: stats.totalPl,
464
+ netPl: stats.netPl,
465
+ totalCommissions: stats.totalCommissions,
466
+ avgWin: stats.avgWin,
467
+ avgLoss: stats.avgLoss,
468
+ maxWin: stats.maxWin,
469
+ maxLoss: stats.maxLoss,
470
+ profitFactor: stats.profitFactor,
471
+ sharpeRatio: stats.sharpeRatio,
472
+ sortinoRatio: stats.sortinoRatio,
473
+ calmarRatio: stats.calmarRatio,
474
+ maxDrawdown: stats.maxDrawdown,
475
+ timeInDrawdown: stats.timeInDrawdown,
476
+ kellyPercentage: stats.kellyPercentage,
477
+ cagr: stats.cagr,
478
+ initialCapital: stats.initialCapital,
479
+ avgDailyPl: stats.avgDailyPl,
480
+ maxWinStreak: stats.maxWinStreak,
481
+ maxLossStreak: stats.maxLossStreak,
482
+ currentStreak: stats.currentStreak,
483
+ monthlyWinRate: stats.monthlyWinRate,
484
+ weeklyWinRate: stats.weeklyWinRate,
485
+ },
486
+ peakExposure: {
487
+ byDollars: peakExposure.peakByDollars,
488
+ byPercent: peakExposure.peakByPercent,
489
+ },
490
+ // Add sync info if sync occurred
491
+ ...(syncResult.status === "synced"
492
+ ? { syncInfo: { status: "synced", tradeCount: syncResult.tradeCount } }
493
+ : {}),
494
+ // Add sync warning if sync errored (continuing with potentially stale data)
495
+ ...(syncResult.status === "error" ? { syncWarning: syncResult.error } : {}),
496
+ };
541
497
 
542
498
  return createToolOutput(summary, structuredData);
543
499
  } catch (error) {
@@ -551,8 +507,7 @@ export function registerCoreBlockTools(
551
507
  isError: true,
552
508
  };
553
509
  }
554
- }
555
- )
510
+ },
511
+ ),
556
512
  );
557
-
558
513
  }