tradeblocks-mcp 3.0.2 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -68
- 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-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
- package/dist/chunk-6S37CXUA.js.map +1 -0
- package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
- package/dist/chunk-AP7IHUUR.js.map +1 -0
- package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
- package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
- package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
- package/dist/chunk-SEUZYQGQ.js.map +1 -0
- package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
- package/dist/chunk-UBUC5A66.js.map +1 -0
- package/dist/{chunk-XXYOUIZY.js → chunk-VDU25Z6X.js} +63 -80
- package/dist/chunk-VDU25Z6X.js.map +1 -0
- package/dist/daily-log-processor-BY3ISY6K.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-H3ARIVZ4.js +16 -0
- package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
- package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
- package/dist/test-exports.js +358 -254
- package/dist/test-exports.js.map +1 -1
- package/dist/trade-processor-EYA3I3XB.js +9 -0
- package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
- package/manifest.json +3 -11
- package/package.json +1 -1
- package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
- package/server/chunk-4P7D7YZP.js.map +1 -0
- package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
- package/server/chunk-BOPHW5M6.js.map +1 -0
- package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
- package/server/chunk-GH2552SE.js.map +1 -0
- package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
- package/server/chunk-OBYKFW2B.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/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
- package/server/chunk-YUCOAJ4Z.js.map +1 -0
- package/server/{chunk-PNKG7RY7.js → chunk-ZBJCF4ZG.js} +63 -80
- package/server/chunk-ZBJCF4ZG.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-ENEUT22A.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 +1771 -1538
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +9 -1
- package/server/market-provider-B437HKLW.js +17 -0
- package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
- package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
- package/server/trade-processor-L3PIQ5TG.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 +12 -4
- package/src/db/connection.ts +107 -40
- 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 +127 -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/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 +9 -12
- 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 +68 -82
- package/src/utils/calibration-probe.ts +1 -2
- 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-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/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-BY3ISY6K.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-H3ARIVZ4.js.map} +0 -0
- /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
- /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
- /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.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-ENEUT22A.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-B437HKLW.js.map} +0 -0
- /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
- /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
- /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.js.map} +0 -0
- /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
|
@@ -22,10 +22,7 @@ import { withSyncedBlock } from "../middleware/sync-middleware.ts";
|
|
|
22
22
|
/**
|
|
23
23
|
* Register analysis block tools
|
|
24
24
|
*/
|
|
25
|
-
export function registerAnalysisBlockTools(
|
|
26
|
-
server: McpServer,
|
|
27
|
-
baseDir: string
|
|
28
|
-
): void {
|
|
25
|
+
export function registerAnalysisBlockTools(server: McpServer, baseDir: string): void {
|
|
29
26
|
const calculator = new PortfolioStatsCalculator();
|
|
30
27
|
|
|
31
28
|
// Tool 7: stress_test
|
|
@@ -40,7 +37,7 @@ export function registerAnalysisBlockTools(
|
|
|
40
37
|
.array(z.string())
|
|
41
38
|
.optional()
|
|
42
39
|
.describe(
|
|
43
|
-
"Specific scenario names to test (e.g., 'covid_crash', 'bear_2022'). If omitted, runs all built-in scenarios."
|
|
40
|
+
"Specific scenario names to test (e.g., 'covid_crash', 'bear_2022'). If omitted, runs all built-in scenarios.",
|
|
44
41
|
),
|
|
45
42
|
customScenarios: z
|
|
46
43
|
.array(
|
|
@@ -48,7 +45,7 @@ export function registerAnalysisBlockTools(
|
|
|
48
45
|
name: z.string().describe("Custom scenario name"),
|
|
49
46
|
startDate: z.string().describe("Start date (YYYY-MM-DD)"),
|
|
50
47
|
endDate: z.string().describe("End date (YYYY-MM-DD)"),
|
|
51
|
-
})
|
|
48
|
+
}),
|
|
52
49
|
)
|
|
53
50
|
.optional()
|
|
54
51
|
.describe("User-defined scenarios with custom date ranges"),
|
|
@@ -57,21 +54,18 @@ export function registerAnalysisBlockTools(
|
|
|
57
54
|
.optional()
|
|
58
55
|
.default(false)
|
|
59
56
|
.describe(
|
|
60
|
-
"Include scenarios with no trades in the results. Default false - only shows scenarios with data coverage."
|
|
57
|
+
"Include scenarios with no trades in the results. Default false - only shows scenarios with data coverage.",
|
|
61
58
|
),
|
|
62
59
|
}),
|
|
63
60
|
},
|
|
64
|
-
withSyncedBlock(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
const block = await loadBlock(baseDir, blockId);
|
|
61
|
+
withSyncedBlock(baseDir, async ({ blockId, scenarios, customScenarios, includeEmpty }) => {
|
|
62
|
+
try {
|
|
63
|
+
const block = await loadBlock(baseDir, blockId);
|
|
69
64
|
const trades = block.trades;
|
|
70
65
|
|
|
71
66
|
// Get portfolio date range for context and pre-filtering
|
|
72
67
|
const sortedTrades = [...trades].sort(
|
|
73
|
-
(a, b) =>
|
|
74
|
-
new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime()
|
|
68
|
+
(a, b) => new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime(),
|
|
75
69
|
);
|
|
76
70
|
const portfolioStartDate = sortedTrades[0]?.dateOpened
|
|
77
71
|
? new Date(sortedTrades[0].dateOpened).toISOString().split("T")[0]
|
|
@@ -82,15 +76,19 @@ export function registerAnalysisBlockTools(
|
|
|
82
76
|
: null;
|
|
83
77
|
|
|
84
78
|
// Build list of scenarios to run
|
|
85
|
-
const scenariosToRun: Array<{
|
|
79
|
+
const scenariosToRun: Array<{
|
|
80
|
+
name: string;
|
|
81
|
+
startDate: string;
|
|
82
|
+
endDate: string;
|
|
83
|
+
description: string;
|
|
84
|
+
isCustom: boolean;
|
|
85
|
+
}> = [];
|
|
86
86
|
const preFilteredScenarioNames: string[] = [];
|
|
87
87
|
|
|
88
88
|
// Add built-in scenarios
|
|
89
89
|
if (scenarios && scenarios.length > 0) {
|
|
90
90
|
// Validate requested scenarios exist
|
|
91
|
-
const invalidScenarios = scenarios.filter(
|
|
92
|
-
(s) => !STRESS_SCENARIOS[s]
|
|
93
|
-
);
|
|
91
|
+
const invalidScenarios = scenarios.filter((s) => !STRESS_SCENARIOS[s]);
|
|
94
92
|
if (invalidScenarios.length > 0) {
|
|
95
93
|
return {
|
|
96
94
|
content: [
|
|
@@ -152,8 +150,23 @@ export function registerAnalysisBlockTools(
|
|
|
152
150
|
}
|
|
153
151
|
|
|
154
152
|
// Calculate stats for each scenario
|
|
155
|
-
type ScenarioStats = {
|
|
156
|
-
|
|
153
|
+
type ScenarioStats = {
|
|
154
|
+
netPl: number;
|
|
155
|
+
winRate: number;
|
|
156
|
+
maxDrawdown: number;
|
|
157
|
+
profitFactor: number | null;
|
|
158
|
+
avgWin: number | null;
|
|
159
|
+
avgLoss: number | null;
|
|
160
|
+
};
|
|
161
|
+
const scenarioResults: Array<{
|
|
162
|
+
name: string;
|
|
163
|
+
description: string;
|
|
164
|
+
dateRange: { start: string; end: string };
|
|
165
|
+
tradeCount: number;
|
|
166
|
+
stats: ScenarioStats | null;
|
|
167
|
+
isCustom: boolean;
|
|
168
|
+
noCoverage?: boolean;
|
|
169
|
+
}> = [];
|
|
157
170
|
|
|
158
171
|
let worstScenario: { name: string; netPl: number } | null = null;
|
|
159
172
|
let bestScenario: { name: string; netPl: number } | null = null;
|
|
@@ -163,11 +176,7 @@ export function registerAnalysisBlockTools(
|
|
|
163
176
|
|
|
164
177
|
for (const scenario of scenariosToRun) {
|
|
165
178
|
// Filter trades to scenario date range
|
|
166
|
-
const scenarioTrades = filterByDateRange(
|
|
167
|
-
trades,
|
|
168
|
-
scenario.startDate,
|
|
169
|
-
scenario.endDate
|
|
170
|
-
);
|
|
179
|
+
const scenarioTrades = filterByDateRange(trades, scenario.startDate, scenario.endDate);
|
|
171
180
|
|
|
172
181
|
if (scenarioTrades.length === 0) {
|
|
173
182
|
// Genuine coverage gap (had date overlap but zero trades)
|
|
@@ -189,7 +198,7 @@ export function registerAnalysisBlockTools(
|
|
|
189
198
|
const stats = calculator.calculatePortfolioStats(
|
|
190
199
|
scenarioTrades,
|
|
191
200
|
undefined, // No daily logs
|
|
192
|
-
true // Force trade-based calculations
|
|
201
|
+
true, // Force trade-based calculations
|
|
193
202
|
);
|
|
194
203
|
|
|
195
204
|
scenarioResults.push({
|
|
@@ -226,7 +235,9 @@ export function registerAnalysisBlockTools(
|
|
|
226
235
|
scenariosWithTrades,
|
|
227
236
|
scenariosSkipped,
|
|
228
237
|
...(skippedScenarioNames.length > 0 ? { skippedScenarios: skippedScenarioNames } : {}),
|
|
229
|
-
...(preFilteredScenarioNames.length > 0
|
|
238
|
+
...(preFilteredScenarioNames.length > 0
|
|
239
|
+
? { preFilteredScenarios: preFilteredScenarioNames }
|
|
240
|
+
: {}),
|
|
230
241
|
worstScenario: worstScenario?.name ?? null,
|
|
231
242
|
bestScenario: bestScenario?.name ?? null,
|
|
232
243
|
portfolioDateRange: {
|
|
@@ -237,9 +248,7 @@ export function registerAnalysisBlockTools(
|
|
|
237
248
|
|
|
238
249
|
// Brief summary for user display
|
|
239
250
|
const skippedNote =
|
|
240
|
-
scenariosSkipped > 0
|
|
241
|
-
? ` (${scenariosSkipped} skipped - no data coverage)`
|
|
242
|
-
: "";
|
|
251
|
+
scenariosSkipped > 0 ? ` (${scenariosSkipped} skipped - no data coverage)` : "";
|
|
243
252
|
const preFilterNote =
|
|
244
253
|
preFilteredScenarioNames.length > 0
|
|
245
254
|
? ` (${preFilteredScenarioNames.length} excluded - outside portfolio date range)`
|
|
@@ -254,20 +263,19 @@ export function registerAnalysisBlockTools(
|
|
|
254
263
|
availableBuiltInScenarios: Object.keys(STRESS_SCENARIOS),
|
|
255
264
|
};
|
|
256
265
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
266
|
+
return createToolOutput(summary, structuredData);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
return {
|
|
269
|
+
content: [
|
|
270
|
+
{
|
|
271
|
+
type: "text",
|
|
272
|
+
text: `Error running stress test: ${(error as Error).message}`,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
isError: true,
|
|
276
|
+
};
|
|
269
277
|
}
|
|
270
|
-
)
|
|
278
|
+
}),
|
|
271
279
|
);
|
|
272
280
|
|
|
273
281
|
// Tool 8: drawdown_attribution
|
|
@@ -281,9 +289,7 @@ export function registerAnalysisBlockTools(
|
|
|
281
289
|
strategy: z
|
|
282
290
|
.string()
|
|
283
291
|
.optional()
|
|
284
|
-
.describe(
|
|
285
|
-
"Optional: Filter to specific strategy before calculating drawdown"
|
|
286
|
-
),
|
|
292
|
+
.describe("Optional: Filter to specific strategy before calculating drawdown"),
|
|
287
293
|
topN: z
|
|
288
294
|
.number()
|
|
289
295
|
.int()
|
|
@@ -328,15 +334,12 @@ export function registerAnalysisBlockTools(
|
|
|
328
334
|
// Build equity curve from trades
|
|
329
335
|
// Initial capital = first trade's fundsAtClose - pl
|
|
330
336
|
const firstTrade = sortedTrades[0];
|
|
331
|
-
const initialCapital =
|
|
332
|
-
(firstTrade.fundsAtClose ?? 10000) - firstTrade.pl;
|
|
337
|
+
const initialCapital = (firstTrade.fundsAtClose ?? 10000) - firstTrade.pl;
|
|
333
338
|
|
|
334
339
|
// Track peak equity and drawdown
|
|
335
340
|
let equity = initialCapital;
|
|
336
341
|
let peakEquity = initialCapital;
|
|
337
|
-
let peakDate: Date = new Date(
|
|
338
|
-
firstTrade.dateClosed ?? firstTrade.dateOpened
|
|
339
|
-
);
|
|
342
|
+
let peakDate: Date = new Date(firstTrade.dateClosed ?? firstTrade.dateOpened);
|
|
340
343
|
let maxDrawdown = 0;
|
|
341
344
|
let maxDrawdownPct = 0;
|
|
342
345
|
let troughDate: Date | null = null;
|
|
@@ -363,8 +366,7 @@ export function registerAnalysisBlockTools(
|
|
|
363
366
|
|
|
364
367
|
// Calculate current drawdown from peak
|
|
365
368
|
const drawdown = peakEquity - equity;
|
|
366
|
-
const drawdownPct =
|
|
367
|
-
peakEquity > 0 ? (drawdown / peakEquity) * 100 : 0;
|
|
369
|
+
const drawdownPct = peakEquity > 0 ? (drawdown / peakEquity) * 100 : 0;
|
|
368
370
|
|
|
369
371
|
equityPoints.push({
|
|
370
372
|
date: closeDate,
|
|
@@ -404,7 +406,10 @@ export function registerAnalysisBlockTools(
|
|
|
404
406
|
});
|
|
405
407
|
|
|
406
408
|
// Group trades by strategy and calculate attribution
|
|
407
|
-
const strategyPl = new Map<
|
|
409
|
+
const strategyPl = new Map<
|
|
410
|
+
string,
|
|
411
|
+
{ pl: number; trades: number; wins: number; losses: number }
|
|
412
|
+
>();
|
|
408
413
|
|
|
409
414
|
let totalLossDuringDrawdown = 0;
|
|
410
415
|
|
|
@@ -429,10 +434,18 @@ export function registerAnalysisBlockTools(
|
|
|
429
434
|
// Contribution %: strategy's P/L as % of total loss (most negative = highest contribution)
|
|
430
435
|
const attribution = Array.from(strategyPl.entries())
|
|
431
436
|
.map(([strategyName, data]) => ({
|
|
432
|
-
strategy: strategyName,
|
|
433
|
-
|
|
437
|
+
strategy: strategyName,
|
|
438
|
+
pl: data.pl,
|
|
439
|
+
trades: data.trades,
|
|
440
|
+
wins: data.wins,
|
|
441
|
+
losses: data.losses,
|
|
442
|
+
contributionPct:
|
|
443
|
+
totalLossDuringDrawdown !== 0
|
|
444
|
+
? Math.abs((data.pl / totalLossDuringDrawdown) * 100)
|
|
445
|
+
: 0,
|
|
434
446
|
}))
|
|
435
|
-
.sort((a, b) => a.pl - b.pl)
|
|
447
|
+
.sort((a, b) => a.pl - b.pl)
|
|
448
|
+
.slice(0, topN);
|
|
436
449
|
|
|
437
450
|
// Calculate duration in days
|
|
438
451
|
const durationMs = troughDate.getTime() - drawdownPeakDate.getTime();
|
|
@@ -479,7 +492,7 @@ export function registerAnalysisBlockTools(
|
|
|
479
492
|
isError: true,
|
|
480
493
|
};
|
|
481
494
|
}
|
|
482
|
-
})
|
|
495
|
+
}),
|
|
483
496
|
);
|
|
484
497
|
|
|
485
498
|
// Tool 9: marginal_contribution
|
|
@@ -494,7 +507,7 @@ export function registerAnalysisBlockTools(
|
|
|
494
507
|
.string()
|
|
495
508
|
.optional()
|
|
496
509
|
.describe(
|
|
497
|
-
"Calculate for specific strategy only. If omitted, calculates for all strategies."
|
|
510
|
+
"Calculate for specific strategy only. If omitted, calculates for all strategies.",
|
|
498
511
|
),
|
|
499
512
|
topN: z
|
|
500
513
|
.number()
|
|
@@ -503,7 +516,7 @@ export function registerAnalysisBlockTools(
|
|
|
503
516
|
.max(50)
|
|
504
517
|
.default(5)
|
|
505
518
|
.describe(
|
|
506
|
-
"Number of top contributors to return when targetStrategy is omitted (default: 5)"
|
|
519
|
+
"Number of top contributors to return when targetStrategy is omitted (default: 5)",
|
|
507
520
|
),
|
|
508
521
|
}),
|
|
509
522
|
},
|
|
@@ -524,14 +537,12 @@ export function registerAnalysisBlockTools(
|
|
|
524
537
|
}
|
|
525
538
|
|
|
526
539
|
// Get unique strategies
|
|
527
|
-
const strategies = Array.from(
|
|
528
|
-
new Set(trades.map((t) => t.strategy))
|
|
529
|
-
).sort();
|
|
540
|
+
const strategies = Array.from(new Set(trades.map((t) => t.strategy))).sort();
|
|
530
541
|
|
|
531
542
|
// Validate targetStrategy if provided
|
|
532
543
|
if (targetStrategy) {
|
|
533
544
|
const matchedStrategy = strategies.find(
|
|
534
|
-
(s) => s.toLowerCase() === targetStrategy.toLowerCase()
|
|
545
|
+
(s) => s.toLowerCase() === targetStrategy.toLowerCase(),
|
|
535
546
|
);
|
|
536
547
|
if (!matchedStrategy) {
|
|
537
548
|
return {
|
|
@@ -549,11 +560,9 @@ export function registerAnalysisBlockTools(
|
|
|
549
560
|
// Edge case: single strategy portfolio
|
|
550
561
|
if (strategies.length === 1) {
|
|
551
562
|
// Use daily logs for baseline when available (consistent with get_statistics)
|
|
552
|
-
const dailyLogs =
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
dailyLogs,
|
|
556
|
-
);
|
|
563
|
+
const dailyLogs =
|
|
564
|
+
block.dailyLogs && block.dailyLogs.length > 0 ? block.dailyLogs : undefined;
|
|
565
|
+
const baselineStats = calculator.calculatePortfolioStats(trades, dailyLogs);
|
|
557
566
|
|
|
558
567
|
const summary = `Marginal Contribution: ${blockId} | Single strategy portfolio - cannot calculate marginal contribution`;
|
|
559
568
|
|
|
@@ -587,33 +596,34 @@ export function registerAnalysisBlockTools(
|
|
|
587
596
|
|
|
588
597
|
// Calculate baseline portfolio metrics using ALL trades
|
|
589
598
|
// Use daily logs for baseline when available (consistent with get_statistics)
|
|
590
|
-
const dailyLogs =
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
dailyLogs,
|
|
594
|
-
);
|
|
599
|
+
const dailyLogs =
|
|
600
|
+
block.dailyLogs && block.dailyLogs.length > 0 ? block.dailyLogs : undefined;
|
|
601
|
+
const baselineStats = calculator.calculatePortfolioStats(trades, dailyLogs);
|
|
595
602
|
|
|
596
603
|
// Determine which strategies to analyze
|
|
597
604
|
const strategiesToAnalyze = targetStrategy
|
|
598
|
-
? strategies.filter(
|
|
599
|
-
(s) => s.toLowerCase() === targetStrategy.toLowerCase()
|
|
600
|
-
)
|
|
605
|
+
? strategies.filter((s) => s.toLowerCase() === targetStrategy.toLowerCase())
|
|
601
606
|
: strategies;
|
|
602
607
|
|
|
603
608
|
// Calculate marginal contribution for each strategy
|
|
604
609
|
// NOTE: Baseline uses daily-log Sharpe (matching get_statistics), but "without" uses
|
|
605
610
|
// trade-based Sharpe because daily logs include the removed strategy's impact.
|
|
606
611
|
// This means marginal deltas are mixed-basis, but the baseline values match get_statistics.
|
|
607
|
-
type Contribution = {
|
|
612
|
+
type Contribution = {
|
|
613
|
+
strategy: string;
|
|
614
|
+
trades: number;
|
|
615
|
+
marginalSharpe: number | null;
|
|
616
|
+
marginalSortino: number | null;
|
|
617
|
+
};
|
|
608
618
|
const contributions: Contribution[] = [];
|
|
609
619
|
|
|
610
620
|
for (const strategy of strategiesToAnalyze) {
|
|
611
621
|
// Filter OUT this strategy's trades (portfolio WITHOUT this strategy)
|
|
612
622
|
const tradesWithout = trades.filter(
|
|
613
|
-
(t) => t.strategy.toLowerCase() !== strategy.toLowerCase()
|
|
623
|
+
(t) => t.strategy.toLowerCase() !== strategy.toLowerCase(),
|
|
614
624
|
);
|
|
615
625
|
const strategyTrades = trades.filter(
|
|
616
|
-
(t) => t.strategy.toLowerCase() === strategy.toLowerCase()
|
|
626
|
+
(t) => t.strategy.toLowerCase() === strategy.toLowerCase(),
|
|
617
627
|
);
|
|
618
628
|
|
|
619
629
|
// Edge case: removing this strategy leaves nothing
|
|
@@ -632,14 +642,20 @@ export function registerAnalysisBlockTools(
|
|
|
632
642
|
const withoutStats = calculator.calculatePortfolioStats(
|
|
633
643
|
tradesWithout,
|
|
634
644
|
undefined,
|
|
635
|
-
true // Force trade-based - daily logs include the removed strategy's impact
|
|
645
|
+
true, // Force trade-based - daily logs include the removed strategy's impact
|
|
636
646
|
);
|
|
637
647
|
|
|
638
648
|
// Marginal contribution = baseline - without (positive = improves, negative = hurts)
|
|
639
|
-
const hasValidSharpe =
|
|
640
|
-
|
|
641
|
-
const
|
|
642
|
-
|
|
649
|
+
const hasValidSharpe =
|
|
650
|
+
baselineStats.sharpeRatio != null && withoutStats.sharpeRatio != null;
|
|
651
|
+
const hasValidSortino =
|
|
652
|
+
baselineStats.sortinoRatio != null && withoutStats.sortinoRatio != null;
|
|
653
|
+
const marginalSharpe = hasValidSharpe
|
|
654
|
+
? baselineStats.sharpeRatio! - withoutStats.sharpeRatio!
|
|
655
|
+
: null;
|
|
656
|
+
const marginalSortino = hasValidSortino
|
|
657
|
+
? baselineStats.sortinoRatio! - withoutStats.sortinoRatio!
|
|
658
|
+
: null;
|
|
643
659
|
|
|
644
660
|
contributions.push({
|
|
645
661
|
strategy,
|
|
@@ -659,19 +675,22 @@ export function registerAnalysisBlockTools(
|
|
|
659
675
|
});
|
|
660
676
|
|
|
661
677
|
// Apply topN limit (only when not filtering by targetStrategy)
|
|
662
|
-
const limitedContributions = targetStrategy
|
|
663
|
-
? contributions
|
|
664
|
-
: contributions.slice(0, topN);
|
|
678
|
+
const limitedContributions = targetStrategy ? contributions : contributions.slice(0, topN);
|
|
665
679
|
|
|
666
680
|
// Find most and least beneficial
|
|
667
681
|
const validContributions = contributions.filter((c) => c.marginalSharpe !== null);
|
|
668
|
-
const mostBeneficial =
|
|
669
|
-
|
|
670
|
-
|
|
682
|
+
const mostBeneficial =
|
|
683
|
+
validContributions.length > 0
|
|
684
|
+
? {
|
|
685
|
+
strategy: validContributions[0].strategy,
|
|
686
|
+
sharpe: validContributions[0].marginalSharpe,
|
|
687
|
+
}
|
|
688
|
+
: null;
|
|
671
689
|
const lastValid = validContributions[validContributions.length - 1];
|
|
672
|
-
const leastBeneficial =
|
|
673
|
-
|
|
674
|
-
|
|
690
|
+
const leastBeneficial =
|
|
691
|
+
validContributions.length > 0
|
|
692
|
+
? { strategy: lastValid.strategy, sharpe: lastValid.marginalSharpe }
|
|
693
|
+
: null;
|
|
675
694
|
|
|
676
695
|
// Build summary line
|
|
677
696
|
const summaryParts: string[] = [`Marginal Contribution: ${blockId}`];
|
|
@@ -680,9 +699,7 @@ export function registerAnalysisBlockTools(
|
|
|
680
699
|
mostBeneficial.sharpe >= 0
|
|
681
700
|
? `+${formatRatio(mostBeneficial.sharpe)}`
|
|
682
701
|
: formatRatio(mostBeneficial.sharpe);
|
|
683
|
-
summaryParts.push(
|
|
684
|
-
`Top: ${mostBeneficial.strategy} (Sharpe ${sharpeStr})`
|
|
685
|
-
);
|
|
702
|
+
summaryParts.push(`Top: ${mostBeneficial.strategy} (Sharpe ${sharpeStr})`);
|
|
686
703
|
}
|
|
687
704
|
if (
|
|
688
705
|
leastBeneficial &&
|
|
@@ -693,9 +710,7 @@ export function registerAnalysisBlockTools(
|
|
|
693
710
|
leastBeneficial.sharpe >= 0
|
|
694
711
|
? `+${formatRatio(leastBeneficial.sharpe)}`
|
|
695
712
|
: formatRatio(leastBeneficial.sharpe);
|
|
696
|
-
summaryParts.push(
|
|
697
|
-
`Worst: ${leastBeneficial.strategy} (Sharpe ${sharpeStr})`
|
|
698
|
-
);
|
|
713
|
+
summaryParts.push(`Worst: ${leastBeneficial.strategy} (Sharpe ${sharpeStr})`);
|
|
699
714
|
}
|
|
700
715
|
const summary = summaryParts.join(" | ");
|
|
701
716
|
|
|
@@ -728,6 +743,6 @@ export function registerAnalysisBlockTools(
|
|
|
728
743
|
isError: true,
|
|
729
744
|
};
|
|
730
745
|
}
|
|
731
|
-
})
|
|
746
|
+
}),
|
|
732
747
|
);
|
|
733
748
|
}
|