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
@@ -15,23 +15,12 @@ import type { Trade } from "@tradeblocks/lib";
15
15
  import { getConnection } from "../db/connection.ts";
16
16
  import { getProfile, listProfiles } from "../db/profile-schemas.ts";
17
17
  import { filterByStrategy } from "./shared/filters.ts";
18
- import {
19
- buildLookaheadFreeQuery,
20
- type MarketLookupKey,
21
- } from "../utils/field-timing.ts";
22
- import {
23
- DEFAULT_MARKET_TICKER,
24
- marketTickerDateKey,
25
- resolveTradeTicker,
26
- } from "../utils/ticker.ts";
18
+ import { buildLookaheadFreeQuery, type MarketLookupKey } from "../utils/field-timing.ts";
19
+ import { DEFAULT_MARKET_TICKER, marketTickerDateKey, resolveTradeTicker } from "../utils/ticker.ts";
27
20
  import { computeSliceStats, type SliceStats } from "../utils/analysis-stats.ts";
28
21
  import { buildFilterPredicate, type FilterPredicate } from "../utils/filter-predicates.ts";
29
22
  import { withSyncedBlock } from "./middleware/sync-middleware.ts";
30
- import {
31
- upgradeToReadWrite,
32
- downgradeToReadOnly,
33
- getConnectionMode,
34
- } from "../db/connection.ts";
23
+ import { upgradeToReadWrite, downgradeToReadOnly, getConnectionMode } from "../db/connection.ts";
35
24
  import { syncAllBlocks } from "../sync/index.ts";
36
25
 
37
26
  // =============================================================================
