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
|
@@ -7,47 +7,28 @@
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
9
|
import { loadBlock } from "../../utils/block-loader.ts";
|
|
10
|
-
import {
|
|
11
|
-
createToolOutput,
|
|
12
|
-
formatCurrency,
|
|
13
|
-
} from "../../utils/output-formatter.ts";
|
|
10
|
+
import { createToolOutput, formatCurrency } from "../../utils/output-formatter.ts";
|
|
14
11
|
import { PortfolioStatsCalculator } from "@tradeblocks/lib";
|
|
15
12
|
import { resolveTradeTicker } from "../../utils/ticker.ts";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
filterDailyLogsByDateRange,
|
|
19
|
-
} from "../shared/filters.ts";
|
|
20
|
-
import {
|
|
21
|
-
withSyncedBlock,
|
|
22
|
-
withSyncedBlocks,
|
|
23
|
-
} from "../middleware/sync-middleware.ts";
|
|
13
|
+
import { filterByDateRange, filterDailyLogsByDateRange } from "../shared/filters.ts";
|
|
14
|
+
import { withSyncedBlock, withSyncedBlocks } from "../middleware/sync-middleware.ts";
|
|
24
15
|
|
|
25
16
|
/**
|
|
26
17
|
* Register comparison block tools
|
|
27
18
|
*/
|
|
28
|
-
export function registerComparisonBlockTools(
|
|
29
|
-
server: McpServer,
|
|
30
|
-
baseDir: string
|
|
31
|
-
): void {
|
|
19
|
+
export function registerComparisonBlockTools(server: McpServer, baseDir: string): void {
|
|
32
20
|
const calculator = new PortfolioStatsCalculator();
|
|
33
21
|
|
|
34
22
|
// Tool 4: get_strategy_comparison
|
|
35
23
|
server.registerTool(
|
|
36
24
|
"get_strategy_comparison",
|
|
37
25
|
{
|
|
38
|
-
description:
|
|
39
|
-
"Compare all strategies within a block with optional filtering and sorting",
|
|
26
|
+
description: "Compare all strategies within a block with optional filtering and sorting",
|
|
40
27
|
inputSchema: z.object({
|
|
41
28
|
blockId: z.string().describe("Block folder name"),
|
|
42
|
-
startDate: z
|
|
43
|
-
.string()
|
|
44
|
-
.optional()
|
|
45
|
-
.describe("Start date filter (YYYY-MM-DD)"),
|
|
29
|
+
startDate: z.string().optional().describe("Start date filter (YYYY-MM-DD)"),
|
|
46
30
|
endDate: z.string().optional().describe("End date filter (YYYY-MM-DD)"),
|
|
47
|
-
tickerFilter: z
|
|
48
|
-
.string()
|
|
49
|
-
.optional()
|
|
50
|
-
.describe("Filter trades by underlying ticker symbol"),
|
|
31
|
+
tickerFilter: z.string().optional().describe("Filter trades by underlying ticker symbol"),
|
|
51
32
|
minTrades: z
|
|
52
33
|
.number()
|
|
53
34
|
.min(1)
|
|
@@ -56,18 +37,12 @@ export function registerComparisonBlockTools(
|
|
|
56
37
|
sortBy: z
|
|
57
38
|
.enum(["netPl", "pl", "winRate", "trades", "profitFactor", "name"])
|
|
58
39
|
.default("netPl")
|
|
59
|
-
.describe(
|
|
60
|
-
"Sort strategies by metric (default: netPl). 'pl' is an alias for 'netPl'."
|
|
61
|
-
),
|
|
40
|
+
.describe("Sort strategies by metric (default: netPl). 'pl' is an alias for 'netPl'."),
|
|
62
41
|
sortOrder: z
|
|
63
42
|
.enum(["asc", "desc"])
|
|
64
43
|
.default("desc")
|
|
65
44
|
.describe("Sort direction (default: desc for highest first)"),
|
|
66
|
-
limit: z
|
|
67
|
-
.number()
|
|
68
|
-
.min(1)
|
|
69
|
-
.optional()
|
|
70
|
-
.describe("Limit number of strategies shown"),
|
|
45
|
+
limit: z.number().min(1).optional().describe("Limit number of strategies shown"),
|
|
71
46
|
}),
|
|
72
47
|
},
|
|
73
48
|
withSyncedBlock(
|
|
@@ -84,93 +59,87 @@ export function registerComparisonBlockTools(
|
|
|
84
59
|
}) => {
|
|
85
60
|
try {
|
|
86
61
|
const block = await loadBlock(baseDir, blockId);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Apply date filter
|
|
90
|
-
trades = filterByDateRange(trades, startDate, endDate);
|
|
62
|
+
let trades = block.trades;
|
|
91
63
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const tickerLower = tickerFilter.toLowerCase();
|
|
95
|
-
trades = trades.filter(
|
|
96
|
-
(t) => resolveTradeTicker(t).toLowerCase() === tickerLower
|
|
97
|
-
);
|
|
98
|
-
}
|
|
64
|
+
// Apply date filter
|
|
65
|
+
trades = filterByDateRange(trades, startDate, endDate);
|
|
99
66
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
};
|
|
106
|
-
}
|
|
67
|
+
// Apply ticker filter (supports both explicit ticker columns and legs-derived symbols)
|
|
68
|
+
if (tickerFilter) {
|
|
69
|
+
const tickerLower = tickerFilter.toLowerCase();
|
|
70
|
+
trades = trades.filter((t) => resolveTradeTicker(t).toLowerCase() === tickerLower);
|
|
71
|
+
}
|
|
107
72
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
73
|
+
if (trades.length === 0) {
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: "text", text: "No trades found matching the filters." }],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
111
78
|
|
|
112
|
-
|
|
113
|
-
|
|
79
|
+
// Calculate stats per strategy - always use trade-based calculations
|
|
80
|
+
// because daily logs represent full portfolio
|
|
81
|
+
const strategyStats = calculator.calculateStrategyStats(trades);
|
|
114
82
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
strategies = strategies.filter((s) => s.tradeCount >= minTrades);
|
|
118
|
-
}
|
|
83
|
+
// Convert to array for filtering and sorting
|
|
84
|
+
let strategies = Object.values(strategyStats);
|
|
119
85
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
switch (sortBy) {
|
|
124
|
-
case "winRate":
|
|
125
|
-
return (a.winRate - b.winRate) * multiplier;
|
|
126
|
-
case "trades":
|
|
127
|
-
return (a.tradeCount - b.tradeCount) * multiplier;
|
|
128
|
-
case "profitFactor":
|
|
129
|
-
return (
|
|
130
|
-
((a.profitFactor ?? 0) - (b.profitFactor ?? 0)) * multiplier
|
|
131
|
-
);
|
|
132
|
-
case "name":
|
|
133
|
-
return a.strategyName.localeCompare(b.strategyName) * multiplier;
|
|
134
|
-
case "netPl":
|
|
135
|
-
case "pl":
|
|
136
|
-
default:
|
|
137
|
-
return (a.totalPl - b.totalPl) * multiplier;
|
|
86
|
+
// Apply minTrades filter
|
|
87
|
+
if (minTrades !== undefined) {
|
|
88
|
+
strategies = strategies.filter((s) => s.tradeCount >= minTrades);
|
|
138
89
|
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// Apply limit
|
|
142
|
-
const totalBeforeLimit = strategies.length;
|
|
143
|
-
if (limit !== undefined && limit < strategies.length) {
|
|
144
|
-
strategies = strategies.slice(0, limit);
|
|
145
|
-
}
|
|
146
90
|
|
|
147
|
-
|
|
148
|
-
|
|
91
|
+
// Apply sorting
|
|
92
|
+
const multiplier = sortOrder === "asc" ? 1 : -1;
|
|
93
|
+
strategies.sort((a, b) => {
|
|
94
|
+
switch (sortBy) {
|
|
95
|
+
case "winRate":
|
|
96
|
+
return (a.winRate - b.winRate) * multiplier;
|
|
97
|
+
case "trades":
|
|
98
|
+
return (a.tradeCount - b.tradeCount) * multiplier;
|
|
99
|
+
case "profitFactor":
|
|
100
|
+
return ((a.profitFactor ?? 0) - (b.profitFactor ?? 0)) * multiplier;
|
|
101
|
+
case "name":
|
|
102
|
+
return a.strategyName.localeCompare(b.strategyName) * multiplier;
|
|
103
|
+
case "netPl":
|
|
104
|
+
case "pl":
|
|
105
|
+
default:
|
|
106
|
+
return (a.totalPl - b.totalPl) * multiplier;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Apply limit
|
|
111
|
+
const totalBeforeLimit = strategies.length;
|
|
112
|
+
if (limit !== undefined && limit < strategies.length) {
|
|
113
|
+
strategies = strategies.slice(0, limit);
|
|
114
|
+
}
|
|
149
115
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
116
|
+
// Brief summary for user display
|
|
117
|
+
const summary = `Strategy Comparison: ${blockId} | ${strategies.length} strategies${totalBeforeLimit > strategies.length ? ` (of ${totalBeforeLimit})` : ""} | Sorted by ${sortBy}`;
|
|
118
|
+
|
|
119
|
+
// Build structured data for Claude reasoning
|
|
120
|
+
const structuredData = {
|
|
121
|
+
blockId,
|
|
122
|
+
options: {
|
|
123
|
+
startDate: startDate ?? null,
|
|
124
|
+
endDate: endDate ?? null,
|
|
125
|
+
tickerFilter: tickerFilter ?? null,
|
|
126
|
+
minTrades: minTrades ?? null,
|
|
127
|
+
sortBy,
|
|
128
|
+
sortOrder,
|
|
129
|
+
limit: limit ?? null,
|
|
130
|
+
},
|
|
131
|
+
strategies: strategies.map((s) => ({
|
|
132
|
+
name: s.strategyName,
|
|
133
|
+
trades: s.tradeCount,
|
|
134
|
+
winRate: s.winRate,
|
|
135
|
+
netPl: s.totalPl,
|
|
136
|
+
avgWin: s.avgWin,
|
|
137
|
+
avgLoss: s.avgLoss,
|
|
138
|
+
profitFactor: s.profitFactor,
|
|
139
|
+
})),
|
|
140
|
+
totalStrategies: totalBeforeLimit,
|
|
141
|
+
count: strategies.length,
|
|
142
|
+
};
|
|
174
143
|
|
|
175
144
|
return createToolOutput(summary, structuredData);
|
|
176
145
|
} catch (error) {
|
|
@@ -184,8 +153,8 @@ export function registerComparisonBlockTools(
|
|
|
184
153
|
isError: true,
|
|
185
154
|
};
|
|
186
155
|
}
|
|
187
|
-
}
|
|
188
|
-
)
|
|
156
|
+
},
|
|
157
|
+
),
|
|
189
158
|
);
|
|
190
159
|
|
|
191
160
|
// Tool 5: compare_blocks
|
|
@@ -211,11 +180,11 @@ export function registerComparisonBlockTools(
|
|
|
211
180
|
"maxDrawdown",
|
|
212
181
|
"profitFactor",
|
|
213
182
|
"calmarRatio",
|
|
214
|
-
])
|
|
183
|
+
]),
|
|
215
184
|
)
|
|
216
185
|
.optional()
|
|
217
186
|
.describe(
|
|
218
|
-
"Specific metrics to include in comparison (default: all). Use to focus on key metrics."
|
|
187
|
+
"Specific metrics to include in comparison (default: all). Use to focus on key metrics.",
|
|
219
188
|
),
|
|
220
189
|
sortBy: z
|
|
221
190
|
.enum([
|
|
@@ -231,28 +200,20 @@ export function registerComparisonBlockTools(
|
|
|
231
200
|
])
|
|
232
201
|
.default("name")
|
|
233
202
|
.describe("Sort blocks by metric (default: name)"),
|
|
234
|
-
sortOrder: z
|
|
235
|
-
.enum(["asc", "desc"])
|
|
236
|
-
.default("asc")
|
|
237
|
-
.describe("Sort direction (default: asc)"),
|
|
203
|
+
sortOrder: z.enum(["asc", "desc"]).default("asc").describe("Sort direction (default: asc)"),
|
|
238
204
|
}),
|
|
239
205
|
},
|
|
240
|
-
withSyncedBlocks(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
for (const blockId of blockIds!) {
|
|
206
|
+
withSyncedBlocks(baseDir, async ({ blockIds, metrics, sortBy, sortOrder }) => {
|
|
207
|
+
try {
|
|
208
|
+
const blockStats: Array<{
|
|
209
|
+
blockId: string;
|
|
210
|
+
stats: ReturnType<typeof calculator.calculatePortfolioStats>;
|
|
211
|
+
}> = [];
|
|
212
|
+
|
|
213
|
+
for (const blockId of blockIds!) {
|
|
250
214
|
try {
|
|
251
215
|
const block = await loadBlock(baseDir, blockId);
|
|
252
|
-
const stats = calculator.calculatePortfolioStats(
|
|
253
|
-
block.trades,
|
|
254
|
-
block.dailyLogs
|
|
255
|
-
);
|
|
216
|
+
const stats = calculator.calculatePortfolioStats(block.trades, block.dailyLogs);
|
|
256
217
|
blockStats.push({ blockId, stats });
|
|
257
218
|
} catch (error) {
|
|
258
219
|
// Include error info in output but continue with other blocks
|
|
@@ -279,36 +240,19 @@ export function registerComparisonBlockTools(
|
|
|
279
240
|
case "totalTrades":
|
|
280
241
|
return (a.stats.totalTrades - b.stats.totalTrades) * multiplier;
|
|
281
242
|
case "winRate":
|
|
282
|
-
return (
|
|
283
|
-
((a.stats.winRate ?? 0) - (b.stats.winRate ?? 0)) * multiplier
|
|
284
|
-
);
|
|
243
|
+
return ((a.stats.winRate ?? 0) - (b.stats.winRate ?? 0)) * multiplier;
|
|
285
244
|
case "netPl":
|
|
286
245
|
return ((a.stats.netPl ?? 0) - (b.stats.netPl ?? 0)) * multiplier;
|
|
287
246
|
case "sharpeRatio":
|
|
288
|
-
return (
|
|
289
|
-
((a.stats.sharpeRatio ?? 0) - (b.stats.sharpeRatio ?? 0)) *
|
|
290
|
-
multiplier
|
|
291
|
-
);
|
|
247
|
+
return ((a.stats.sharpeRatio ?? 0) - (b.stats.sharpeRatio ?? 0)) * multiplier;
|
|
292
248
|
case "sortinoRatio":
|
|
293
|
-
return (
|
|
294
|
-
((a.stats.sortinoRatio ?? 0) - (b.stats.sortinoRatio ?? 0)) *
|
|
295
|
-
multiplier
|
|
296
|
-
);
|
|
249
|
+
return ((a.stats.sortinoRatio ?? 0) - (b.stats.sortinoRatio ?? 0)) * multiplier;
|
|
297
250
|
case "maxDrawdown":
|
|
298
|
-
return (
|
|
299
|
-
((a.stats.maxDrawdown ?? 0) - (b.stats.maxDrawdown ?? 0)) *
|
|
300
|
-
multiplier
|
|
301
|
-
);
|
|
251
|
+
return ((a.stats.maxDrawdown ?? 0) - (b.stats.maxDrawdown ?? 0)) * multiplier;
|
|
302
252
|
case "profitFactor":
|
|
303
|
-
return (
|
|
304
|
-
((a.stats.profitFactor ?? 0) - (b.stats.profitFactor ?? 0)) *
|
|
305
|
-
multiplier
|
|
306
|
-
);
|
|
253
|
+
return ((a.stats.profitFactor ?? 0) - (b.stats.profitFactor ?? 0)) * multiplier;
|
|
307
254
|
case "calmarRatio":
|
|
308
|
-
return (
|
|
309
|
-
((a.stats.calmarRatio ?? 0) - (b.stats.calmarRatio ?? 0)) *
|
|
310
|
-
multiplier
|
|
311
|
-
);
|
|
255
|
+
return ((a.stats.calmarRatio ?? 0) - (b.stats.calmarRatio ?? 0)) * multiplier;
|
|
312
256
|
case "name":
|
|
313
257
|
default:
|
|
314
258
|
return a.blockId.localeCompare(b.blockId) * multiplier;
|
|
@@ -346,21 +290,15 @@ export function registerComparisonBlockTools(
|
|
|
346
290
|
},
|
|
347
291
|
comparisons: blockStats.map(({ blockId, stats }) => {
|
|
348
292
|
const filteredStats: Record<string, number | null> = {};
|
|
349
|
-
if (requestedMetrics.totalTrades)
|
|
350
|
-
|
|
351
|
-
if (requestedMetrics.winRate)
|
|
352
|
-
filteredStats.winRate = stats.winRate;
|
|
293
|
+
if (requestedMetrics.totalTrades) filteredStats.totalTrades = stats.totalTrades;
|
|
294
|
+
if (requestedMetrics.winRate) filteredStats.winRate = stats.winRate;
|
|
353
295
|
if (requestedMetrics.netPl) filteredStats.netPl = stats.netPl;
|
|
354
|
-
if (requestedMetrics.sharpeRatio)
|
|
355
|
-
filteredStats.sharpeRatio = stats.sharpeRatio ?? null;
|
|
296
|
+
if (requestedMetrics.sharpeRatio) filteredStats.sharpeRatio = stats.sharpeRatio ?? null;
|
|
356
297
|
if (requestedMetrics.sortinoRatio)
|
|
357
298
|
filteredStats.sortinoRatio = stats.sortinoRatio ?? null;
|
|
358
|
-
if (requestedMetrics.maxDrawdown)
|
|
359
|
-
|
|
360
|
-
if (requestedMetrics.
|
|
361
|
-
filteredStats.profitFactor = stats.profitFactor;
|
|
362
|
-
if (requestedMetrics.calmarRatio)
|
|
363
|
-
filteredStats.calmarRatio = stats.calmarRatio ?? null;
|
|
299
|
+
if (requestedMetrics.maxDrawdown) filteredStats.maxDrawdown = stats.maxDrawdown;
|
|
300
|
+
if (requestedMetrics.profitFactor) filteredStats.profitFactor = stats.profitFactor;
|
|
301
|
+
if (requestedMetrics.calmarRatio) filteredStats.calmarRatio = stats.calmarRatio ?? null;
|
|
364
302
|
return {
|
|
365
303
|
blockId,
|
|
366
304
|
stats: filteredStats,
|
|
@@ -369,20 +307,19 @@ export function registerComparisonBlockTools(
|
|
|
369
307
|
failedBlocks: failedIds,
|
|
370
308
|
};
|
|
371
309
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
310
|
+
return createToolOutput(summary, structuredData);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
return {
|
|
313
|
+
content: [
|
|
314
|
+
{
|
|
315
|
+
type: "text",
|
|
316
|
+
text: `Error comparing blocks: ${(error as Error).message}`,
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
isError: true,
|
|
320
|
+
};
|
|
384
321
|
}
|
|
385
|
-
)
|
|
322
|
+
}),
|
|
386
323
|
);
|
|
387
324
|
|
|
388
325
|
// Tool 6: block_diff
|
|
@@ -392,18 +329,10 @@ export function registerComparisonBlockTools(
|
|
|
392
329
|
description:
|
|
393
330
|
"Compare two blocks with strategy overlap analysis and P/L attribution. Shows which strategies are shared vs unique between blocks, and calculates performance deltas for shared strategies.",
|
|
394
331
|
inputSchema: z.object({
|
|
395
|
-
blockIdA: z
|
|
396
|
-
.string()
|
|
397
|
-
.describe("First block (baseline) for comparison"),
|
|
332
|
+
blockIdA: z.string().describe("First block (baseline) for comparison"),
|
|
398
333
|
blockIdB: z.string().describe("Second block (comparison target)"),
|
|
399
|
-
startDate: z
|
|
400
|
-
|
|
401
|
-
.optional()
|
|
402
|
-
.describe("Optional start date filter (YYYY-MM-DD)"),
|
|
403
|
-
endDate: z
|
|
404
|
-
.string()
|
|
405
|
-
.optional()
|
|
406
|
-
.describe("Optional end date filter (YYYY-MM-DD)"),
|
|
334
|
+
startDate: z.string().optional().describe("Optional start date filter (YYYY-MM-DD)"),
|
|
335
|
+
endDate: z.string().optional().describe("Optional end date filter (YYYY-MM-DD)"),
|
|
407
336
|
metricsToCompare: z
|
|
408
337
|
.array(
|
|
409
338
|
z.enum([
|
|
@@ -414,11 +343,11 @@ export function registerComparisonBlockTools(
|
|
|
414
343
|
"profitFactor",
|
|
415
344
|
"sharpeRatio",
|
|
416
345
|
"maxDrawdown",
|
|
417
|
-
])
|
|
346
|
+
]),
|
|
418
347
|
)
|
|
419
348
|
.optional()
|
|
420
349
|
.describe(
|
|
421
|
-
"Specific metrics to include in comparison (default: all). 'netPl' and 'pl' are equivalent. Use to focus output."
|
|
350
|
+
"Specific metrics to include in comparison (default: all). 'netPl' and 'pl' are equivalent. Use to focus output.",
|
|
422
351
|
),
|
|
423
352
|
}),
|
|
424
353
|
},
|
|
@@ -432,189 +361,177 @@ export function registerComparisonBlockTools(
|
|
|
432
361
|
loadBlock(baseDir, blockIdB!),
|
|
433
362
|
]);
|
|
434
363
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
364
|
+
// Apply date filters
|
|
365
|
+
const tradesA = filterByDateRange(blockA.trades, startDate, endDate);
|
|
366
|
+
const tradesB = filterByDateRange(blockB.trades, startDate, endDate);
|
|
367
|
+
|
|
368
|
+
// Extract unique strategy names from each block
|
|
369
|
+
const strategiesA = new Set(tradesA.map((t) => t.strategy));
|
|
370
|
+
const strategiesB = new Set(tradesB.map((t) => t.strategy));
|
|
371
|
+
|
|
372
|
+
// Categorize strategies
|
|
373
|
+
const shared: string[] = [];
|
|
374
|
+
const uniqueToA: string[] = [];
|
|
375
|
+
const uniqueToB: string[] = [];
|
|
376
|
+
|
|
377
|
+
for (const strategy of strategiesA) {
|
|
378
|
+
if (strategiesB.has(strategy)) {
|
|
379
|
+
shared.push(strategy);
|
|
380
|
+
} else {
|
|
381
|
+
uniqueToA.push(strategy);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
438
384
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
385
|
+
for (const strategy of strategiesB) {
|
|
386
|
+
if (!strategiesA.has(strategy)) {
|
|
387
|
+
uniqueToB.push(strategy);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
442
390
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
391
|
+
// Sort for consistent output
|
|
392
|
+
shared.sort();
|
|
393
|
+
uniqueToA.sort();
|
|
394
|
+
uniqueToB.sort();
|
|
447
395
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
uniqueToA.push(strategy);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
396
|
+
// Calculate overlap percentage
|
|
397
|
+
const totalUniqueStrategies = new Set([...strategiesA, ...strategiesB]).size;
|
|
398
|
+
const overlapPercent =
|
|
399
|
+
totalUniqueStrategies > 0 ? (shared.length / totalUniqueStrategies) * 100 : 0;
|
|
455
400
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
}
|
|
401
|
+
// Calculate per-strategy stats using trade-based calculations only
|
|
402
|
+
const statsA = calculator.calculateStrategyStats(tradesA);
|
|
403
|
+
const statsB = calculator.calculateStrategyStats(tradesB);
|
|
461
404
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const totalUniqueStrategies = new Set([
|
|
469
|
-
...strategiesA,
|
|
470
|
-
...strategiesB,
|
|
471
|
-
]).size;
|
|
472
|
-
const overlapPercent =
|
|
473
|
-
totalUniqueStrategies > 0
|
|
474
|
-
? (shared.length / totalUniqueStrategies) * 100
|
|
475
|
-
: 0;
|
|
476
|
-
|
|
477
|
-
// Calculate per-strategy stats using trade-based calculations only
|
|
478
|
-
const statsA = calculator.calculateStrategyStats(tradesA);
|
|
479
|
-
const statsB = calculator.calculateStrategyStats(tradesB);
|
|
480
|
-
|
|
481
|
-
// Helper to build strategy comparison entry
|
|
482
|
-
const buildStrategyEntry = (strategy: string) => {
|
|
483
|
-
const blockAStats = statsA[strategy];
|
|
484
|
-
const blockBStats = statsB[strategy];
|
|
485
|
-
|
|
486
|
-
const entryA = blockAStats
|
|
487
|
-
? {
|
|
488
|
-
trades: blockAStats.tradeCount,
|
|
489
|
-
netPl: blockAStats.totalPl,
|
|
490
|
-
winRate: blockAStats.winRate,
|
|
491
|
-
profitFactor: blockAStats.profitFactor,
|
|
492
|
-
}
|
|
493
|
-
: null;
|
|
494
|
-
|
|
495
|
-
const entryB = blockBStats
|
|
496
|
-
? {
|
|
497
|
-
trades: blockBStats.tradeCount,
|
|
498
|
-
netPl: blockBStats.totalPl,
|
|
499
|
-
winRate: blockBStats.winRate,
|
|
500
|
-
profitFactor: blockBStats.profitFactor,
|
|
501
|
-
}
|
|
502
|
-
: null;
|
|
503
|
-
|
|
504
|
-
// Calculate delta only for shared strategies
|
|
505
|
-
const delta =
|
|
506
|
-
entryA && entryB
|
|
405
|
+
// Helper to build strategy comparison entry
|
|
406
|
+
const buildStrategyEntry = (strategy: string) => {
|
|
407
|
+
const blockAStats = statsA[strategy];
|
|
408
|
+
const blockBStats = statsB[strategy];
|
|
409
|
+
|
|
410
|
+
const entryA = blockAStats
|
|
507
411
|
? {
|
|
508
|
-
trades:
|
|
509
|
-
netPl:
|
|
510
|
-
winRate:
|
|
412
|
+
trades: blockAStats.tradeCount,
|
|
413
|
+
netPl: blockAStats.totalPl,
|
|
414
|
+
winRate: blockAStats.winRate,
|
|
415
|
+
profitFactor: blockAStats.profitFactor,
|
|
511
416
|
}
|
|
512
417
|
: null;
|
|
513
418
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
419
|
+
const entryB = blockBStats
|
|
420
|
+
? {
|
|
421
|
+
trades: blockBStats.tradeCount,
|
|
422
|
+
netPl: blockBStats.totalPl,
|
|
423
|
+
winRate: blockBStats.winRate,
|
|
424
|
+
profitFactor: blockBStats.profitFactor,
|
|
425
|
+
}
|
|
426
|
+
: null;
|
|
427
|
+
|
|
428
|
+
// Calculate delta only for shared strategies
|
|
429
|
+
const delta =
|
|
430
|
+
entryA && entryB
|
|
431
|
+
? {
|
|
432
|
+
trades: entryB.trades - entryA.trades,
|
|
433
|
+
netPl: entryB.netPl - entryA.netPl,
|
|
434
|
+
winRate: entryB.winRate - entryA.winRate,
|
|
435
|
+
}
|
|
436
|
+
: null;
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
strategy,
|
|
440
|
+
blockA: entryA,
|
|
441
|
+
blockB: entryB,
|
|
442
|
+
delta,
|
|
443
|
+
};
|
|
519
444
|
};
|
|
520
|
-
};
|
|
521
445
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
// Build portfolio totals with all or filtered metrics
|
|
548
|
-
const allMetrics =
|
|
549
|
-
!metricsToCompare || metricsToCompare.length === 0;
|
|
550
|
-
const includeMetric = (m: string) =>
|
|
551
|
-
allMetrics ||
|
|
552
|
-
metricsToCompare?.includes(
|
|
553
|
-
m as (typeof metricsToCompare)[number]
|
|
446
|
+
// Build per-strategy comparison for all strategies
|
|
447
|
+
const perStrategyComparison = [
|
|
448
|
+
...shared.map(buildStrategyEntry),
|
|
449
|
+
...uniqueToA.map(buildStrategyEntry),
|
|
450
|
+
...uniqueToB.map(buildStrategyEntry),
|
|
451
|
+
];
|
|
452
|
+
|
|
453
|
+
// Calculate portfolio-level totals
|
|
454
|
+
// Use daily logs for portfolio-level stats when available (consistent with get_statistics)
|
|
455
|
+
// Per-strategy stats remain trade-based since daily logs are portfolio-wide
|
|
456
|
+
const dailyLogsA =
|
|
457
|
+
blockA.dailyLogs && blockA.dailyLogs.length > 0
|
|
458
|
+
? filterDailyLogsByDateRange(blockA.dailyLogs, startDate, endDate)
|
|
459
|
+
: undefined;
|
|
460
|
+
const dailyLogsB =
|
|
461
|
+
blockB.dailyLogs && blockB.dailyLogs.length > 0
|
|
462
|
+
? filterDailyLogsByDateRange(blockB.dailyLogs, startDate, endDate)
|
|
463
|
+
: undefined;
|
|
464
|
+
const portfolioStatsA = calculator.calculatePortfolioStats(
|
|
465
|
+
tradesA,
|
|
466
|
+
dailyLogsA && dailyLogsA.length > 0 ? dailyLogsA : undefined,
|
|
467
|
+
);
|
|
468
|
+
const portfolioStatsB = calculator.calculatePortfolioStats(
|
|
469
|
+
tradesB,
|
|
470
|
+
dailyLogsB && dailyLogsB.length > 0 ? dailyLogsB : undefined,
|
|
554
471
|
);
|
|
555
472
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
entry.
|
|
566
|
-
|
|
567
|
-
entry.
|
|
568
|
-
|
|
569
|
-
entry.
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const portfolioA = buildPortfolioEntry(portfolioStatsA);
|
|
574
|
-
const portfolioB = buildPortfolioEntry(portfolioStatsB);
|
|
473
|
+
// Build portfolio totals with all or filtered metrics
|
|
474
|
+
const allMetrics = !metricsToCompare || metricsToCompare.length === 0;
|
|
475
|
+
const includeMetric = (m: string) =>
|
|
476
|
+
allMetrics || metricsToCompare?.includes(m as (typeof metricsToCompare)[number]);
|
|
477
|
+
|
|
478
|
+
const buildPortfolioEntry = (
|
|
479
|
+
stats: ReturnType<typeof calculator.calculatePortfolioStats>,
|
|
480
|
+
) => {
|
|
481
|
+
const entry: Record<string, number | null> = {};
|
|
482
|
+
if (includeMetric("trades")) entry.totalTrades = stats.totalTrades;
|
|
483
|
+
if (includeMetric("pl") || includeMetric("netPl")) entry.netPl = stats.netPl;
|
|
484
|
+
if (includeMetric("winRate")) entry.winRate = stats.winRate;
|
|
485
|
+
if (includeMetric("profitFactor")) entry.profitFactor = stats.profitFactor;
|
|
486
|
+
if (includeMetric("sharpeRatio")) entry.sharpeRatio = stats.sharpeRatio ?? null;
|
|
487
|
+
if (includeMetric("maxDrawdown")) entry.maxDrawdown = stats.maxDrawdown;
|
|
488
|
+
return entry;
|
|
489
|
+
};
|
|
575
490
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
for (const key of Object.keys(portfolioA)) {
|
|
579
|
-
const valA = portfolioA[key];
|
|
580
|
-
const valB = portfolioB[key];
|
|
581
|
-
portfolioDelta[key] =
|
|
582
|
-
valA !== null && valB !== null ? valB - valA : null;
|
|
583
|
-
}
|
|
491
|
+
const portfolioA = buildPortfolioEntry(portfolioStatsA);
|
|
492
|
+
const portfolioB = buildPortfolioEntry(portfolioStatsB);
|
|
584
493
|
|
|
585
|
-
|
|
586
|
-
|
|
494
|
+
// Calculate deltas for portfolio totals
|
|
495
|
+
const portfolioDelta: Record<string, number | null> = {};
|
|
496
|
+
for (const key of Object.keys(portfolioA)) {
|
|
497
|
+
const valA = portfolioA[key];
|
|
498
|
+
const valB = portfolioB[key];
|
|
499
|
+
portfolioDelta[key] = valA !== null && valB !== null ? valB - valA : null;
|
|
500
|
+
}
|
|
587
501
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
502
|
+
// Brief summary for user display
|
|
503
|
+
const summary = `Block Diff: ${blockIdA} vs ${blockIdB} | ${shared.length} shared, ${uniqueToA.length} unique to A, ${uniqueToB.length} unique to B | P/L delta: ${formatCurrency(portfolioStatsB.netPl - portfolioStatsA.netPl)}`;
|
|
504
|
+
|
|
505
|
+
// Build structured output
|
|
506
|
+
const structuredData = {
|
|
507
|
+
blockA: {
|
|
508
|
+
id: blockIdA,
|
|
509
|
+
tradeCount: tradesA.length,
|
|
510
|
+
strategies: Array.from(strategiesA).sort(),
|
|
511
|
+
},
|
|
512
|
+
blockB: {
|
|
513
|
+
id: blockIdB,
|
|
514
|
+
tradeCount: tradesB.length,
|
|
515
|
+
strategies: Array.from(strategiesB).sort(),
|
|
516
|
+
},
|
|
517
|
+
strategyOverlap: {
|
|
518
|
+
shared,
|
|
519
|
+
uniqueToA,
|
|
520
|
+
uniqueToB,
|
|
521
|
+
overlapPercent,
|
|
522
|
+
},
|
|
523
|
+
perStrategyComparison,
|
|
524
|
+
portfolioTotals: {
|
|
525
|
+
blockA: portfolioA,
|
|
526
|
+
blockB: portfolioB,
|
|
527
|
+
delta: portfolioDelta,
|
|
528
|
+
},
|
|
529
|
+
filters: {
|
|
530
|
+
startDate: startDate ?? null,
|
|
531
|
+
endDate: endDate ?? null,
|
|
532
|
+
metricsToCompare: metricsToCompare ?? null,
|
|
533
|
+
},
|
|
534
|
+
};
|
|
618
535
|
|
|
619
536
|
return createToolOutput(summary, structuredData);
|
|
620
537
|
} catch (error) {
|
|
@@ -628,7 +545,7 @@ export function registerComparisonBlockTools(
|
|
|
628
545
|
isError: true,
|
|
629
546
|
};
|
|
630
547
|
}
|
|
631
|
-
}
|
|
632
|
-
)
|
|
548
|
+
},
|
|
549
|
+
),
|
|
633
550
|
);
|
|
634
551
|
}
|