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
@@ -34,44 +34,35 @@ import { resolveTradeTicker } from "../utils/ticker.ts";
34
34
  */
35
35
  function filterByStrategy(trades: Trade[], strategy?: string): Trade[] {
36
36
  if (!strategy) return trades;
37
- return trades.filter(
38
- (t) => t.strategy.toLowerCase() === strategy.toLowerCase()
39
- );
37
+ return trades.filter((t) => t.strategy.toLowerCase() === strategy.toLowerCase());
40
38
  }
41
39
 
42
40
  /**
43
41
  * Register all analysis MCP tools
44
42
  */
45
- export function registerAnalysisTools(
46
- server: McpServer,
47
- baseDir: string
48
- ): void {
43
+ export function registerAnalysisTools(server: McpServer, baseDir: string): void {
49
44
  // Tool 1: run_walk_forward
50
45
  server.registerTool(
51
46
  "run_walk_forward",
52
47
  {
53
- description:
54
- "Execute walk-forward analysis to test parameter robustness across time windows",
48
+ description: "Execute walk-forward analysis to test parameter robustness across time windows",
55
49
  inputSchema: z.object({
56
50
  blockId: z.string().describe("Block folder name"),
57
- strategy: z
58
- .string()
59
- .optional()
60
- .describe("Filter by strategy name (case-insensitive)"),
51
+ strategy: z.string().optional().describe("Filter by strategy name (case-insensitive)"),
61
52
  // Window count mode (convenience parameters)
62
53
  isWindowCount: z
63
54
  .number()
64
55
  .min(2)
65
56
  .default(5)
66
57
  .describe(
67
- "Number of in-sample windows (default: 5). Used to calculate inSampleDays if not explicitly provided."
58
+ "Number of in-sample windows (default: 5). Used to calculate inSampleDays if not explicitly provided.",
68
59
  ),
69
60
  oosWindowCount: z
70
61
  .number()
71
62
  .min(1)
72
63
  .default(1)
73
64
  .describe(
74
- "Number of out-of-sample windows (default: 1). Used to calculate outOfSampleDays if not explicitly provided."
65
+ "Number of out-of-sample windows (default: 1). Used to calculate outOfSampleDays if not explicitly provided.",
75
66
  ),
76
67
  // Explicit days mode (overrides window count calculations)
77
68
  inSampleDays: z
@@ -79,22 +70,20 @@ export function registerAnalysisTools(
79
70
  .min(7)
80
71
  .optional()
81
72
  .describe(
82
- "Explicit in-sample period in days. Overrides isWindowCount calculation if provided."
73
+ "Explicit in-sample period in days. Overrides isWindowCount calculation if provided.",
83
74
  ),
84
75
  outOfSampleDays: z
85
76
  .number()
86
77
  .min(1)
87
78
  .optional()
88
79
  .describe(
89
- "Explicit out-of-sample period in days. Overrides oosWindowCount calculation if provided."
80
+ "Explicit out-of-sample period in days. Overrides oosWindowCount calculation if provided.",
90
81
  ),
91
82
  stepSizeDays: z
92
83
  .number()
93
84
  .min(1)
94
85
  .optional()
95
- .describe(
96
- "Days to slide forward each period. If not provided, equals outOfSampleDays."
97
- ),
86
+ .describe("Days to slide forward each period. If not provided, equals outOfSampleDays."),
98
87
  // Optimization settings
99
88
  optimizationTarget: z
100
89
  .enum([
@@ -137,36 +126,50 @@ export function registerAnalysisTools(
137
126
  dateRangeFrom: z
138
127
  .string()
139
128
  .optional()
140
- .describe("Start date for analysis (ISO format: YYYY-MM-DD). Only include trades on or after this date."),
129
+ .describe(
130
+ "Start date for analysis (ISO format: YYYY-MM-DD). Only include trades on or after this date.",
131
+ ),
141
132
  dateRangeTo: z
142
133
  .string()
143
134
  .optional()
144
- .describe("End date for analysis (ISO format: YYYY-MM-DD). Only include trades on or before this date."),
135
+ .describe(
136
+ "End date for analysis (ISO format: YYYY-MM-DD). Only include trades on or before this date.",
137
+ ),
145
138
  // Performance floor constraints (reject parameter combinations that don't meet minimums)
146
139
  minSharpeRatio: z
147
140
  .number()
148
141
  .optional()
149
- .describe("Minimum Sharpe ratio required during in-sample optimization. Combinations below this are rejected."),
142
+ .describe(
143
+ "Minimum Sharpe ratio required during in-sample optimization. Combinations below this are rejected.",
144
+ ),
150
145
  minProfitFactor: z
151
146
  .number()
152
147
  .min(0)
153
148
  .optional()
154
- .describe("Minimum profit factor required during in-sample optimization. Combinations below this are rejected."),
149
+ .describe(
150
+ "Minimum profit factor required during in-sample optimization. Combinations below this are rejected.",
151
+ ),
155
152
  requirePositiveNetPl: z
156
153
  .boolean()
157
154
  .default(false)
158
- .describe("Require positive net P&L during in-sample optimization. Reject combinations with losses."),
155
+ .describe(
156
+ "Require positive net P&L during in-sample optimization. Reject combinations with losses.",
157
+ ),
159
158
  // Diversification constraints
160
159
  enableCorrelationConstraint: z
161
160
  .boolean()
162
161
  .default(false)
163
- .describe("Enable correlation constraint to reject highly correlated strategy combinations during optimization."),
162
+ .describe(
163
+ "Enable correlation constraint to reject highly correlated strategy combinations during optimization.",
164
+ ),
164
165
  maxCorrelationThreshold: z
165
166
  .number()
166
167
  .min(0)
167
168
  .max(1)
168
169
  .default(0.7)
169
- .describe("Maximum allowed correlation between any strategy pair (default: 0.7). Only used if enableCorrelationConstraint is true."),
170
+ .describe(
171
+ "Maximum allowed correlation between any strategy pair (default: 0.7). Only used if enableCorrelationConstraint is true.",
172
+ ),
170
173
  correlationMethod: z
171
174
  .enum(["kendall", "spearman", "pearson"])
172
175
  .default("kendall")
@@ -174,19 +177,25 @@ export function registerAnalysisTools(
174
177
  enableTailRiskConstraint: z
175
178
  .boolean()
176
179
  .default(false)
177
- .describe("Enable tail risk constraint to reject combinations with high joint tail dependence."),
180
+ .describe(
181
+ "Enable tail risk constraint to reject combinations with high joint tail dependence.",
182
+ ),
178
183
  maxTailDependenceThreshold: z
179
184
  .number()
180
185
  .min(0)
181
186
  .max(1)
182
187
  .default(0.5)
183
- .describe("Maximum allowed tail dependence between any strategy pair (default: 0.5). Only used if enableTailRiskConstraint is true."),
188
+ .describe(
189
+ "Maximum allowed tail dependence between any strategy pair (default: 0.5). Only used if enableTailRiskConstraint is true.",
190
+ ),
184
191
  tailThreshold: z
185
192
  .number()
186
193
  .min(0.01)
187
194
  .max(0.5)
188
195
  .default(0.1)
189
- .describe("Percentile threshold for tail definition (default: 0.1 = worst 10%). Only used if enableTailRiskConstraint is true."),
196
+ .describe(
197
+ "Percentile threshold for tail definition (default: 0.1 = worst 10%). Only used if enableTailRiskConstraint is true.",
198
+ ),
190
199
  diversificationNormalization: z
191
200
  .enum(["raw", "margin", "notional"])
192
201
  .default("raw")
@@ -197,24 +206,21 @@ export function registerAnalysisTools(
197
206
  .describe("Which trade date to use for diversification calculations (default: opened)."),
198
207
  // Parameter ranges for position sizing sweeps
199
208
  parameterRanges: z
200
- .record(
201
- z.string(),
202
- z.array(z.number()).min(3).max(3)
203
- )
209
+ .record(z.string(), z.array(z.number()).min(3).max(3))
204
210
  .optional()
205
211
  .describe(
206
212
  "Parameter ranges for optimization sweep. Format: {paramName: [min, max, step]}. " +
207
- "POSITION SIZING: " +
208
- "'kellyMultiplier' scales P&L by multiplier (e.g., {\"kellyMultiplier\": [0.25, 1.0, 0.25]} tests quarter/half/3-quarter/full Kelly); " +
209
- "'fixedFractionPct' scales relative to 2% baseline (e.g., [1, 4, 1] tests 1-4%); " +
210
- "'fixedContracts' scales relative to avg contracts (e.g., [1, 5, 1] tests 1-5 contracts). " +
211
- "RISK CONSTRAINTS (reject combinations exceeding threshold): " +
212
- "'maxDrawdownPct' max drawdown % (e.g., [15, 25, 5] allows 15-25%); " +
213
- "'maxDailyLossPct' max single-day loss %; " +
214
- "'consecutiveLossLimit' max consecutive losing trades. " +
215
- "STRATEGY WEIGHTS: " +
216
- "'strategy:StrategyName' weight multiplier per strategy (e.g., {\"strategy:IronCondor\": [0, 1, 0.5], \"strategy:Straddle\": [0, 1, 0.5]} tests include/exclude combinations). " +
217
- "Multiple parameters create a grid search across all combinations."
213
+ "POSITION SIZING: " +
214
+ "'kellyMultiplier' scales P&L by multiplier (e.g., {\"kellyMultiplier\": [0.25, 1.0, 0.25]} tests quarter/half/3-quarter/full Kelly); " +
215
+ "'fixedFractionPct' scales relative to 2% baseline (e.g., [1, 4, 1] tests 1-4%); " +
216
+ "'fixedContracts' scales relative to avg contracts (e.g., [1, 5, 1] tests 1-5 contracts). " +
217
+ "RISK CONSTRAINTS (reject combinations exceeding threshold): " +
218
+ "'maxDrawdownPct' max drawdown % (e.g., [15, 25, 5] allows 15-25%); " +
219
+ "'maxDailyLossPct' max single-day loss %; " +
220
+ "'consecutiveLossLimit' max consecutive losing trades. " +
221
+ "STRATEGY WEIGHTS: " +
222
+ '\'strategy:StrategyName\' weight multiplier per strategy (e.g., {"strategy:IronCondor": [0, 1, 0.5], "strategy:Straddle": [0, 1, 0.5]} tests include/exclude combinations). ' +
223
+ "Multiple parameters create a grid search across all combinations.",
218
224
  ),
219
225
  }),
220
226
  },
@@ -257,9 +263,7 @@ export function registerAnalysisTools(
257
263
  // Apply ticker filter (supports both explicit ticker columns and legs-derived symbols)
258
264
  if (tickerFilter) {
259
265
  const tickerLower = tickerFilter.toLowerCase();
260
- trades = trades.filter(
261
- (t) => resolveTradeTicker(t).toLowerCase() === tickerLower
262
- );
266
+ trades = trades.filter((t) => resolveTradeTicker(t).toLowerCase() === tickerLower);
263
267
  }
264
268
 
265
269
  // Apply date range filter
@@ -267,12 +271,8 @@ export function registerAnalysisTools(
267
271
 
268
272
  // Apply selectedStrategies filter if provided (in addition to single strategy filter)
269
273
  if (selectedStrategies && selectedStrategies.length > 0) {
270
- const strategySet = new Set(
271
- selectedStrategies.map((s) => s.toLowerCase())
272
- );
273
- trades = trades.filter((t) =>
274
- strategySet.has(t.strategy.toLowerCase())
275
- );
274
+ const strategySet = new Set(selectedStrategies.map((s) => s.toLowerCase()));
275
+ trades = trades.filter((t) => strategySet.has(t.strategy.toLowerCase()));
276
276
  }
277
277
 
278
278
  if (trades.length < 20) {
@@ -289,15 +289,12 @@ export function registerAnalysisTools(
289
289
 
290
290
  // Calculate date range and window sizes
291
291
  const sortedTrades = [...trades].sort(
292
- (a, b) =>
293
- new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime()
292
+ (a, b) => new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime(),
294
293
  );
295
294
  const firstDate = new Date(sortedTrades[0].dateOpened);
296
- const lastDate = new Date(
297
- sortedTrades[sortedTrades.length - 1].dateOpened
298
- );
295
+ const lastDate = new Date(sortedTrades[sortedTrades.length - 1].dateOpened);
299
296
  const totalDays = Math.ceil(
300
- (lastDate.getTime() - firstDate.getTime()) / (24 * 60 * 60 * 1000)
297
+ (lastDate.getTime() - firstDate.getTime()) / (24 * 60 * 60 * 1000),
301
298
  );
302
299
 
303
300
  // Determine window sizes: explicit days override window count calculations
@@ -320,7 +317,8 @@ export function registerAnalysisTools(
320
317
  }
321
318
 
322
319
  // Build performance floor config if any constraints are set
323
- const hasPerformanceFloor = minSharpeRatio !== undefined || minProfitFactor !== undefined || requirePositiveNetPl;
320
+ const hasPerformanceFloor =
321
+ minSharpeRatio !== undefined || minProfitFactor !== undefined || requirePositiveNetPl;
324
322
  const performanceFloor = hasPerformanceFloor
325
323
  ? {
326
324
  enableMinSharpe: minSharpeRatio !== undefined,
@@ -332,7 +330,8 @@ export function registerAnalysisTools(
332
330
  : undefined;
333
331
 
334
332
  // Build diversification config if any constraints are enabled
335
- const hasDiversificationConstraints = enableCorrelationConstraint || enableTailRiskConstraint;
333
+ const hasDiversificationConstraints =
334
+ enableCorrelationConstraint || enableTailRiskConstraint;
336
335
  const diversificationConfig = hasDiversificationConstraints
337
336
  ? {
338
337
  enableCorrelationConstraint,
@@ -415,8 +414,7 @@ export function registerAnalysisTools(
415
414
  : null,
416
415
  summary: {
417
416
  avgInSamplePerformance: results.summary.avgInSamplePerformance,
418
- avgOutOfSamplePerformance:
419
- results.summary.avgOutOfSamplePerformance,
417
+ avgOutOfSamplePerformance: results.summary.avgOutOfSamplePerformance,
420
418
  degradationFactor: results.summary.degradationFactor,
421
419
  parameterStability: results.summary.parameterStability,
422
420
  robustnessScore: results.summary.robustnessScore,
@@ -464,7 +462,7 @@ export function registerAnalysisTools(
464
462
  isError: true,
465
463
  };
466
464
  }
467
- }
465
+ },
468
466
  );
469
467
 
470
468
  // Tool 2: run_monte_carlo
@@ -475,10 +473,7 @@ export function registerAnalysisTools(
475
473
  "Run Monte Carlo simulation to project future performance and calculate risk metrics",
476
474
  inputSchema: z.object({
477
475
  blockId: z.string().describe("Block folder name"),
478
- strategy: z
479
- .string()
480
- .optional()
481
- .describe("Filter by strategy name (case-insensitive)"),
476
+ strategy: z.string().optional().describe("Filter by strategy name (case-insensitive)"),
482
477
  numSimulations: z
483
478
  .number()
484
479
  .min(100)
@@ -490,34 +485,34 @@ export function registerAnalysisTools(
490
485
  .min(10)
491
486
  .optional()
492
487
  .describe(
493
- "Number of trades/days to project forward. If not specified, uses the number of historical trades."
488
+ "Number of trades/days to project forward. If not specified, uses the number of historical trades.",
494
489
  ),
495
490
  resampleWindow: z
496
491
  .number()
497
492
  .min(5)
498
493
  .optional()
499
494
  .describe(
500
- "Size of resample pool (how many recent trades/days to sample from). If not specified, uses all available data."
495
+ "Size of resample pool (how many recent trades/days to sample from). If not specified, uses all available data.",
501
496
  ),
502
497
  resampleMethod: z
503
498
  .enum(["trades", "daily", "percentage"])
504
499
  .default("trades")
505
500
  .describe(
506
- "What to resample: 'trades' (individual trade P&L), 'daily' (daily aggregated returns), 'percentage' (percentage returns for compounding strategies)"
501
+ "What to resample: 'trades' (individual trade P&L), 'daily' (daily aggregated returns), 'percentage' (percentage returns for compounding strategies)",
507
502
  ),
508
503
  initialCapital: z
509
504
  .number()
510
505
  .positive()
511
506
  .optional()
512
507
  .describe(
513
- "Starting capital for simulations. If not specified, inferred from first trade."
508
+ "Starting capital for simulations. If not specified, inferred from first trade.",
514
509
  ),
515
510
  tradesPerYear: z
516
511
  .number()
517
512
  .min(1)
518
513
  .optional()
519
514
  .describe(
520
- "Expected trades per year for annualization. If not specified, calculated from historical data."
515
+ "Expected trades per year for annualization. If not specified, calculated from historical data.",
521
516
  ),
522
517
  randomSeed: z
523
518
  .number()
@@ -527,7 +522,7 @@ export function registerAnalysisTools(
527
522
  .boolean()
528
523
  .default(false)
529
524
  .describe(
530
- "Normalize trades to 1-lot by dividing P&L by contract count. Useful for comparing different position sizes."
525
+ "Normalize trades to 1-lot by dividing P&L by contract count. Useful for comparing different position sizes.",
531
526
  ),
532
527
  includeWorstCase: z
533
528
  .boolean()
@@ -539,32 +534,32 @@ export function registerAnalysisTools(
539
534
  .max(100)
540
535
  .default(5)
541
536
  .describe(
542
- "Percentage of simulation length that should be max-loss scenarios (0-100, default: 5)"
537
+ "Percentage of simulation length that should be max-loss scenarios (0-100, default: 5)",
543
538
  ),
544
539
  worstCaseMode: z
545
540
  .enum(["pool", "guarantee"])
546
541
  .default("pool")
547
542
  .describe(
548
- "How to inject worst-case: 'pool' adds synthetic losses to resample pool, 'guarantee' ensures worst-case appears in every simulation"
543
+ "How to inject worst-case: 'pool' adds synthetic losses to resample pool, 'guarantee' ensures worst-case appears in every simulation",
549
544
  ),
550
545
  worstCaseSizing: z
551
546
  .enum(["absolute", "relative"])
552
547
  .default("relative")
553
548
  .describe(
554
- "Worst-case sizing: 'absolute' uses historical dollar amounts, 'relative' scales to account capital ratio"
549
+ "Worst-case sizing: 'absolute' uses historical dollar amounts, 'relative' scales to account capital ratio",
555
550
  ),
556
551
  worstCaseBasedOn: z
557
552
  .enum(["simulation", "historical"])
558
553
  .default("simulation")
559
554
  .describe(
560
- "What to base worst-case percentage on: 'simulation' (simulation length) or 'historical' (historical data size)"
555
+ "What to base worst-case percentage on: 'simulation' (simulation length) or 'historical' (historical data size)",
561
556
  ),
562
557
  historicalInitialCapital: z
563
558
  .number()
564
559
  .positive()
565
560
  .optional()
566
561
  .describe(
567
- "Historical initial capital for percentage return calculation. Only needed when filtering strategies from multi-strategy portfolios where fundsAtClose reflects combined portfolio."
562
+ "Historical initial capital for percentage return calculation. Only needed when filtering strategies from multi-strategy portfolios where fundsAtClose reflects combined portfolio.",
568
563
  ),
569
564
  }),
570
565
  },
@@ -607,8 +602,7 @@ export function registerAnalysisTools(
607
602
 
608
603
  // Calculate initial capital and trades per year if not provided
609
604
  const sortedTrades = [...trades].sort(
610
- (a, b) =>
611
- new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime()
605
+ (a, b) => new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime(),
612
606
  );
613
607
  const firstTrade = sortedTrades[0];
614
608
  const lastTrade = sortedTrades[sortedTrades.length - 1];
@@ -620,11 +614,9 @@ export function registerAnalysisTools(
620
614
 
621
615
  // Use provided trades per year or calculate from data
622
616
  const daySpan =
623
- (new Date(lastTrade.dateOpened).getTime() -
624
- new Date(firstTrade.dateOpened).getTime()) /
617
+ (new Date(lastTrade.dateOpened).getTime() - new Date(firstTrade.dateOpened).getTime()) /
625
618
  (24 * 60 * 60 * 1000);
626
- const calculatedTradesPerYear =
627
- daySpan > 0 ? (trades.length / daySpan) * 365 : 252;
619
+ const calculatedTradesPerYear = daySpan > 0 ? (trades.length / daySpan) * 365 : 252;
628
620
  const tradesPerYear = tradesPerYearParam ?? calculatedTradesPerYear;
629
621
 
630
622
  // Use provided simulation length or default to trade count
@@ -716,34 +708,33 @@ export function registerAnalysisTools(
716
708
  isError: true,
717
709
  };
718
710
  }
719
- }
711
+ },
720
712
  );
721
713
 
722
714
  // Tool 3: get_correlation_matrix
723
715
  server.registerTool(
724
716
  "get_correlation_matrix",
725
717
  {
726
- description:
727
- "Calculate correlation matrix between strategies to identify diversification",
718
+ description: "Calculate correlation matrix between strategies to identify diversification",
728
719
  inputSchema: z.object({
729
720
  blockId: z.string().describe("Block folder name"),
730
721
  method: z
731
722
  .enum(["kendall", "spearman", "pearson"])
732
723
  .default("kendall")
733
724
  .describe(
734
- "Correlation method: 'kendall' (robust, rank-based), 'spearman' (rank), 'pearson' (linear)"
725
+ "Correlation method: 'kendall' (robust, rank-based), 'spearman' (rank), 'pearson' (linear)",
735
726
  ),
736
727
  alignment: z
737
728
  .enum(["shared", "zero-pad"])
738
729
  .default("shared")
739
730
  .describe(
740
- "How to handle missing dates: 'shared' uses only days both strategies traded, 'zero-pad' fills missing days with 0"
731
+ "How to handle missing dates: 'shared' uses only days both strategies traded, 'zero-pad' fills missing days with 0",
741
732
  ),
742
733
  normalization: z
743
734
  .enum(["raw", "margin", "notional"])
744
735
  .default("raw")
745
736
  .describe(
746
- "How to normalize returns: 'raw' absolute P&L, 'margin' P&L/margin, 'notional' P&L/notional"
737
+ "How to normalize returns: 'raw' absolute P&L, 'margin' P&L/margin, 'notional' P&L/notional",
747
738
  ),
748
739
  dateBasis: z
749
740
  .enum(["opened", "closed"])
@@ -769,17 +760,23 @@ export function registerAnalysisTools(
769
760
  dateRangeFrom: z
770
761
  .string()
771
762
  .optional()
772
- .describe("Start date for analysis (ISO format: YYYY-MM-DD). Only include trades on or after this date."),
763
+ .describe(
764
+ "Start date for analysis (ISO format: YYYY-MM-DD). Only include trades on or after this date.",
765
+ ),
773
766
  dateRangeTo: z
774
767
  .string()
775
768
  .optional()
776
- .describe("End date for analysis (ISO format: YYYY-MM-DD). Only include trades on or before this date."),
769
+ .describe(
770
+ "End date for analysis (ISO format: YYYY-MM-DD). Only include trades on or before this date.",
771
+ ),
777
772
  highlightThreshold: z
778
773
  .number()
779
774
  .min(0)
780
775
  .max(1)
781
776
  .default(0.7)
782
- .describe("Threshold for highlighting highly correlated pairs (default: 0.7 = |r| > 0.7)"),
777
+ .describe(
778
+ "Threshold for highlighting highly correlated pairs (default: 0.7 = |r| > 0.7)",
779
+ ),
783
780
  }),
784
781
  },
785
782
  async ({
@@ -803,9 +800,7 @@ export function registerAnalysisTools(
803
800
  // Apply ticker filter (supports both explicit ticker columns and legs-derived symbols)
804
801
  if (tickerFilter) {
805
802
  const tickerLower = tickerFilter.toLowerCase();
806
- trades = trades.filter(
807
- (t) => resolveTradeTicker(t).toLowerCase() === tickerLower
808
- );
803
+ trades = trades.filter((t) => resolveTradeTicker(t).toLowerCase() === tickerLower);
809
804
  }
810
805
 
811
806
  // Apply date range filter
@@ -818,9 +813,7 @@ export function registerAnalysisTools(
818
813
  }
819
814
 
820
815
  // Get unique strategies after filtering
821
- const strategies = Array.from(
822
- new Set(trades.map((t) => t.strategy))
823
- ).filter(Boolean);
816
+ const strategies = Array.from(new Set(trades.map((t) => t.strategy))).filter(Boolean);
824
817
 
825
818
  if (strategies.length < 2) {
826
819
  return {
@@ -845,12 +838,20 @@ export function registerAnalysisTools(
845
838
  const analytics = calculateCorrelationAnalytics(matrix, minSamples);
846
839
 
847
840
  // Find highly correlated pairs
848
- const highlyCorrelated: Array<{ pair: [string, string]; value: number; sampleSize: number }> = [];
841
+ const highlyCorrelated: Array<{
842
+ pair: [string, string];
843
+ value: number;
844
+ sampleSize: number;
845
+ }> = [];
849
846
  for (let i = 0; i < matrix.strategies.length; i++) {
850
847
  for (let j = i + 1; j < matrix.strategies.length; j++) {
851
848
  const val = matrix.correlationData[i][j];
852
849
  const sampleSize = matrix.sampleSizes[i][j];
853
- if (!Number.isNaN(val) && Math.abs(val) > highlightThreshold && sampleSize >= minSamples) {
850
+ if (
851
+ !Number.isNaN(val) &&
852
+ Math.abs(val) > highlightThreshold &&
853
+ sampleSize >= minSamples
854
+ ) {
854
855
  highlyCorrelated.push({
855
856
  pair: [matrix.strategies[i], matrix.strategies[j]],
856
857
  value: val,
@@ -861,7 +862,9 @@ export function registerAnalysisTools(
861
862
  }
862
863
 
863
864
  // Brief summary for user display
864
- const avgCorr = Number.isNaN(analytics.averageCorrelation) ? "N/A" : formatRatio(analytics.averageCorrelation);
865
+ const avgCorr = Number.isNaN(analytics.averageCorrelation)
866
+ ? "N/A"
867
+ : formatRatio(analytics.averageCorrelation);
865
868
  const summary = `Correlation: ${blockId} | ${strategies.length} strategies | Avg: ${avgCorr} | High-corr pairs: ${highlyCorrelated.length}`;
866
869
 
867
870
  // Build structured data for Claude reasoning
@@ -914,15 +917,14 @@ export function registerAnalysisTools(
914
917
  isError: true,
915
918
  };
916
919
  }
917
- }
920
+ },
918
921
  );
919
922
 
920
923
  // Tool 4: get_tail_risk
921
924
  server.registerTool(
922
925
  "get_tail_risk",
923
926
  {
924
- description:
925
- "Calculate Gaussian copula tail dependence to identify extreme co-movement risk",
927
+ description: "Calculate Gaussian copula tail dependence to identify extreme co-movement risk",
926
928
  inputSchema: z.object({
927
929
  blockId: z.string().describe("Block folder name"),
928
930
  tailThreshold: z
@@ -931,7 +933,7 @@ export function registerAnalysisTools(
931
933
  .max(0.5)
932
934
  .default(0.1)
933
935
  .describe(
934
- "Percentile threshold for tail events (0.1 = worst 10% of days). Lower = more extreme events only."
936
+ "Percentile threshold for tail events (0.1 = worst 10% of days). Lower = more extreme events only.",
935
937
  ),
936
938
  minTradingDays: z
937
939
  .number()
@@ -942,7 +944,7 @@ export function registerAnalysisTools(
942
944
  .enum(["raw", "margin", "notional"])
943
945
  .default("raw")
944
946
  .describe(
945
- "How to normalize returns: 'raw' absolute P&L, 'margin' P&L/margin, 'notional' P&L/notional"
947
+ "How to normalize returns: 'raw' absolute P&L, 'margin' P&L/margin, 'notional' P&L/notional",
946
948
  ),
947
949
  dateBasis: z
948
950
  .enum(["opened", "closed"])
@@ -959,18 +961,22 @@ export function registerAnalysisTools(
959
961
  dateRangeFrom: z
960
962
  .string()
961
963
  .optional()
962
- .describe("Start date for analysis (ISO format: YYYY-MM-DD). Only include trades on or after this date."),
964
+ .describe(
965
+ "Start date for analysis (ISO format: YYYY-MM-DD). Only include trades on or after this date.",
966
+ ),
963
967
  dateRangeTo: z
964
968
  .string()
965
969
  .optional()
966
- .describe("End date for analysis (ISO format: YYYY-MM-DD). Only include trades on or before this date."),
970
+ .describe(
971
+ "End date for analysis (ISO format: YYYY-MM-DD). Only include trades on or before this date.",
972
+ ),
967
973
  varianceThreshold: z
968
974
  .number()
969
975
  .min(0.5)
970
976
  .max(0.99)
971
977
  .default(0.8)
972
978
  .describe(
973
- "Variance threshold for determining effective factors (0.8 = 80% variance explained)"
979
+ "Variance threshold for determining effective factors (0.8 = 80% variance explained)",
974
980
  ),
975
981
  }),
976
982
  },
@@ -991,9 +997,7 @@ export function registerAnalysisTools(
991
997
  const trades = block.trades;
992
998
 
993
999
  // Get unique strategies
994
- const strategies = Array.from(
995
- new Set(trades.map((t) => t.strategy))
996
- ).filter(Boolean);
1000
+ const strategies = Array.from(new Set(trades.map((t) => t.strategy))).filter(Boolean);
997
1001
 
998
1002
  if (strategies.length < 2) {
999
1003
  return {
@@ -1022,8 +1026,12 @@ export function registerAnalysisTools(
1022
1026
  });
1023
1027
 
1024
1028
  // Determine risk level for summary
1025
- const riskLevel = result.analytics.averageJointTailRisk > 0.5 ? "HIGH" :
1026
- result.analytics.averageJointTailRisk > 0.3 ? "MODERATE" : "LOW";
1029
+ const riskLevel =
1030
+ result.analytics.averageJointTailRisk > 0.5
1031
+ ? "HIGH"
1032
+ : result.analytics.averageJointTailRisk > 0.3
1033
+ ? "MODERATE"
1034
+ : "LOW";
1027
1035
 
1028
1036
  // Brief summary for user display
1029
1037
  const summary = `Tail Risk: ${blockId} | ${result.strategies.length} strategies | Avg Joint Risk: ${formatRatio(result.analytics.averageJointTailRisk)} | Level: ${riskLevel}`;
@@ -1077,30 +1085,28 @@ export function registerAnalysisTools(
1077
1085
  isError: true,
1078
1086
  };
1079
1087
  }
1080
- }
1088
+ },
1081
1089
  );
1082
1090
 
1083
1091
  // Tool 5: get_position_sizing
1084
1092
  server.registerTool(
1085
1093
  "get_position_sizing",
1086
1094
  {
1087
- description:
1088
- "Calculate Kelly criterion position sizing for optimal capital allocation",
1095
+ description: "Calculate Kelly criterion position sizing for optimal capital allocation",
1089
1096
  inputSchema: z.object({
1090
1097
  blockId: z.string().describe("Block folder name"),
1091
- capitalBase: z
1092
- .number()
1093
- .positive()
1094
- .describe("Starting capital in dollars"),
1098
+ capitalBase: z.number().positive().describe("Starting capital in dollars"),
1095
1099
  strategy: z
1096
1100
  .string()
1097
1101
  .optional()
1098
- .describe("Filter to a specific strategy name (case-insensitive). If provided, only calculates Kelly for that strategy."),
1102
+ .describe(
1103
+ "Filter to a specific strategy name (case-insensitive). If provided, only calculates Kelly for that strategy.",
1104
+ ),
1099
1105
  kellyFraction: z
1100
1106
  .enum(["full", "half", "quarter"])
1101
1107
  .default("half")
1102
1108
  .describe(
1103
- "Kelly fraction to use: 'full' (100%), 'half' (50%, recommended), 'quarter' (25%, conservative)"
1109
+ "Kelly fraction to use: 'full' (100%), 'half' (50%, recommended), 'quarter' (25%, conservative)",
1104
1110
  ),
1105
1111
  maxAllocationPct: z
1106
1112
  .number()
@@ -1112,23 +1118,27 @@ export function registerAnalysisTools(
1112
1118
  .boolean()
1113
1119
  .default(true)
1114
1120
  .describe(
1115
- "Include strategies with negative Kelly in output (useful for identifying loss-reduction opportunities)"
1121
+ "Include strategies with negative Kelly in output (useful for identifying loss-reduction opportunities)",
1116
1122
  ),
1117
1123
  useMarginReturns: z
1118
1124
  .boolean()
1119
1125
  .default(false)
1120
1126
  .describe(
1121
- "Prefer percentage returns based on margin requirement instead of absolute P&L. More appropriate for compounding strategies with variable position sizes."
1127
+ "Prefer percentage returns based on margin requirement instead of absolute P&L. More appropriate for compounding strategies with variable position sizes.",
1122
1128
  ),
1123
1129
  minTrades: z
1124
1130
  .number()
1125
1131
  .min(1)
1126
1132
  .default(10)
1127
- .describe("Minimum trades required per strategy for valid Kelly calculation (default: 10)"),
1133
+ .describe(
1134
+ "Minimum trades required per strategy for valid Kelly calculation (default: 10)",
1135
+ ),
1128
1136
  sortBy: z
1129
1137
  .enum(["name", "kelly", "winRate", "payoffRatio", "allocation"])
1130
1138
  .default("kelly")
1131
- .describe("Sort strategies by: 'name', 'kelly' percentage, 'winRate', 'payoffRatio', or 'allocation' amount"),
1139
+ .describe(
1140
+ "Sort strategies by: 'name', 'kelly' percentage, 'winRate', 'payoffRatio', or 'allocation' amount",
1141
+ ),
1132
1142
  sortOrder: z
1133
1143
  .enum(["asc", "desc"])
1134
1144
  .default("desc")
@@ -1181,7 +1191,7 @@ export function registerAnalysisTools(
1181
1191
  const skippedStrategies: string[] = [];
1182
1192
  for (const [strategyName, kelly] of strategyKelly.entries()) {
1183
1193
  const strategyTrades = trades.filter(
1184
- (t) => t.strategy.toLowerCase() === strategyName.toLowerCase()
1194
+ (t) => t.strategy.toLowerCase() === strategyName.toLowerCase(),
1185
1195
  );
1186
1196
  if (strategyTrades.length >= minTrades) {
1187
1197
  filteredStrategyKelly.set(strategyName, kelly);
@@ -1210,21 +1220,19 @@ export function registerAnalysisTools(
1210
1220
  }
1211
1221
 
1212
1222
  // Calculate raw allocation (full Kelly)
1213
- const rawAllocation = kelly.hasValidKelly
1214
- ? capitalBase * Math.max(0, kelly.fraction)
1215
- : 0;
1223
+ const rawAllocation = kelly.hasValidKelly ? capitalBase * Math.max(0, kelly.fraction) : 0;
1216
1224
 
1217
1225
  // Apply Kelly multiplier and cap
1218
1226
  const adjustedFraction = Math.min(
1219
1227
  kelly.fraction * kellyMultiplier,
1220
- maxAllocationFraction
1228
+ maxAllocationFraction,
1221
1229
  );
1222
1230
  const adjustedAllocation = kelly.hasValidKelly
1223
1231
  ? capitalBase * Math.max(0, adjustedFraction)
1224
1232
  : 0;
1225
1233
 
1226
1234
  const tradeCount = trades.filter(
1227
- (t) => t.strategy.toLowerCase() === strategyName.toLowerCase()
1235
+ (t) => t.strategy.toLowerCase() === strategyName.toLowerCase(),
1228
1236
  ).length;
1229
1237
 
1230
1238
  strategyResults.push({
@@ -1274,9 +1282,17 @@ export function registerAnalysisTools(
1274
1282
  }
1275
1283
 
1276
1284
  // Brief summary for user display
1277
- const kellyDisplay = portfolioKelly.hasValidKelly ? formatPercent(portfolioKelly.percent) : "N/A";
1285
+ const kellyDisplay = portfolioKelly.hasValidKelly
1286
+ ? formatPercent(portfolioKelly.percent)
1287
+ : "N/A";
1278
1288
  const allocDisplay = portfolioKelly.hasValidKelly
1279
- ? formatCurrency(capitalBase * Math.max(0, Math.min(portfolioKelly.fraction * kellyMultiplier, maxAllocationFraction)))
1289
+ ? formatCurrency(
1290
+ capitalBase *
1291
+ Math.max(
1292
+ 0,
1293
+ Math.min(portfolioKelly.fraction * kellyMultiplier, maxAllocationFraction),
1294
+ ),
1295
+ )
1280
1296
  : "N/A";
1281
1297
  const summary = `Position Sizing: ${blockId}${strategy ? ` (${strategy})` : ""} | Kelly: ${kellyDisplay} | ${kellyFraction} allocation: ${allocDisplay} | ${strategyResults.length} strategies`;
1282
1298
 
@@ -1309,7 +1325,10 @@ export function registerAnalysisTools(
1309
1325
  : null,
1310
1326
  recommendedAllocation: portfolioKelly.hasValidKelly
1311
1327
  ? capitalBase *
1312
- Math.max(0, Math.min(portfolioKelly.fraction * kellyMultiplier, maxAllocationFraction))
1328
+ Math.max(
1329
+ 0,
1330
+ Math.min(portfolioKelly.fraction * kellyMultiplier, maxAllocationFraction),
1331
+ )
1313
1332
  : null,
1314
1333
  fullKelly: portfolioKelly.hasValidKelly
1315
1334
  ? Math.min(portfolioKelly.fraction, maxAllocationFraction)
@@ -1327,25 +1346,27 @@ export function registerAnalysisTools(
1327
1346
  calculationMethod: portfolioKelly.calculationMethod ?? null,
1328
1347
  hasUnrealisticValues: portfolioKelly.hasUnrealisticValues ?? false,
1329
1348
  },
1330
- strategies: strategyResults.map(({ name, kelly, rawAllocation, adjustedAllocation, tradeCount }) => ({
1331
- name,
1332
- tradeCount,
1333
- winRate: kelly.winRate,
1334
- avgWin: kelly.avgWin,
1335
- avgLoss: kelly.avgLoss,
1336
- payoffRatio: kelly.payoffRatio,
1337
- rawKellyFraction: kelly.fraction,
1338
- rawKellyPercent: kelly.percent,
1339
- hasValidKelly: kelly.hasValidKelly,
1340
- rawAllocation,
1341
- adjustedAllocation,
1342
- // Margin-based metrics
1343
- avgWinPct: kelly.avgWinPct ?? null,
1344
- avgLossPct: kelly.avgLossPct ?? null,
1345
- normalizedKellyPct: kelly.normalizedKellyPct ?? null,
1346
- calculationMethod: kelly.calculationMethod ?? null,
1347
- hasUnrealisticValues: kelly.hasUnrealisticValues ?? false,
1348
- })),
1349
+ strategies: strategyResults.map(
1350
+ ({ name, kelly, rawAllocation, adjustedAllocation, tradeCount }) => ({
1351
+ name,
1352
+ tradeCount,
1353
+ winRate: kelly.winRate,
1354
+ avgWin: kelly.avgWin,
1355
+ avgLoss: kelly.avgLoss,
1356
+ payoffRatio: kelly.payoffRatio,
1357
+ rawKellyFraction: kelly.fraction,
1358
+ rawKellyPercent: kelly.percent,
1359
+ hasValidKelly: kelly.hasValidKelly,
1360
+ rawAllocation,
1361
+ adjustedAllocation,
1362
+ // Margin-based metrics
1363
+ avgWinPct: kelly.avgWinPct ?? null,
1364
+ avgLossPct: kelly.avgLossPct ?? null,
1365
+ normalizedKellyPct: kelly.normalizedKellyPct ?? null,
1366
+ calculationMethod: kelly.calculationMethod ?? null,
1367
+ hasUnrealisticValues: kelly.hasUnrealisticValues ?? false,
1368
+ }),
1369
+ ),
1349
1370
  skippedStrategies,
1350
1371
  warnings,
1351
1372
  };
@@ -1362,6 +1383,6 @@ export function registerAnalysisTools(
1362
1383
  isError: true,
1363
1384
  };
1364
1385
  }
1365
- }
1386
+ },
1366
1387
  );
1367
1388
  }