@@ -92,7 +81,7 @@ function resultToRecords(result: {
92
81
  }
93
82
 
94
83
  function recordsByTickerDate(
95
- records: Record<string, unknown>[]
84
+ records: Record<string, unknown>[],
96
85
  ): Map<string, Record<string, unknown>> {
97
86
  const mapped = new Map<string, Record<string, unknown>>();
98
87
  for (const record of records) {
@@ -150,8 +139,8 @@ function getTimeBucket(timeOpened: string | undefined): string | null {
150
139
 
151
140
  // morning: 09:30-11:00, midday: 11:00-14:00, afternoon: 14:00-16:00
152
141
  if (totalMinutes < 570) return null; // before 09:30
153
- if (totalMinutes < 660) return "morning"; // 09:30-11:00
154
- if (totalMinutes < 840) return "midday"; // 11:00-14:00
142
+ if (totalMinutes < 660) return "morning"; // 09:30-11:00
143
+ if (totalMinutes < 840) return "midday"; // 11:00-14:00
155
144
  if (totalMinutes <= 960) return "afternoon"; // 14:00-16:00
156
145
  return null; // after 16:00
157
146
  }
@@ -175,7 +164,7 @@ interface TradeWithMarket {
175
164
  async function loadTradesAndMarket(
176
165
  baseDir: string,
177
166
  blockId: string,
178
- strategyName: string
167
+ strategyName: string,
179
168
  ): Promise<{
180
169
  matched: TradeWithMarket[];
181
170
  unmatchedCount: number;
@@ -267,7 +256,7 @@ function createNumericBuckets(values: number[]): { label: string; min: number; m
267
256
  */
268
257
  function findBucket(
269
258
  value: number,
270
- buckets: { label: string; min: number; max: number }[]
259
+ buckets: { label: string; min: number; max: number }[],
271
260
  ): string | null {
272
261
  for (const bucket of buckets) {
273
262
  if (value >= bucket.min && value <= bucket.max) return bucket.label;
@@ -291,7 +280,7 @@ export const analyzeStructureFitSchema = z.object({
291
280
 
292
281
  export async function handleAnalyzeStructureFit(
293
282
  input: z.infer<typeof analyzeStructureFitSchema>,
294
- baseDir: string
283
+ baseDir: string,
295
284
  ): Promise<ReturnType<typeof createToolOutput>> {
296
285
  const { blockId, strategyName } = input;
297
286
  const minTrades = input.minTrades ?? 10;
@@ -302,7 +291,7 @@ export async function handleAnalyzeStructureFit(
302
291
  if (!profile) {
303
292
  return createToolOutput(
304
293
  `No profile found for strategy '${strategyName}' in block '${blockId}'. Create one with profile_strategy first.`,
305
- { error: "profile_not_found" }
294
+ { error: "profile_not_found" },
306
295
  );
307
296
  }
308
297
 
@@ -310,7 +299,7 @@ export async function handleAnalyzeStructureFit(
310
299
  const { matched, unmatchedCount, allTrades } = await loadTradesAndMarket(
311
300
  baseDir,
312
301
  blockId,
313
- strategyName
302
+ strategyName,
314
303
  );
315
304
 
316
305
  const warnings: string[] = [];
@@ -318,20 +307,20 @@ export async function handleAnalyzeStructureFit(
318
307
  if (allTrades.length === 0) {
319
308
  return createToolOutput(
320
309
  `No trades found for strategy '${strategyName}' in block '${blockId}'.`,
321
- { error: "no_trades" }
310
+ { error: "no_trades" },
322
311
  );
323
312
  }
324
313
 
325
314
  if (unmatchedCount > 0) {
326
315
  warnings.push(
327
- `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded from market-based analysis.`
316
+ `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded from market-based analysis.`,
328
317
  );
329
318
  }
330
319
 
331
320
  if (matched.length === 0) {
332
321
  return createToolOutput(
333
322
  `No trades could be matched to market data for strategy '${strategyName}'.`,
334
- { error: "no_market_match", warnings }
323
+ { error: "no_market_match", warnings },
335
324
  );
336
325
  }
337
326
 
@@ -427,7 +416,7 @@ export async function handleAnalyzeStructureFit(
427
416
  for (const [bucketLabel, stats] of Object.entries(bucketStats)) {
428
417
  if (stats.tradeCount > 0 && stats.tradeCount < minTrades) {
429
418
  warnings.push(
430
- `${dimName}/${bucketLabel}: only ${stats.tradeCount} trades (< ${minTrades} threshold)`
419
+ `${dimName}/${bucketLabel}: only ${stats.tradeCount} trades (< ${minTrades} threshold)`,
431
420
  );
432
421
  }
433
422
  }
@@ -525,7 +514,7 @@ export const validateEntryFiltersSchema = z.object({
525
514
 
526
515
  export async function handleValidateEntryFilters(
527
516
  input: z.infer<typeof validateEntryFiltersSchema>,
528
- baseDir: string
517
+ baseDir: string,
529
518
  ): Promise<ReturnType<typeof createToolOutput>> {
530
519
  const { blockId, strategyName } = input;
531
520
  const minTrades = input.minTrades ?? 10;
@@ -537,7 +526,7 @@ export async function handleValidateEntryFilters(
537
526
  if (!profile) {
538
527
  return createToolOutput(
539
528
  `No profile found for strategy '${strategyName}' in block '${blockId}'. Create one with profile_strategy first.`,
540
- { error: "profile_not_found" }
529
+ { error: "profile_not_found" },
541
530
  );
542
531
  }
543
532
 
@@ -545,7 +534,7 @@ export async function handleValidateEntryFilters(
545
534
  if (!profile.entryFilters || profile.entryFilters.length === 0) {
546
535
  return createToolOutput(
547
536
  `Profile '${strategyName}' has no entry_filters defined. Add filters via profile_strategy to enable validation.`,
548
- { no_filters: true }
537
+ { no_filters: true },
549
538
  );
550
539
  }
551
540
 
@@ -557,7 +546,7 @@ export async function handleValidateEntryFilters(
557
546
  if (marketFilters.length === 0) {
558
547
  return createToolOutput(
559
548
  `Profile '${strategyName}' has ${allFilters.length} filter(s) but all are tagged source:'execution' (platform-level). No market-data filters to validate.`,
560
- { no_market_filters: true, execution_filters: executionFilters }
549
+ { no_market_filters: true, execution_filters: executionFilters },
561
550
  );
562
551
  }
563
552
 
@@ -565,34 +554,34 @@ export async function handleValidateEntryFilters(
565
554
  const { matched, unmatchedCount, allTrades } = await loadTradesAndMarket(
566
555
  baseDir,
567
556
  blockId,
568
- strategyName
557
+ strategyName,
569
558
  );
570
559
 
571
560
  const warnings: string[] = [];
572
561
 
573
562
  if (executionFilters.length > 0) {
574
563
  warnings.push(
575
- `${executionFilters.length} execution-level filter(s) skipped (not testable against market data): ${executionFilters.map((f) => f.description || f.field).join(", ")}`
564
+ `${executionFilters.length} execution-level filter(s) skipped (not testable against market data): ${executionFilters.map((f) => f.description || f.field).join(", ")}`,
576
565
  );
577
566
  }
578
567
 
579
568
  if (allTrades.length === 0) {
580
569
  return createToolOutput(
581
570
  `No trades found for strategy '${strategyName}' in block '${blockId}'.`,
582
- { error: "no_trades" }
571
+ { error: "no_trades" },
583
572
  );
584
573
  }
585
574
 
586
575
  if (unmatchedCount > 0) {
587
576
  warnings.push(
588
- `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded.`
577
+ `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded.`,
589
578
  );
590
579
  }
591
580
 
592
581
  if (matched.length === 0) {
593
582
  return createToolOutput(
594
583
  `No trades could be matched to market data for strategy '${strategyName}'.`,
595
- { error: "no_market_match", warnings }
584
+ { error: "no_market_match", warnings },
596
585
  );
597
586
  }
598
587
 
@@ -740,10 +729,7 @@ export async function handleValidateEntryFilters(
740
729
 
741
730
  // Check per-filter: if entered performs worse than filtered_out, suggest removal
742
731
  for (const [filterDesc, { entered, filtered_out }] of Object.entries(perFilter)) {
743
- if (
744
- entered.tradeCount >= minTrades &&
745
- filtered_out.tradeCount >= minTrades
746
- ) {
732
+ if (entered.tradeCount >= minTrades && filtered_out.tradeCount >= minTrades) {
747
733
  if (entered.avgPl < filtered_out.avgPl && filtered_out.avgPl > 0) {
748
734
  profileUpdateHints.push({
749
735
  field: filterDesc,
@@ -770,24 +756,25 @@ export async function handleValidateEntryFilters(
770
756
  // Thin-data warnings
771
757
  if (baseline.tradeCount > 0 && baseline.tradeCount < minTrades) {
772
758
  warnings.push(
773
- `Baseline (all filters): only ${baseline.tradeCount} trades (< ${minTrades} threshold)`
759
+ `Baseline (all filters): only ${baseline.tradeCount} trades (< ${minTrades} threshold)`,
774
760
  );
775
761
  }
776
762
  for (const [filterDesc, { entered, filtered_out }] of Object.entries(perFilter)) {
777
763
  if (entered.tradeCount > 0 && entered.tradeCount < minTrades) {
778
764
  warnings.push(
779
- `${filterDesc} entered: only ${entered.tradeCount} trades (< ${minTrades} threshold)`
765
+ `${filterDesc} entered: only ${entered.tradeCount} trades (< ${minTrades} threshold)`,
780
766
  );
781
767
  }
782
768
  if (filtered_out.tradeCount > 0 && filtered_out.tradeCount < minTrades) {
783
769
  warnings.push(
784
- `${filterDesc} filtered_out: only ${filtered_out.tradeCount} trades (< ${minTrades} threshold)`
770
+ `${filterDesc} filtered_out: only ${filtered_out.tradeCount} trades (< ${minTrades} threshold)`,
785
771
  );
786
772
  }
787
773
  }
788
774
 
789
775
  // Summary text
790
- const execNote = executionFilters.length > 0 ? ` (${executionFilters.length} execution filter(s) skipped)` : "";
776
+ const execNote =
777
+ executionFilters.length > 0 ? ` (${executionFilters.length} execution filter(s) skipped)` : "";
791
778
  const summaryText = `Filter validation for '${strategyName}': ${filters.length} market filter(s) analyzed across ${matched.length} trades${execNote}. Baseline (all market filters): ${baseline.tradeCount} trades, win rate ${baseline.winRate.toFixed(1)}%, avg P&L $${baseline.avgPl.toFixed(2)}. ${profileUpdateHints.length} update hint(s).`;
792
779
 
793
780
  return createToolOutput(summaryText, {
@@ -798,7 +785,9 @@ export async function handleValidateEntryFilters(
798
785
  single: ablationSingle,
799
786
  pairs: ablationPairs,
800
787
  },
801
- execution_filters_skipped: executionFilters.map((f) => f.description || `${f.field} ${f.operator} ${f.value}`),
788
+ execution_filters_skipped: executionFilters.map(
789
+ (f) => f.description || `${f.field} ${f.operator} ${f.value}`,
790
+ ),
802
791
  profile_update_hints: profileUpdateHints,
803
792
  warnings,
804
793
  });
@@ -822,8 +811,11 @@ export const portfolioStructureMapSchema = z.object({
822
811
 
823
812
  export async function handlePortfolioStructureMap(
824
813
  input: z.infer<typeof portfolioStructureMapSchema>,
825
- baseDir: string
826
- ): Promise<ReturnType<typeof createToolOutput> | { content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
814
+ baseDir: string,
815
+ ): Promise<
816
+ | ReturnType<typeof createToolOutput>
817
+ | { content: Array<{ type: "text"; text: string }>; isError?: boolean }
818
+ > {
827
819
  try {
828
820
  const { blockId, minTrades } = portfolioStructureMapSchema.parse(input);
829
821
  const conn = await getConnection(baseDir);
@@ -858,7 +850,9 @@ export async function handlePortfolioStructureMap(
858
850
  try {
859
851
  block = await loadBlock(baseDir, profile.blockId);
860
852
  } catch {
861
- warnings.push(`Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'`);
853
+ warnings.push(
854
+ `Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'`,
855
+ );
862
856
  continue;
863
857
  }
864
858
 
@@ -871,7 +865,9 @@ export async function handlePortfolioStructureMap(
871
865
  }
872
866
  }
873
867
  if (trades.length === 0) {
874
- warnings.push(`No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'`);
868
+ warnings.push(
869
+ `No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'`,
870
+ );
875
871
  continue;
876
872
  }
877
873
 
@@ -927,11 +923,7 @@ export async function handlePortfolioStructureMap(
927
923
 
928
924
  // Handle missing Trend_Direction
929
925
  let trend: TrendLabel | null = null;
930
- if (
931
- trendRaw === null ||
932
- trendRaw === undefined ||
933
- trendRaw === ""
934
- ) {
926
+ if (trendRaw === null || trendRaw === undefined || trendRaw === "") {
935
927
  unknownTrendCount++;
936
928
  if (!unknownTrendPls.has(strategyName)) {
937
929
  unknownTrendPls.set(strategyName, []);
@@ -1001,7 +993,7 @@ export async function handlePortfolioStructureMap(
1001
993
  // Thin-data warning
1002
994
  if (pls.length > 0 && pls.length < minTrades) {
1003
995
  warnings.push(
1004
- `Thin data: '${stratName}' has only ${pls.length} trades in ${regimeLabel}/${trend} (threshold: ${minTrades})`
996
+ `Thin data: '${stratName}' has only ${pls.length} trades in ${regimeLabel}/${trend} (threshold: ${minTrades})`,
1005
997
  );
1006
998
  }
1007
999
  }
@@ -1026,7 +1018,7 @@ export async function handlePortfolioStructureMap(
1026
1018
  // Handle unknown trend trades
1027
1019
  if (unknownTrendCount > 0) {
1028
1020
  warnings.push(
1029
- `${unknownTrendCount} trades had missing or unknown Trend_Direction. Consider running enrich_market_data to populate Trend_Direction.`
1021
+ `${unknownTrendCount} trades had missing or unknown Trend_Direction. Consider running enrich_market_data to populate Trend_Direction.`,
1030
1022
  );
1031
1023
  }
1032
1024
 
@@ -1034,10 +1026,7 @@ export async function handlePortfolioStructureMap(
1034
1026
  const unknownTrendStats: Record<string, SliceStats> | undefined =
1035
1027
  unknownTrendPls.size > 0
1036
1028
  ? Object.fromEntries(
1037
- [...unknownTrendPls.entries()].map(([name, pls]) => [
1038
- name,
1039
- computeSliceStats(pls),
1040
- ])
1029
+ [...unknownTrendPls.entries()].map(([name, pls]) => [name, computeSliceStats(pls)]),
1041
1030
  )
1042
1031
  : undefined;
1043
1032
 
@@ -1086,10 +1075,7 @@ export async function handlePortfolioStructureMap(
1086
1075
  * This includes portfolio_structure_map (from Plan 03) and
1087
1076
  * analyze_structure_fit + validate_entry_filters (from Plan 02, if present).
1088
1077
  */
1089
- export function registerProfileAnalysisTools(
1090
- server: McpServer,
1091
- baseDir: string
1092
- ): void {
1078
+ export function registerProfileAnalysisTools(server: McpServer, baseDir: string): void {
1093
1079
  // portfolio_structure_map: optional blockId means we can't always use withSyncedBlock.
1094
1080
  // When blockId is provided, sync that block. When omitted, sync all blocks.
1095
1081
  server.registerTool(
@@ -1119,7 +1105,7 @@ export function registerProfileAnalysisTools(
1119
1105
  }
1120
1106
 
1121
1107
  return handlePortfolioStructureMap(input, baseDir);
1122
- }
1108
+ },
1123
1109
  );
1124
1110
 
1125
1111
  // -------------------------------------------------------------------------
@@ -1137,7 +1123,7 @@ export function registerProfileAnalysisTools(
1137
1123
  },
1138
1124
  withSyncedBlock(baseDir, async (input, ctx) => {
1139
1125
  return handleAnalyzeStructureFit(input, ctx.baseDir);
1140
- })
1126
+ }),
1141
1127
  );
1142
1128
 
1143
1129
  // -------------------------------------------------------------------------
@@ -1155,6 +1141,6 @@ export function registerProfileAnalysisTools(
1155
1141
  },
1156
1142
  withSyncedBlock(baseDir, async (input, ctx) => {
1157
1143
  return handleValidateEntryFilters(input, ctx.baseDir);
1158
- })
1144
+ }),
1159
1145
  );
1160
1146
  }