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
|
@@ -7,21 +7,14 @@
|
|
|
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
|
-
formatPercent,
|
|
13
|
-
formatRatio,
|
|
14
|
-
} from "../../utils/output-formatter.ts";
|
|
10
|
+
import { createToolOutput, formatPercent, formatRatio } from "../../utils/output-formatter.ts";
|
|
15
11
|
import {
|
|
16
12
|
PortfolioStatsCalculator,
|
|
17
13
|
calculateCorrelationMatrix,
|
|
18
14
|
performTailRiskAnalysis,
|
|
19
15
|
} from "@tradeblocks/lib";
|
|
20
16
|
import type { Trade } from "@tradeblocks/lib";
|
|
21
|
-
import {
|
|
22
|
-
filterByDateRange,
|
|
23
|
-
filterDailyLogsByDateRange,
|
|
24
|
-
} from "../shared/filters.ts";
|
|
17
|
+
import { filterByDateRange, filterDailyLogsByDateRange } from "../shared/filters.ts";
|
|
25
18
|
import { withSyncedBlock } from "../middleware/sync-middleware.ts";
|
|
26
19
|
import { getConnection } from "../../db/connection.ts";
|
|
27
20
|
import { getProfile } from "../../db/profile-schemas.ts";
|
|
@@ -38,10 +31,7 @@ const SIMILARITY_DEFAULTS = {
|
|
|
38
31
|
/**
|
|
39
32
|
* Register similarity block tools
|
|
40
33
|
*/
|
|
41
|
-
export function registerSimilarityBlockTools(
|
|
42
|
-
server: McpServer,
|
|
43
|
-
baseDir: string
|
|
44
|
-
): void {
|
|
34
|
+
export function registerSimilarityBlockTools(server: McpServer, baseDir: string): void {
|
|
45
35
|
const calculator = new PortfolioStatsCalculator();
|
|
46
36
|
|
|
47
37
|
// Tool 10: strategy_similarity
|
|
@@ -58,7 +48,7 @@ export function registerSimilarityBlockTools(
|
|
|
58
48
|
.max(1)
|
|
59
49
|
.default(SIMILARITY_DEFAULTS.correlationThreshold)
|
|
60
50
|
.describe(
|
|
61
|
-
`Minimum correlation to flag as similar (default: ${SIMILARITY_DEFAULTS.correlationThreshold})
|
|
51
|
+
`Minimum correlation to flag as similar (default: ${SIMILARITY_DEFAULTS.correlationThreshold})`,
|
|
62
52
|
),
|
|
63
53
|
tailDependenceThreshold: z
|
|
64
54
|
.number()
|
|
@@ -66,7 +56,7 @@ export function registerSimilarityBlockTools(
|
|
|
66
56
|
.max(1)
|
|
67
57
|
.default(SIMILARITY_DEFAULTS.tailDependenceThreshold)
|
|
68
58
|
.describe(
|
|
69
|
-
`Minimum tail dependence to flag as high joint risk (default: ${SIMILARITY_DEFAULTS.tailDependenceThreshold})
|
|
59
|
+
`Minimum tail dependence to flag as high joint risk (default: ${SIMILARITY_DEFAULTS.tailDependenceThreshold})`,
|
|
70
60
|
),
|
|
71
61
|
method: z
|
|
72
62
|
.enum(["kendall", "spearman", "pearson"])
|
|
@@ -78,7 +68,7 @@ export function registerSimilarityBlockTools(
|
|
|
78
68
|
.min(1)
|
|
79
69
|
.default(SIMILARITY_DEFAULTS.minSharedDays)
|
|
80
70
|
.describe(
|
|
81
|
-
`Minimum shared trading days for valid comparison (default: ${SIMILARITY_DEFAULTS.minSharedDays})
|
|
71
|
+
`Minimum shared trading days for valid comparison (default: ${SIMILARITY_DEFAULTS.minSharedDays})`,
|
|
82
72
|
),
|
|
83
73
|
topN: z
|
|
84
74
|
.number()
|
|
@@ -87,7 +77,7 @@ export function registerSimilarityBlockTools(
|
|
|
87
77
|
.max(50)
|
|
88
78
|
.default(SIMILARITY_DEFAULTS.topN)
|
|
89
79
|
.describe(
|
|
90
|
-
`Number of most similar pairs to return (default: ${SIMILARITY_DEFAULTS.topN})
|
|
80
|
+
`Number of most similar pairs to return (default: ${SIMILARITY_DEFAULTS.topN})`,
|
|
91
81
|
),
|
|
92
82
|
}),
|
|
93
83
|
},
|
|
@@ -102,11 +92,9 @@ export function registerSimilarityBlockTools(
|
|
|
102
92
|
topN,
|
|
103
93
|
}) => {
|
|
104
94
|
// Apply defaults for optional parameters (zod defaults may not apply through MCP CLI)
|
|
105
|
-
const corrThreshold =
|
|
106
|
-
correlationThreshold ?? SIMILARITY_DEFAULTS.correlationThreshold;
|
|
95
|
+
const corrThreshold = correlationThreshold ?? SIMILARITY_DEFAULTS.correlationThreshold;
|
|
107
96
|
const tailThreshold =
|
|
108
|
-
tailDependenceThreshold ??
|
|
109
|
-
SIMILARITY_DEFAULTS.tailDependenceThreshold;
|
|
97
|
+
tailDependenceThreshold ?? SIMILARITY_DEFAULTS.tailDependenceThreshold;
|
|
110
98
|
const corrMethod = method ?? SIMILARITY_DEFAULTS.method;
|
|
111
99
|
const minDays = minSharedDays ?? SIMILARITY_DEFAULTS.minSharedDays;
|
|
112
100
|
const limit = topN ?? SIMILARITY_DEFAULTS.topN;
|
|
@@ -126,12 +114,10 @@ export function registerSimilarityBlockTools(
|
|
|
126
114
|
};
|
|
127
115
|
}
|
|
128
116
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
new Set(trades.map((t) => t.strategy))
|
|
132
|
-
).sort();
|
|
117
|
+
// Get unique strategies
|
|
118
|
+
const strategies = Array.from(new Set(trades.map((t) => t.strategy))).sort();
|
|
133
119
|
|
|
134
|
-
|
|
120
|
+
// Need at least 2 strategies for similarity analysis
|
|
135
121
|
if (strategies.length < 2) {
|
|
136
122
|
return {
|
|
137
123
|
content: [
|
|
@@ -144,187 +130,180 @@ export function registerSimilarityBlockTools(
|
|
|
144
130
|
};
|
|
145
131
|
}
|
|
146
132
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
133
|
+
// Calculate correlation matrix using existing utility
|
|
134
|
+
const correlationMatrix = calculateCorrelationMatrix(trades, {
|
|
135
|
+
method: corrMethod,
|
|
136
|
+
normalization: "raw",
|
|
137
|
+
dateBasis: "opened",
|
|
138
|
+
alignment: "shared",
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Calculate tail risk using existing utility
|
|
142
|
+
const tailRisk = performTailRiskAnalysis(trades, {
|
|
143
|
+
normalization: "raw",
|
|
144
|
+
dateBasis: "opened",
|
|
145
|
+
minTradingDays: minDays,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Calculate overlap scores: count shared trading days / total unique days
|
|
149
|
+
// Group trades by strategy and date
|
|
150
|
+
const strategyDates: Record<string, Set<string>> = {};
|
|
151
|
+
for (const trade of trades) {
|
|
152
|
+
if (!trade.strategy || !trade.dateOpened) continue;
|
|
153
|
+
if (!strategyDates[trade.strategy]) {
|
|
154
|
+
strategyDates[trade.strategy] = new Set();
|
|
155
|
+
}
|
|
156
|
+
// Extract date key from dateOpened
|
|
157
|
+
const dateKey = trade.dateOpened.toISOString().split("T")[0];
|
|
158
|
+
strategyDates[trade.strategy].add(dateKey);
|
|
169
159
|
}
|
|
170
|
-
// Extract date key from dateOpened
|
|
171
|
-
const dateKey = trade.dateOpened.toISOString().split("T")[0];
|
|
172
|
-
strategyDates[trade.strategy].add(dateKey);
|
|
173
|
-
}
|
|
174
160
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
161
|
+
// Build similarity pairs
|
|
162
|
+
interface SimilarPair {
|
|
163
|
+
strategyA: string;
|
|
164
|
+
strategyB: string;
|
|
165
|
+
correlation: number | null;
|
|
166
|
+
tailDependence: number | null;
|
|
167
|
+
overlapScore: number;
|
|
168
|
+
compositeSimilarity: number | null;
|
|
169
|
+
sharedTradingDays: number;
|
|
170
|
+
flags: {
|
|
171
|
+
isHighCorrelation: boolean;
|
|
172
|
+
isHighTailDependence: boolean;
|
|
173
|
+
isRedundant: boolean;
|
|
174
|
+
};
|
|
175
|
+
}
|
|
190
176
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
177
|
+
const pairs: SimilarPair[] = [];
|
|
178
|
+
let redundantPairs = 0;
|
|
179
|
+
let highCorrelationPairs = 0;
|
|
180
|
+
let highTailDependencePairs = 0;
|
|
181
|
+
|
|
182
|
+
// Iterate over unique strategy pairs (i < j)
|
|
183
|
+
for (let i = 0; i < strategies.length; i++) {
|
|
184
|
+
for (let j = i + 1; j < strategies.length; j++) {
|
|
185
|
+
const strategyA = strategies[i];
|
|
186
|
+
const strategyB = strategies[j];
|
|
187
|
+
|
|
188
|
+
// Get correlation from matrix
|
|
189
|
+
const idxA = correlationMatrix.strategies.indexOf(strategyA);
|
|
190
|
+
const idxB = correlationMatrix.strategies.indexOf(strategyB);
|
|
191
|
+
const correlation =
|
|
192
|
+
idxA >= 0 && idxB >= 0 && correlationMatrix.correlationData[idxA]
|
|
193
|
+
? correlationMatrix.correlationData[idxA][idxB]
|
|
194
|
+
: null;
|
|
195
|
+
const sharedDaysFromCorr =
|
|
196
|
+
idxA >= 0 && idxB >= 0 && correlationMatrix.sampleSizes[idxA]
|
|
197
|
+
? correlationMatrix.sampleSizes[idxA][idxB]
|
|
198
|
+
: 0;
|
|
199
|
+
|
|
200
|
+
// Get tail dependence from jointTailRiskMatrix
|
|
201
|
+
const tailIdxA = tailRisk.strategies.indexOf(strategyA);
|
|
202
|
+
const tailIdxB = tailRisk.strategies.indexOf(strategyB);
|
|
203
|
+
let tailDependence: number | null = null;
|
|
204
|
+
if (
|
|
205
|
+
tailIdxA >= 0 &&
|
|
206
|
+
tailIdxB >= 0 &&
|
|
207
|
+
tailRisk.jointTailRiskMatrix[tailIdxA] &&
|
|
208
|
+
tailRisk.jointTailRiskMatrix[tailIdxB]
|
|
209
|
+
) {
|
|
210
|
+
// Average both directions since matrix can be asymmetric
|
|
211
|
+
const valAB = tailRisk.jointTailRiskMatrix[tailIdxA][tailIdxB];
|
|
212
|
+
const valBA = tailRisk.jointTailRiskMatrix[tailIdxB][tailIdxA];
|
|
213
|
+
if (!Number.isNaN(valAB) && !Number.isNaN(valBA)) {
|
|
214
|
+
tailDependence = (valAB + valBA) / 2;
|
|
215
|
+
}
|
|
229
216
|
}
|
|
230
|
-
}
|
|
231
217
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const overlapComponent = overlapScore * 0.2;
|
|
252
|
-
compositeSimilarity =
|
|
253
|
-
corrComponent + tailComponent + overlapComponent;
|
|
254
|
-
}
|
|
218
|
+
// Calculate overlap score
|
|
219
|
+
const datesA = strategyDates[strategyA] || new Set();
|
|
220
|
+
const datesB = strategyDates[strategyB] || new Set();
|
|
221
|
+
const allDates = new Set([...datesA, ...datesB]);
|
|
222
|
+
const sharedDates = [...datesA].filter((d) => datesB.has(d)).length;
|
|
223
|
+
const overlapScore = allDates.size > 0 ? sharedDates / allDates.size : 0;
|
|
224
|
+
|
|
225
|
+
// Use sharedDaysFromCorr or calculate from overlap
|
|
226
|
+
const sharedTradingDays = sharedDaysFromCorr > 0 ? sharedDaysFromCorr : sharedDates;
|
|
227
|
+
|
|
228
|
+
// Calculate composite similarity score (weighted average)
|
|
229
|
+
// 50% correlation (absolute value), 30% tail dependence, 20% overlap score
|
|
230
|
+
let compositeSimilarity: number | null = null;
|
|
231
|
+
if (correlation !== null && !Number.isNaN(correlation)) {
|
|
232
|
+
const corrComponent = Math.abs(correlation) * 0.5;
|
|
233
|
+
const tailComponent = (tailDependence !== null ? tailDependence : 0) * 0.3;
|
|
234
|
+
const overlapComponent = overlapScore * 0.2;
|
|
235
|
+
compositeSimilarity = corrComponent + tailComponent + overlapComponent;
|
|
236
|
+
}
|
|
255
237
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
});
|
|
238
|
+
// Determine flags
|
|
239
|
+
const isHighCorrelation =
|
|
240
|
+
correlation !== null &&
|
|
241
|
+
!Number.isNaN(correlation) &&
|
|
242
|
+
Math.abs(correlation) >= corrThreshold;
|
|
243
|
+
const isHighTailDependence =
|
|
244
|
+
tailDependence !== null && tailDependence >= tailThreshold;
|
|
245
|
+
const isRedundant = isHighCorrelation && isHighTailDependence;
|
|
246
|
+
|
|
247
|
+
// Only include pairs that meet minDays requirement
|
|
248
|
+
if (sharedTradingDays >= minDays) {
|
|
249
|
+
// Update counters (only for included pairs)
|
|
250
|
+
if (isHighCorrelation) highCorrelationPairs++;
|
|
251
|
+
if (isHighTailDependence) highTailDependencePairs++;
|
|
252
|
+
if (isRedundant) redundantPairs++;
|
|
253
|
+
|
|
254
|
+
pairs.push({
|
|
255
|
+
strategyA,
|
|
256
|
+
strategyB,
|
|
257
|
+
correlation:
|
|
258
|
+
correlation !== null && !Number.isNaN(correlation) ? correlation : null,
|
|
259
|
+
tailDependence,
|
|
260
|
+
overlapScore,
|
|
261
|
+
compositeSimilarity,
|
|
262
|
+
sharedTradingDays,
|
|
263
|
+
flags: {
|
|
264
|
+
isHighCorrelation,
|
|
265
|
+
isHighTailDependence,
|
|
266
|
+
isRedundant,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
}
|
|
289
270
|
}
|
|
290
271
|
}
|
|
291
|
-
}
|
|
292
272
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
};
|
|
273
|
+
// Sort by composite similarity (highest first), handling nulls
|
|
274
|
+
pairs.sort((a, b) => {
|
|
275
|
+
if (a.compositeSimilarity === null && b.compositeSimilarity === null) return 0;
|
|
276
|
+
if (a.compositeSimilarity === null) return 1;
|
|
277
|
+
if (b.compositeSimilarity === null) return -1;
|
|
278
|
+
return b.compositeSimilarity - a.compositeSimilarity;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Apply limit
|
|
282
|
+
const topPairs = pairs.slice(0, limit);
|
|
283
|
+
|
|
284
|
+
// Build summary line
|
|
285
|
+
const mostSimilar = topPairs[0];
|
|
286
|
+
const summary = `Strategy Similarity: ${blockId} | ${strategies.length} strategies | ${redundantPairs} redundant pairs | Most similar: ${mostSimilar ? `${mostSimilar.strategyA}-${mostSimilar.strategyB} (${mostSimilar.compositeSimilarity?.toFixed(2) ?? "N/A"})` : "N/A"}`;
|
|
287
|
+
|
|
288
|
+
// Build structured data
|
|
289
|
+
const structuredData = {
|
|
290
|
+
blockId,
|
|
291
|
+
options: {
|
|
292
|
+
correlationThreshold: corrThreshold,
|
|
293
|
+
tailDependenceThreshold: tailThreshold,
|
|
294
|
+
method: corrMethod,
|
|
295
|
+
minSharedDays: minDays,
|
|
296
|
+
topN: limit,
|
|
297
|
+
},
|
|
298
|
+
strategySummary: {
|
|
299
|
+
totalStrategies: strategies.length,
|
|
300
|
+
totalPairs: (strategies.length * (strategies.length - 1)) / 2,
|
|
301
|
+
redundantPairs,
|
|
302
|
+
highCorrelationPairs,
|
|
303
|
+
highTailDependencePairs,
|
|
304
|
+
},
|
|
305
|
+
similarPairs: topPairs,
|
|
306
|
+
};
|
|
328
307
|
|
|
329
308
|
return createToolOutput(summary, structuredData);
|
|
330
309
|
} catch (error) {
|
|
@@ -338,8 +317,8 @@ export function registerSimilarityBlockTools(
|
|
|
338
317
|
isError: true as const,
|
|
339
318
|
};
|
|
340
319
|
}
|
|
341
|
-
}
|
|
342
|
-
)
|
|
320
|
+
},
|
|
321
|
+
),
|
|
343
322
|
);
|
|
344
323
|
|
|
345
324
|
// Tool 11: what_if_scaling
|
|
@@ -349,29 +328,58 @@ export function registerSimilarityBlockTools(
|
|
|
349
328
|
description:
|
|
350
329
|
"Explore strategy weight combinations within a portfolio. Answer 'what if I scaled strategy X to 0.5x?' questions. Shows before/after comparison with per-strategy breakdown. Profile-aware: uses backtest block data, enforces maxContractsPerTrade ceilings, flags ignoreMarginReq. Multi-strategy mode combines trades from multiple blocks.",
|
|
351
330
|
inputSchema: z.object({
|
|
352
|
-
blockId: z
|
|
331
|
+
blockId: z
|
|
332
|
+
.string()
|
|
333
|
+
.describe(
|
|
334
|
+
"Block folder name (required for single-strategy mode, optional default for multi-strategy mode)",
|
|
335
|
+
),
|
|
353
336
|
strategyWeights: z
|
|
354
337
|
.record(z.string(), z.number().min(0).max(2))
|
|
355
338
|
.optional()
|
|
356
339
|
.describe(
|
|
357
|
-
'Weight per strategy, e.g., {"5/7 17Δ": 0.5}. Unspecified strategies default to 1.0. Weight 0 = exclude strategy entirely. Max weight: 2.0. Ignored when strategies array is provided.'
|
|
340
|
+
'Weight per strategy, e.g., {"5/7 17Δ": 0.5}. Unspecified strategies default to 1.0. Weight 0 = exclude strategy entirely. Max weight: 2.0. Ignored when strategies array is provided.',
|
|
358
341
|
),
|
|
359
|
-
strategies: z
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
342
|
+
strategies: z
|
|
343
|
+
.array(
|
|
344
|
+
z.object({
|
|
345
|
+
strategyName: z.string().describe("Strategy name matching a stored profile"),
|
|
346
|
+
blockId: z
|
|
347
|
+
.string()
|
|
348
|
+
.describe(
|
|
349
|
+
"Block ID to source trades from (overrides top-level blockId for this strategy)",
|
|
350
|
+
),
|
|
351
|
+
scaleFactor: z
|
|
352
|
+
.number()
|
|
353
|
+
.min(0)
|
|
354
|
+
.max(5)
|
|
355
|
+
.describe("Scale factor for this strategy (1.0 = current allocation)"),
|
|
356
|
+
}),
|
|
357
|
+
)
|
|
367
358
|
.optional()
|
|
368
|
-
.describe(
|
|
359
|
+
.describe(
|
|
360
|
+
"Multi-strategy mode: array of strategies with per-strategy block source and scale. When provided, ignores strategyWeights.",
|
|
361
|
+
),
|
|
362
|
+
showUncapped: z
|
|
363
|
+
.boolean()
|
|
364
|
+
.optional()
|
|
365
|
+
.default(false)
|
|
366
|
+
.describe(
|
|
367
|
+
"When true, also run without maxContractsPerTrade ceiling for side-by-side comparison",
|
|
368
|
+
),
|
|
369
|
+
startDate: z.string().optional().describe("Start date filter (YYYY-MM-DD)"),
|
|
369
370
|
endDate: z.string().optional().describe("End date filter (YYYY-MM-DD)"),
|
|
370
371
|
}),
|
|
371
372
|
},
|
|
372
373
|
withSyncedBlock(
|
|
373
374
|
baseDir,
|
|
374
|
-
async ({
|
|
375
|
+
async ({
|
|
376
|
+
blockId,
|
|
377
|
+
strategyWeights,
|
|
378
|
+
strategies: strategiesInput,
|
|
379
|
+
showUncapped,
|
|
380
|
+
startDate,
|
|
381
|
+
endDate,
|
|
382
|
+
}) => {
|
|
375
383
|
try {
|
|
376
384
|
const useMultiStrategy = strategiesInput && strategiesInput.length > 0;
|
|
377
385
|
const doShowUncapped = showUncapped ?? false;
|
|
@@ -379,7 +387,7 @@ export function registerSimilarityBlockTools(
|
|
|
379
387
|
// Helper: try to get a profile for a strategy (best-effort)
|
|
380
388
|
async function tryGetProfile(
|
|
381
389
|
strategyBlockId: string,
|
|
382
|
-
strategyName: string
|
|
390
|
+
strategyName: string,
|
|
383
391
|
): Promise<{ profile: StrategyProfile | null; status: "found" | "not_found" | "error" }> {
|
|
384
392
|
try {
|
|
385
393
|
const conn = await getConnection(baseDir);
|
|
@@ -395,7 +403,7 @@ export function registerSimilarityBlockTools(
|
|
|
395
403
|
// Helper: filter trades by strategy with single-strategy fallback
|
|
396
404
|
function filterTradesByStrategy(allTrades: Trade[], strategyName: string): Trade[] {
|
|
397
405
|
const matched = allTrades.filter(
|
|
398
|
-
(t) => t.strategy.toLowerCase() === strategyName.toLowerCase()
|
|
406
|
+
(t) => t.strategy.toLowerCase() === strategyName.toLowerCase(),
|
|
399
407
|
);
|
|
400
408
|
if (matched.length > 0) return matched;
|
|
401
409
|
// Single-strategy block fallback
|
|
@@ -416,7 +424,7 @@ export function registerSimilarityBlockTools(
|
|
|
416
424
|
function buildScaledTrades(
|
|
417
425
|
tradesToScale: Trade[],
|
|
418
426
|
weights: Record<string, number>,
|
|
419
|
-
maxContractsCeilings?: Record<string, number
|
|
427
|
+
maxContractsCeilings?: Record<string, number>,
|
|
420
428
|
): { scaledTrades: ScaledTrade[]; cappedStrategies: Set<string> } {
|
|
421
429
|
const scaled: ScaledTrade[] = [];
|
|
422
430
|
const cappedStrategies = new Set<string>();
|
|
@@ -452,7 +460,7 @@ export function registerSimilarityBlockTools(
|
|
|
452
460
|
// Helper: build modified Trade[] with recalculated fundsAtClose for portfolio stats
|
|
453
461
|
function buildModifiedTrades(
|
|
454
462
|
scaledTrades: ScaledTrade[],
|
|
455
|
-
originalTrades: Trade[]
|
|
463
|
+
originalTrades: Trade[],
|
|
456
464
|
): Trade[] {
|
|
457
465
|
const sortedOriginal = [...originalTrades]
|
|
458
466
|
.filter((t) => t.dateClosed && t.fundsAtClose !== undefined)
|
|
@@ -498,7 +506,7 @@ export function registerSimilarityBlockTools(
|
|
|
498
506
|
// Helper: calculate comparison deltas
|
|
499
507
|
function calcDelta(
|
|
500
508
|
original: number | null,
|
|
501
|
-
scaled: number | null
|
|
509
|
+
scaled: number | null,
|
|
502
510
|
): {
|
|
503
511
|
original: number | null;
|
|
504
512
|
scaled: number | null;
|
|
@@ -509,8 +517,7 @@ export function registerSimilarityBlockTools(
|
|
|
509
517
|
return { original, scaled, delta: null, deltaPct: null };
|
|
510
518
|
}
|
|
511
519
|
const delta = scaled - original;
|
|
512
|
-
const deltaPct =
|
|
513
|
-
original !== 0 ? (delta / Math.abs(original)) * 100 : null;
|
|
520
|
+
const deltaPct = original !== 0 ? (delta / Math.abs(original)) * 100 : null;
|
|
514
521
|
return { original, scaled, delta, deltaPct };
|
|
515
522
|
}
|
|
516
523
|
|
|
@@ -538,11 +545,19 @@ export function registerSimilarityBlockTools(
|
|
|
538
545
|
const allScaledTrades: ScaledTrade[] = [];
|
|
539
546
|
const allOriginalTrades: Trade[] = [];
|
|
540
547
|
const perStrategyBreakdown: MultiStrategyBreakdown[] = [];
|
|
541
|
-
const dataAvailability: {
|
|
548
|
+
const dataAvailability: {
|
|
549
|
+
strategy: string;
|
|
550
|
+
blockId: string;
|
|
551
|
+
profileStatus: string;
|
|
552
|
+
profileBlockId?: string;
|
|
553
|
+
}[] = [];
|
|
542
554
|
|
|
543
555
|
for (const entry of strategiesInput!) {
|
|
544
556
|
const sourceBlockId = entry.blockId || blockId;
|
|
545
|
-
const { profile, status: profileStatus } = await tryGetProfile(
|
|
557
|
+
const { profile, status: profileStatus } = await tryGetProfile(
|
|
558
|
+
sourceBlockId,
|
|
559
|
+
entry.strategyName,
|
|
560
|
+
);
|
|
546
561
|
|
|
547
562
|
// Determine which block to load trades from
|
|
548
563
|
let tradeSourceBlockId = sourceBlockId;
|
|
@@ -600,7 +615,7 @@ export function registerSimilarityBlockTools(
|
|
|
600
615
|
// Build weights and ceilings for this strategy's trades
|
|
601
616
|
const weightsMap: Record<string, number> = {};
|
|
602
617
|
const ceilings: Record<string, number> = {};
|
|
603
|
-
const uniqueNames = Array.from(new Set(entryTrades.map(t => t.strategy)));
|
|
618
|
+
const uniqueNames = Array.from(new Set(entryTrades.map((t) => t.strategy)));
|
|
604
619
|
for (const name of uniqueNames) {
|
|
605
620
|
weightsMap[name] = entry.scaleFactor;
|
|
606
621
|
if (profile?.positionSizing?.maxContractsPerTrade !== undefined) {
|
|
@@ -609,7 +624,9 @@ export function registerSimilarityBlockTools(
|
|
|
609
624
|
}
|
|
610
625
|
|
|
611
626
|
const { scaledTrades: entryScaled, cappedStrategies } = buildScaledTrades(
|
|
612
|
-
entryTrades,
|
|
627
|
+
entryTrades,
|
|
628
|
+
weightsMap,
|
|
629
|
+
Object.keys(ceilings).length > 0 ? ceilings : undefined,
|
|
613
630
|
);
|
|
614
631
|
|
|
615
632
|
allOriginalTrades.push(...entryTrades);
|
|
@@ -644,7 +661,8 @@ export function registerSimilarityBlockTools(
|
|
|
644
661
|
|
|
645
662
|
if (profile?.ignoreMarginReq) {
|
|
646
663
|
breakdown.ignoreMarginReq = true;
|
|
647
|
-
breakdown.marginNote =
|
|
664
|
+
breakdown.marginNote =
|
|
665
|
+
"Strategy ignores margin requirements. Scaled notional exposure shown but buying power impact not estimated.";
|
|
648
666
|
breakdown.scaledNotionalExposure = Math.abs(scaledNetPl);
|
|
649
667
|
}
|
|
650
668
|
|
|
@@ -654,20 +672,21 @@ export function registerSimilarityBlockTools(
|
|
|
654
672
|
// Calculate contribution percentages
|
|
655
673
|
const totalCombinedPl = perStrategyBreakdown.reduce((sum, b) => sum + b.scaledNetPl, 0);
|
|
656
674
|
for (const b of perStrategyBreakdown) {
|
|
657
|
-
b.plContributionPct =
|
|
658
|
-
? (b.scaledNetPl / Math.abs(totalCombinedPl)) * 100
|
|
659
|
-
: 0;
|
|
675
|
+
b.plContributionPct =
|
|
676
|
+
totalCombinedPl !== 0 ? (b.scaledNetPl / Math.abs(totalCombinedPl)) * 100 : 0;
|
|
660
677
|
}
|
|
661
678
|
|
|
662
679
|
// Calculate combined portfolio stats
|
|
663
680
|
const modifiedTrades = buildModifiedTrades(allScaledTrades, allOriginalTrades);
|
|
664
681
|
const combinedStats = calculator.calculatePortfolioStats(
|
|
665
|
-
modifiedTrades,
|
|
682
|
+
modifiedTrades,
|
|
683
|
+
undefined,
|
|
684
|
+
true,
|
|
666
685
|
);
|
|
667
686
|
|
|
668
687
|
// Build uncapped comparison if requested and any strategy was capped
|
|
669
688
|
let uncappedComparison: Record<string, unknown> | undefined;
|
|
670
|
-
const anyCapped = perStrategyBreakdown.some(b => b.capped);
|
|
689
|
+
const anyCapped = perStrategyBreakdown.some((b) => b.capped);
|
|
671
690
|
if (doShowUncapped && anyCapped) {
|
|
672
691
|
const uncappedScaled: ScaledTrade[] = [];
|
|
673
692
|
for (const entry of strategiesInput!) {
|
|
@@ -682,16 +701,22 @@ export function registerSimilarityBlockTools(
|
|
|
682
701
|
let entryTrades = filterTradesByStrategy(entryBlock.trades, entry.strategyName);
|
|
683
702
|
entryTrades = filterByDateRange(entryTrades, startDate, endDate);
|
|
684
703
|
const wm: Record<string, number> = {};
|
|
685
|
-
for (const name of new Set(entryTrades.map(t => t.strategy))) {
|
|
704
|
+
for (const name of new Set(entryTrades.map((t) => t.strategy))) {
|
|
686
705
|
wm[name] = entry.scaleFactor;
|
|
687
706
|
}
|
|
688
707
|
const { scaledTrades: ust } = buildScaledTrades(entryTrades, wm);
|
|
689
708
|
uncappedScaled.push(...ust);
|
|
690
|
-
} catch {
|
|
709
|
+
} catch {
|
|
710
|
+
/* skip */
|
|
711
|
+
}
|
|
691
712
|
}
|
|
692
713
|
if (uncappedScaled.length > 0) {
|
|
693
714
|
const uncappedModified = buildModifiedTrades(uncappedScaled, allOriginalTrades);
|
|
694
|
-
const uncappedStats = calculator.calculatePortfolioStats(
|
|
715
|
+
const uncappedStats = calculator.calculatePortfolioStats(
|
|
716
|
+
uncappedModified,
|
|
717
|
+
undefined,
|
|
718
|
+
true,
|
|
719
|
+
);
|
|
695
720
|
uncappedComparison = {
|
|
696
721
|
sharpeRatio: uncappedStats.sharpeRatio,
|
|
697
722
|
sortinoRatio: uncappedStats.sortinoRatio,
|
|
@@ -748,9 +773,7 @@ export function registerSimilarityBlockTools(
|
|
|
748
773
|
}
|
|
749
774
|
|
|
750
775
|
// Get all unique strategies
|
|
751
|
-
const allStrategies = Array.from(
|
|
752
|
-
new Set(trades.map((t) => t.strategy))
|
|
753
|
-
).sort();
|
|
776
|
+
const allStrategies = Array.from(new Set(trades.map((t) => t.strategy))).sort();
|
|
754
777
|
|
|
755
778
|
// Build applied weights (default 1.0 for unspecified)
|
|
756
779
|
const appliedWeights: Record<string, number> = {};
|
|
@@ -763,7 +786,7 @@ export function registerSimilarityBlockTools(
|
|
|
763
786
|
if (strategyWeights) {
|
|
764
787
|
for (const [strategy, weight] of Object.entries(strategyWeights)) {
|
|
765
788
|
const matchedStrategy = allStrategies.find(
|
|
766
|
-
(s) => s.toLowerCase() === strategy.toLowerCase()
|
|
789
|
+
(s) => s.toLowerCase() === strategy.toLowerCase(),
|
|
767
790
|
);
|
|
768
791
|
if (matchedStrategy) {
|
|
769
792
|
appliedWeights[matchedStrategy] = weight;
|
|
@@ -773,9 +796,7 @@ export function registerSimilarityBlockTools(
|
|
|
773
796
|
}
|
|
774
797
|
}
|
|
775
798
|
|
|
776
|
-
const allZeroWeight = Object.values(appliedWeights).every(
|
|
777
|
-
(w) => w === 0
|
|
778
|
-
);
|
|
799
|
+
const allZeroWeight = Object.values(appliedWeights).every((w) => w === 0);
|
|
779
800
|
if (allZeroWeight) {
|
|
780
801
|
return {
|
|
781
802
|
content: [
|
|
@@ -789,11 +810,18 @@ export function registerSimilarityBlockTools(
|
|
|
789
810
|
}
|
|
790
811
|
|
|
791
812
|
// Profile-aware enhancements: look up profiles for each strategy (best-effort)
|
|
792
|
-
const profileLookups: Record<
|
|
813
|
+
const profileLookups: Record<
|
|
814
|
+
string,
|
|
815
|
+
{ profile: StrategyProfile | null; status: "found" | "not_found" | "error" }
|
|
816
|
+
> = {};
|
|
793
817
|
const maxContractsCeilings: Record<string, number> = {};
|
|
794
818
|
const dataSourceMap: Record<string, string> = {};
|
|
795
819
|
let tradesToUse = trades;
|
|
796
|
-
const backtestSubstitutions: {
|
|
820
|
+
const backtestSubstitutions: {
|
|
821
|
+
strategy: string;
|
|
822
|
+
originalBlockId: string;
|
|
823
|
+
backtestBlockId: string;
|
|
824
|
+
}[] = [];
|
|
797
825
|
|
|
798
826
|
for (const strategy of allStrategies) {
|
|
799
827
|
const lookup = await tryGetProfile(blockId, strategy);
|
|
@@ -810,7 +838,7 @@ export function registerSimilarityBlockTools(
|
|
|
810
838
|
|
|
811
839
|
if (backtestTrades.length > 0) {
|
|
812
840
|
tradesToUse = tradesToUse.filter(
|
|
813
|
-
(t) => t.strategy.toLowerCase() !== strategy.toLowerCase()
|
|
841
|
+
(t) => t.strategy.toLowerCase() !== strategy.toLowerCase(),
|
|
814
842
|
);
|
|
815
843
|
tradesToUse = [...tradesToUse, ...backtestTrades];
|
|
816
844
|
dataSourceMap[strategy] = "standalone_backtest";
|
|
@@ -833,9 +861,10 @@ export function registerSimilarityBlockTools(
|
|
|
833
861
|
}
|
|
834
862
|
|
|
835
863
|
// Calculate baseline portfolio metrics (from original trades, before substitution)
|
|
836
|
-
const dailyLogs =
|
|
837
|
-
|
|
838
|
-
|
|
864
|
+
const dailyLogs =
|
|
865
|
+
block.dailyLogs && block.dailyLogs.length > 0
|
|
866
|
+
? filterDailyLogsByDateRange(block.dailyLogs, startDate, endDate)
|
|
867
|
+
: undefined;
|
|
839
868
|
const baselineStats = calculator.calculatePortfolioStats(
|
|
840
869
|
trades,
|
|
841
870
|
dailyLogs && dailyLogs.length > 0 ? dailyLogs : undefined,
|
|
@@ -846,26 +875,37 @@ export function registerSimilarityBlockTools(
|
|
|
846
875
|
const { scaledTrades, cappedStrategies } = buildScaledTrades(
|
|
847
876
|
tradesToUse,
|
|
848
877
|
appliedWeights,
|
|
849
|
-
hasCeilings ? maxContractsCeilings : undefined
|
|
878
|
+
hasCeilings ? maxContractsCeilings : undefined,
|
|
850
879
|
);
|
|
851
880
|
|
|
852
881
|
const modifiedTrades = buildModifiedTrades(scaledTrades, trades);
|
|
853
|
-
const scaledStats = calculator.calculatePortfolioStats(
|
|
854
|
-
modifiedTrades, undefined, true
|
|
855
|
-
);
|
|
882
|
+
const scaledStats = calculator.calculatePortfolioStats(modifiedTrades, undefined, true);
|
|
856
883
|
|
|
857
884
|
// Uncapped comparison if requested and any strategy was capped
|
|
858
885
|
let uncappedComparison: Record<string, unknown> | undefined;
|
|
859
886
|
if (doShowUncapped && cappedStrategies.size > 0) {
|
|
860
887
|
const { scaledTrades: uncappedScaled } = buildScaledTrades(tradesToUse, appliedWeights);
|
|
861
888
|
const uncappedModified = buildModifiedTrades(uncappedScaled, trades);
|
|
862
|
-
const uncappedStats = calculator.calculatePortfolioStats(
|
|
889
|
+
const uncappedStats = calculator.calculatePortfolioStats(
|
|
890
|
+
uncappedModified,
|
|
891
|
+
undefined,
|
|
892
|
+
true,
|
|
893
|
+
);
|
|
863
894
|
uncappedComparison = {
|
|
864
|
-
sharpeRatio: calcDelta(
|
|
865
|
-
|
|
895
|
+
sharpeRatio: calcDelta(
|
|
896
|
+
baselineStats.sharpeRatio ?? null,
|
|
897
|
+
uncappedStats.sharpeRatio ?? null,
|
|
898
|
+
),
|
|
899
|
+
sortinoRatio: calcDelta(
|
|
900
|
+
baselineStats.sortinoRatio ?? null,
|
|
901
|
+
uncappedStats.sortinoRatio ?? null,
|
|
902
|
+
),
|
|
866
903
|
maxDrawdown: calcDelta(baselineStats.maxDrawdown, uncappedStats.maxDrawdown),
|
|
867
904
|
netPl: calcDelta(baselineStats.netPl, uncappedStats.netPl),
|
|
868
|
-
totalTrades: {
|
|
905
|
+
totalTrades: {
|
|
906
|
+
original: baselineStats.totalTrades,
|
|
907
|
+
scaled: uncappedStats.totalTrades,
|
|
908
|
+
},
|
|
869
909
|
};
|
|
870
910
|
}
|
|
871
911
|
|
|
@@ -873,16 +913,13 @@ export function registerSimilarityBlockTools(
|
|
|
873
913
|
const comparison = {
|
|
874
914
|
sharpeRatio: calcDelta(
|
|
875
915
|
baselineStats.sharpeRatio ?? null,
|
|
876
|
-
scaledStats.sharpeRatio ?? null
|
|
916
|
+
scaledStats.sharpeRatio ?? null,
|
|
877
917
|
),
|
|
878
918
|
sortinoRatio: calcDelta(
|
|
879
919
|
baselineStats.sortinoRatio ?? null,
|
|
880
|
-
scaledStats.sortinoRatio ?? null
|
|
881
|
-
),
|
|
882
|
-
maxDrawdown: calcDelta(
|
|
883
|
-
baselineStats.maxDrawdown,
|
|
884
|
-
scaledStats.maxDrawdown
|
|
920
|
+
scaledStats.sortinoRatio ?? null,
|
|
885
921
|
),
|
|
922
|
+
maxDrawdown: calcDelta(baselineStats.maxDrawdown, scaledStats.maxDrawdown),
|
|
886
923
|
netPl: calcDelta(baselineStats.netPl, scaledStats.netPl),
|
|
887
924
|
totalTrades: {
|
|
888
925
|
original: baselineStats.totalTrades,
|
|
@@ -944,10 +981,9 @@ export function registerSimilarityBlockTools(
|
|
|
944
981
|
}
|
|
945
982
|
|
|
946
983
|
// Merge all strategy names (original + any from backtest substitution)
|
|
947
|
-
const allStrategyNames = Array.from(
|
|
948
|
-
...allStrategies,
|
|
949
|
-
|
|
950
|
-
])).sort();
|
|
984
|
+
const allStrategyNames = Array.from(
|
|
985
|
+
new Set([...allStrategies, ...Object.keys(originalByStrategy)]),
|
|
986
|
+
).sort();
|
|
951
987
|
|
|
952
988
|
for (const strategy of allStrategyNames) {
|
|
953
989
|
const weight = appliedWeights[strategy] ?? 1.0;
|
|
@@ -955,17 +991,12 @@ export function registerSimilarityBlockTools(
|
|
|
955
991
|
const scaled = scaledByStrategy[strategy] ?? { trades: 0, netPl: 0 };
|
|
956
992
|
|
|
957
993
|
const origContributionPct =
|
|
958
|
-
totalOriginalPl !== 0
|
|
959
|
-
? (orig.netPl / Math.abs(totalOriginalPl)) * 100
|
|
960
|
-
: 0;
|
|
994
|
+
totalOriginalPl !== 0 ? (orig.netPl / Math.abs(totalOriginalPl)) * 100 : 0;
|
|
961
995
|
const scaledContributionPct =
|
|
962
|
-
totalScaledPl !== 0
|
|
963
|
-
? (scaled.netPl / Math.abs(totalScaledPl)) * 100
|
|
964
|
-
: 0;
|
|
996
|
+
totalScaledPl !== 0 ? (scaled.netPl / Math.abs(totalScaledPl)) * 100 : 0;
|
|
965
997
|
|
|
966
998
|
const deltaNetPl = scaled.netPl - orig.netPl;
|
|
967
|
-
const deltaNetPlPct =
|
|
968
|
-
orig.netPl !== 0 ? (deltaNetPl / Math.abs(orig.netPl)) * 100 : 0;
|
|
999
|
+
const deltaNetPlPct = orig.netPl !== 0 ? (deltaNetPl / Math.abs(orig.netPl)) * 100 : 0;
|
|
969
1000
|
|
|
970
1001
|
const lookup = profileLookups[strategy];
|
|
971
1002
|
const breakdown: StrategyBreakdown = {
|
|
@@ -998,7 +1029,8 @@ export function registerSimilarityBlockTools(
|
|
|
998
1029
|
// ANLYS-07: ignoreMarginReq flag
|
|
999
1030
|
if (lookup?.profile?.ignoreMarginReq) {
|
|
1000
1031
|
breakdown.ignoreMarginReq = true;
|
|
1001
|
-
breakdown.marginNote =
|
|
1032
|
+
breakdown.marginNote =
|
|
1033
|
+
"Strategy ignores margin requirements. Scaled notional exposure shown but buying power impact not estimated.";
|
|
1002
1034
|
breakdown.scaledNotionalExposure = Math.abs(scaled.netPl);
|
|
1003
1035
|
}
|
|
1004
1036
|
|
|
@@ -1021,21 +1053,22 @@ export function registerSimilarityBlockTools(
|
|
|
1021
1053
|
start: startDate ?? null,
|
|
1022
1054
|
end: endDate ?? null,
|
|
1023
1055
|
},
|
|
1024
|
-
unknownStrategies:
|
|
1025
|
-
unknownStrategies.length > 0 ? unknownStrategies : undefined,
|
|
1056
|
+
unknownStrategies: unknownStrategies.length > 0 ? unknownStrategies : undefined,
|
|
1026
1057
|
comparison,
|
|
1027
1058
|
perStrategy,
|
|
1028
1059
|
};
|
|
1029
1060
|
|
|
1030
1061
|
// Add profile-aware metadata only when profiles were found
|
|
1031
|
-
const anyProfileFound = Object.values(profileLookups).some(l => l.status === "found");
|
|
1062
|
+
const anyProfileFound = Object.values(profileLookups).some((l) => l.status === "found");
|
|
1032
1063
|
if (anyProfileFound) {
|
|
1033
|
-
structuredData.dataAvailability = Object.entries(profileLookups).map(
|
|
1034
|
-
strategy,
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1064
|
+
structuredData.dataAvailability = Object.entries(profileLookups).map(
|
|
1065
|
+
([strategy, lookup]) => ({
|
|
1066
|
+
strategy,
|
|
1067
|
+
profileStatus: lookup.status,
|
|
1068
|
+
profileBlockId: lookup.profile?.blockId,
|
|
1069
|
+
dataSource: dataSourceMap[strategy],
|
|
1070
|
+
}),
|
|
1071
|
+
);
|
|
1039
1072
|
}
|
|
1040
1073
|
|
|
1041
1074
|
if (backtestSubstitutions.length > 0) {
|
|
@@ -1058,7 +1091,7 @@ export function registerSimilarityBlockTools(
|
|
|
1058
1091
|
isError: true,
|
|
1059
1092
|
};
|
|
1060
1093
|
}
|
|
1061
|
-
}
|
|
1062
|
-
)
|
|
1094
|
+
},
|
|
1095
|
+
),
|
|
1063
1096
|
);
|
|
1064
1097
|
}
|