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.
- package/README.md +85 -80
- package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
- package/dist/chunk-27S67XW3.js.map +1 -0
- package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
- package/dist/chunk-2E63THNI.js.map +1 -0
- package/dist/{chunk-QTTR7AAW.js → chunk-2I3S2ZLT.js} +325 -84
- package/dist/chunk-2I3S2ZLT.js.map +1 -0
- package/dist/{chunk-4BLCXNQ6.js → chunk-4HZLVUOZ.js} +179 -183
- package/dist/chunk-4HZLVUOZ.js.map +1 -0
- package/dist/{chunk-XXYOUIZY.js → chunk-NWNSKALN.js} +63 -80
- package/dist/chunk-NWNSKALN.js.map +1 -0
- package/dist/{chunk-PRAYH3RT.js → chunk-WNTYA7ER.js} +29 -12
- package/dist/chunk-WNTYA7ER.js.map +1 -0
- package/dist/{chunk-BKQ4PM4Y.js → chunk-YZA4RS76.js} +24 -18
- package/dist/chunk-YZA4RS76.js.map +1 -0
- package/dist/{chunk-W2PP3LEH.js → chunk-ZTJXC3LS.js} +17 -8
- package/dist/chunk-ZTJXC3LS.js.map +1 -0
- package/dist/daily-log-processor-MZW2XBZY.js +9 -0
- package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
- package/dist/iv-solver-worker.js +9 -1
- package/dist/market-provider-6U33TQUT.js +16 -0
- package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
- package/dist/{sync-PO4IPCYV.js → sync-3UBC37VW.js} +7 -7
- package/dist/test-exports.js +876 -483
- package/dist/test-exports.js.map +1 -1
- package/dist/trade-processor-DKDCWGKD.js +9 -0
- package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
- package/package.json +2 -6
- package/server/{chunk-CSDVJPBB.js → chunk-536NSFED.js} +29 -12
- package/server/chunk-536NSFED.js.map +1 -0
- package/server/{chunk-WA5AAPCH.js → chunk-A3UKD64A.js} +17 -8
- package/server/chunk-A3UKD64A.js.map +1 -0
- package/server/{chunk-PNKG7RY7.js → chunk-OMUDNJBJ.js} +63 -80
- package/server/chunk-OMUDNJBJ.js.map +1 -0
- package/server/{chunk-72GKJE2U.js → chunk-RJDJHZ5Y.js} +934 -335
- package/server/chunk-RJDJHZ5Y.js.map +1 -0
- package/server/{chunk-NZO6PT64.js → chunk-RM4B2VKS.js} +167 -177
- package/server/chunk-RM4B2VKS.js.map +1 -0
- package/server/{chunk-5EBXHT6C.js → chunk-SY6GDNVI.js} +24 -18
- package/server/chunk-SY6GDNVI.js.map +1 -0
- package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
- package/server/chunk-T66KH2XH.js.map +1 -0
- package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
- package/server/chunk-W5E7FHC4.js.map +1 -0
- package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
- package/server/config-DK7KOMNL.js.map +1 -0
- package/server/daily-log-processor-B2VPU2FQ.js +10 -0
- package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
- package/server/http-server.js +2 -5
- package/server/index.js +2497 -2029
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +9 -1
- package/server/market-provider-L56RFHAC.js +17 -0
- package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
- package/server/{sync-QFI5L7S7.js → sync-CENKDN53.js} +7 -7
- package/server/trade-processor-3PI4LHZI.js +10 -0
- package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
- package/src/auth/clients-store.ts +4 -4
- package/src/auth/code-store.ts +1 -1
- package/src/auth/config.ts +11 -12
- package/src/auth/login-page.ts +8 -10
- package/src/auth/provider.ts +35 -30
- package/src/auth/token.ts +17 -15
- package/src/db/backtest-schemas.ts +38 -8
- package/src/db/connection.ts +107 -40
- package/src/db/data-root.ts +22 -0
- package/src/db/index.ts +37 -5
- package/src/db/json-adapters.ts +22 -31
- package/src/db/json-migration.ts +11 -15
- package/src/db/json-store.ts +5 -3
- package/src/db/market-datasets.ts +48 -15
- package/src/db/market-schemas.ts +11 -5
- package/src/db/market-views.ts +119 -32
- package/src/db/parquet-writer.ts +7 -7
- package/src/db/profile-schemas.ts +39 -22
- package/src/db/schemas.ts +2 -2
- package/src/http-server.ts +7 -12
- package/src/index.ts +8 -9
- package/src/market/ingestor/index.ts +1 -4
- package/src/market/ingestor/market-ingestor.ts +140 -67
- package/src/market/ingestor/types.ts +1 -3
- package/src/market/stores/chain-sql.ts +2 -8
- package/src/market/stores/chain-store.ts +1 -4
- package/src/market/stores/duckdb-chain-store.ts +3 -14
- package/src/market/stores/duckdb-enriched-store.ts +3 -13
- package/src/market/stores/duckdb-quote-store.ts +10 -27
- package/src/market/stores/duckdb-spot-store.ts +4 -20
- package/src/market/stores/enriched-sql.ts +3 -9
- package/src/market/stores/enriched-store.ts +2 -2
- package/src/market/stores/parquet-chain-store.ts +5 -22
- package/src/market/stores/parquet-enriched-store.ts +4 -16
- package/src/market/stores/parquet-oi-daily-store.ts +3 -14
- package/src/market/stores/parquet-quote-store.ts +9 -29
- package/src/market/stores/parquet-spot-store.ts +14 -33
- package/src/market/stores/quote-store.ts +2 -10
- package/src/market/stores/spot-sql.ts +3 -15
- package/src/market/stores/spot-store.ts +1 -1
- package/src/market/tickers/defaults.json +6 -6
- package/src/market/tickers/loader.ts +2 -7
- package/src/market/tickers/registry.ts +5 -15
- package/src/market/tickers/schemas.ts +2 -6
- package/src/models/strategy-profile.ts +39 -39
- package/src/sync/block-sync.ts +44 -65
- package/src/sync/index.ts +1 -4
- package/src/sync/metadata.ts +14 -20
- package/src/test-exports.ts +139 -123
- package/src/tools/analysis.ts +180 -159
- package/src/tools/batch-exit-analysis.ts +100 -59
- package/src/tools/blocks/analysis.ts +116 -101
- package/src/tools/blocks/comparison.ts +281 -364
- package/src/tools/blocks/core.ts +208 -253
- package/src/tools/blocks/health.ts +571 -563
- package/src/tools/blocks/index.ts +2 -0
- package/src/tools/blocks/paired-comparison.ts +425 -0
- package/src/tools/blocks/similarity.ts +316 -283
- package/src/tools/edge-decay.ts +124 -197
- package/src/tools/exit-analysis.ts +130 -77
- package/src/tools/greeks-attribution.ts +84 -35
- package/src/tools/guides.ts +4 -6
- package/src/tools/imports.ts +11 -14
- package/src/tools/market-data.ts +1067 -757
- package/src/tools/market-enrichment.ts +3 -3
- package/src/tools/market-fetch.ts +148 -67
- package/src/tools/market-imports.ts +12 -12
- package/src/tools/middleware/sync-middleware.ts +5 -6
- package/src/tools/performance.ts +185 -302
- package/src/tools/profile-analysis.ts +52 -66
- package/src/tools/profiles.ts +106 -69
- package/src/tools/regime-advisor.ts +20 -45
- package/src/tools/replay.ts +81 -77
- package/src/tools/reports/discrepancies.ts +298 -328
- package/src/tools/reports/fields.ts +7 -25
- package/src/tools/reports/helpers.ts +18 -49
- package/src/tools/reports/predictive.ts +27 -70
- package/src/tools/reports/slippage-trends.ts +315 -345
- package/src/tools/reports/slippage.ts +1 -4
- package/src/tools/reports/strategy-matches.ts +399 -441
- package/src/tools/schema.ts +43 -40
- package/src/tools/shared/filters.ts +3 -9
- package/src/tools/snapshot.ts +9 -30
- package/src/tools/sql.ts +15 -14
- package/src/tools/tickers.ts +1 -4
- package/src/utils/batch-exit-analysis.ts +31 -42
- package/src/utils/black-scholes.ts +39 -29
- package/src/utils/block-loader.ts +69 -83
- package/src/utils/calibration-probe.ts +3 -4
- package/src/utils/chain-loader.ts +3 -3
- package/src/utils/csv-discovery.ts +16 -22
- package/src/utils/data-quality.ts +24 -36
- package/src/utils/exit-triggers.ts +91 -96
- package/src/utils/field-timing.ts +94 -79
- package/src/utils/filter-predicates.ts +13 -9
- package/src/utils/flatfile-importer.ts +94 -64
- package/src/utils/greeks-decomposition.ts +152 -100
- package/src/utils/iv-solver-pool.ts +55 -25
- package/src/utils/iv-solver-worker.ts +5 -5
- package/src/utils/market-enricher.ts +528 -497
- package/src/utils/market-importer.ts +31 -12
- package/src/utils/market-provider.ts +21 -23
- package/src/utils/massive-tier.ts +5 -7
- package/src/utils/migrate-option-data-helpers.ts +2 -8
- package/src/utils/option-quote-greeks.ts +25 -31
- package/src/utils/option-time.ts +4 -8
- package/src/utils/output-formatter.ts +1 -4
- package/src/utils/provider-capabilities.ts +1 -4
- package/src/utils/providers/massive.ts +59 -93
- package/src/utils/providers/thetadata/backfill.ts +14 -23
- package/src/utils/providers/thetadata/client.ts +12 -8
- package/src/utils/providers/thetadata/decode.ts +2 -20
- package/src/utils/providers/thetadata/endpoints.ts +32 -40
- package/src/utils/providers/thetadata/join.ts +11 -10
- package/src/utils/providers/thetadata/proto.ts +12 -10
- package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
- package/src/utils/providers/thetadata.ts +11 -10
- package/src/utils/quote-enricher.ts +4 -4
- package/src/utils/quote-parquet-projection.ts +3 -11
- package/src/utils/sample-date-selector.ts +3 -5
- package/src/utils/schema-metadata.ts +102 -70
- package/src/utils/ticker.ts +5 -9
- package/src/utils/trade-replay.ts +77 -68
- package/dist/chunk-4BLCXNQ6.js.map +0 -1
- package/dist/chunk-BKQ4PM4Y.js.map +0 -1
- package/dist/chunk-FGZH632F.js.map +0 -1
- package/dist/chunk-LDKTV7GW.js.map +0 -1
- package/dist/chunk-PRAYH3RT.js.map +0 -1
- package/dist/chunk-QTTR7AAW.js.map +0 -1
- package/dist/chunk-W2PP3LEH.js.map +0 -1
- package/dist/chunk-XXYOUIZY.js.map +0 -1
- package/dist/daily-log-processor-6MWJ23JK.js +0 -9
- package/dist/market-provider-VDRJUEF2.js +0 -16
- package/dist/trade-processor-NHU4VWRX.js +0 -9
- package/manifest.json +0 -141
- package/server/chunk-5EBXHT6C.js.map +0 -1
- package/server/chunk-72GKJE2U.js.map +0 -1
- package/server/chunk-CSDVJPBB.js.map +0 -1
- package/server/chunk-FBNDMCT5.js.map +0 -1
- package/server/chunk-NRFXAJF7.js.map +0 -1
- package/server/chunk-NZO6PT64.js.map +0 -1
- package/server/chunk-PNKG7RY7.js.map +0 -1
- package/server/chunk-WA5AAPCH.js.map +0 -1
- package/server/config-6IZXEFEX.js.map +0 -1
- package/server/daily-log-processor-Y3PVSVBM.js +0 -10
- package/server/market-provider-VOYYVYWT.js +0 -17
- package/server/trade-processor-JWVS37KM.js +0 -10
- /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-MZW2XBZY.js.map} +0 -0
- /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
- /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-6U33TQUT.js.map} +0 -0
- /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
- /package/dist/{sync-PO4IPCYV.js.map → sync-3UBC37VW.js.map} +0 -0
- /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-DKDCWGKD.js.map} +0 -0
- /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
- /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-B2VPU2FQ.js.map} +0 -0
- /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
- /package/server/{market-provider-VOYYVYWT.js.map → market-provider-L56RFHAC.js.map} +0 -0
- /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
- /package/server/{sync-QFI5L7S7.js.map → sync-CENKDN53.js.map} +0 -0
- /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-3PI4LHZI.js.map} +0 -0
- /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
package/src/tools/analysis.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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<{
|
|
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 (
|
|
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)
|
|
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(
|
|
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(
|
|
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 =
|
|
1026
|
-
result.analytics.averageJointTailRisk > 0.
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
1285
|
+
const kellyDisplay = portfolioKelly.hasValidKelly
|
|
1286
|
+
? formatPercent(portfolioKelly.percent)
|
|
1287
|
+
: "N/A";
|
|
1278
1288
|
const allocDisplay = portfolioKelly.hasValidKelly
|
|
1279
|
-
? formatCurrency(
|
|
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(
|
|
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(
|
|
1331
|
-
name,
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
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
|
}
|