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,11 +7,7 @@
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
9
|
import { loadBlock, loadReportingLog } from "../../utils/block-loader.ts";
|
|
10
|
-
import {
|
|
11
|
-
createToolOutput,
|
|
12
|
-
formatPercent,
|
|
13
|
-
formatCurrency,
|
|
14
|
-
} from "../../utils/output-formatter.ts";
|
|
10
|
+
import { createToolOutput, formatPercent, formatCurrency } from "../../utils/output-formatter.ts";
|
|
15
11
|
import type { ReportingTrade } from "@tradeblocks/lib";
|
|
16
12
|
import { pearsonCorrelation, kendallTau } from "@tradeblocks/lib";
|
|
17
13
|
import {
|
|
@@ -25,10 +21,7 @@ import { withSyncedBlock } from "../middleware/sync-middleware.ts";
|
|
|
25
21
|
/**
|
|
26
22
|
* Register the analyze_discrepancies tool
|
|
27
23
|
*/
|
|
28
|
-
export function registerDiscrepancyTool(
|
|
29
|
-
server: McpServer,
|
|
30
|
-
baseDir: string
|
|
31
|
-
): void {
|
|
24
|
+
export function registerDiscrepancyTool(server: McpServer, baseDir: string): void {
|
|
32
25
|
server.registerTool(
|
|
33
26
|
"analyze_discrepancies",
|
|
34
27
|
{
|
|
@@ -36,10 +29,7 @@ export function registerDiscrepancyTool(
|
|
|
36
29
|
"Analyze slippage patterns between backtest and actual trades. Detects systematic biases (direction, time-of-day) and correlates slippage with market conditions (VIX, gap, movement). Matches trades by date+strategy+time (minute precision). Requires both tradelog.csv (backtest) and reportinglog.csv (actual). Limitation: If multiple trades share the same date+strategy+minute, matching is order-dependent.",
|
|
37
30
|
inputSchema: z.object({
|
|
38
31
|
blockId: z.string().describe("Block folder name"),
|
|
39
|
-
strategy: z
|
|
40
|
-
.string()
|
|
41
|
-
.optional()
|
|
42
|
-
.describe("Filter to specific strategy name"),
|
|
32
|
+
strategy: z.string().optional().describe("Filter to specific strategy name"),
|
|
43
33
|
dateRange: z
|
|
44
34
|
.object({
|
|
45
35
|
from: z.string().optional().describe("Start date YYYY-MM-DD"),
|
|
@@ -65,9 +55,7 @@ export function registerDiscrepancyTool(
|
|
|
65
55
|
.min(0.5)
|
|
66
56
|
.max(0.95)
|
|
67
57
|
.default(0.7)
|
|
68
|
-
.describe(
|
|
69
|
-
"Threshold for detecting systematic patterns (0.7 = 70% consistency)"
|
|
70
|
-
),
|
|
58
|
+
.describe("Threshold for detecting systematic patterns (0.7 = 70% consistency)"),
|
|
71
59
|
}),
|
|
72
60
|
},
|
|
73
61
|
withSyncedBlock(
|
|
@@ -83,353 +71,335 @@ export function registerDiscrepancyTool(
|
|
|
83
71
|
}) => {
|
|
84
72
|
try {
|
|
85
73
|
const block = await loadBlock(baseDir, blockId);
|
|
86
|
-
|
|
74
|
+
let backtestTrades = block.trades;
|
|
75
|
+
|
|
76
|
+
// Load reporting log (actual trades)
|
|
77
|
+
let actualTrades: ReportingTrade[];
|
|
78
|
+
try {
|
|
79
|
+
actualTrades = await loadReportingLog(baseDir, blockId);
|
|
80
|
+
} catch {
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: `No reportinglog.csv found in block "${blockId}". This tool requires both tradelog.csv (backtest) and reportinglog.csv (actual).`,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
87
91
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
// Apply filters
|
|
93
|
+
backtestTrades = applyStrategyFilter(backtestTrades, strategy);
|
|
94
|
+
actualTrades = applyStrategyFilter(actualTrades, strategy);
|
|
95
|
+
backtestTrades = applyDateRangeFilter(backtestTrades, dateRange);
|
|
96
|
+
actualTrades = applyDateRangeFilter(actualTrades, dateRange);
|
|
97
|
+
|
|
98
|
+
if (backtestTrades.length === 0) {
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: "No backtest trades found in tradelog.csv matching filters.",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
isError: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
103
109
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
if (actualTrades.length === 0) {
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: "No actual trades found in reportinglog.csv matching filters.",
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
isError: true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
109
121
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
|
-
isError: true,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
122
|
+
// Match trades
|
|
123
|
+
const { matchedTrades, unmatchedBacktestCount, unmatchedActualCount } = matchTrades(
|
|
124
|
+
backtestTrades,
|
|
125
|
+
actualTrades,
|
|
126
|
+
scaling,
|
|
127
|
+
);
|
|
121
128
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
if (matchedTrades.length === 0) {
|
|
130
|
+
return {
|
|
131
|
+
content: [
|
|
132
|
+
{
|
|
133
|
+
type: "text",
|
|
134
|
+
text: "No matching trades found between backtest and actual data. Cannot perform slippage analysis.",
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
isError: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Calculate date range from matched trades
|
|
142
|
+
const dates = matchedTrades.map((t) => t.date).sort();
|
|
143
|
+
const dateRangeResult = {
|
|
144
|
+
from: dates[0],
|
|
145
|
+
to: dates[dates.length - 1],
|
|
131
146
|
};
|
|
132
|
-
}
|
|
133
147
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
148
|
+
// Calculate summary statistics
|
|
149
|
+
const slippages = matchedTrades.map((t) => t.totalSlippage);
|
|
150
|
+
const totalSlippage = slippages.reduce((sum, s) => sum + s, 0);
|
|
151
|
+
const avgSlippagePerTrade = totalSlippage / matchedTrades.length;
|
|
137
152
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
],
|
|
146
|
-
isError: true,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
153
|
+
// Pattern insight interface
|
|
154
|
+
interface PatternInsight {
|
|
155
|
+
pattern: string;
|
|
156
|
+
metric: string;
|
|
157
|
+
value: number;
|
|
158
|
+
sampleSize: number;
|
|
159
|
+
}
|
|
149
160
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
from: dates[0],
|
|
154
|
-
to: dates[dates.length - 1],
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
// Calculate summary statistics
|
|
158
|
-
const slippages = matchedTrades.map((t) => t.totalSlippage);
|
|
159
|
-
const totalSlippage = slippages.reduce((sum, s) => sum + s, 0);
|
|
160
|
-
const avgSlippagePerTrade = totalSlippage / matchedTrades.length;
|
|
161
|
-
|
|
162
|
-
// Pattern insight interface
|
|
163
|
-
interface PatternInsight {
|
|
164
|
-
pattern: string;
|
|
165
|
-
metric: string;
|
|
166
|
-
value: number;
|
|
167
|
-
sampleSize: number;
|
|
168
|
-
}
|
|
161
|
+
// Pattern detection function
|
|
162
|
+
const detectPatterns = (trades: MatchedTradeData[]): PatternInsight[] => {
|
|
163
|
+
const patterns: PatternInsight[] = [];
|
|
169
164
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
): PatternInsight[] => {
|
|
174
|
-
const patterns: PatternInsight[] = [];
|
|
165
|
+
if (trades.length < minSamples) {
|
|
166
|
+
return patterns;
|
|
167
|
+
}
|
|
175
168
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
169
|
+
// 1. Direction bias - if >patternThreshold of slippages are same sign
|
|
170
|
+
const tradeSlippages = trades.map((t) => t.totalSlippage);
|
|
171
|
+
const positiveCount = tradeSlippages.filter((s) => s > 0).length;
|
|
172
|
+
const negativeCount = tradeSlippages.filter((s) => s < 0).length;
|
|
173
|
+
const positiveRate = positiveCount / tradeSlippages.length;
|
|
174
|
+
const negativeRate = negativeCount / tradeSlippages.length;
|
|
179
175
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
patterns.push({
|
|
196
|
-
pattern: `Direction bias: ${formatPercent(negativeRate * 100)} of trades have negative slippage (actual < backtest)`,
|
|
197
|
-
metric: "negative_slippage_rate",
|
|
198
|
-
value: negativeRate,
|
|
199
|
-
sampleSize: tradeSlippages.length,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
176
|
+
if (positiveRate >= patternThreshold) {
|
|
177
|
+
patterns.push({
|
|
178
|
+
pattern: `Direction bias: ${formatPercent(positiveRate * 100)} of trades have positive slippage (actual > backtest)`,
|
|
179
|
+
metric: "positive_slippage_rate",
|
|
180
|
+
value: positiveRate,
|
|
181
|
+
sampleSize: tradeSlippages.length,
|
|
182
|
+
});
|
|
183
|
+
} else if (negativeRate >= patternThreshold) {
|
|
184
|
+
patterns.push({
|
|
185
|
+
pattern: `Direction bias: ${formatPercent(negativeRate * 100)} of trades have negative slippage (actual < backtest)`,
|
|
186
|
+
metric: "negative_slippage_rate",
|
|
187
|
+
value: negativeRate,
|
|
188
|
+
sampleSize: tradeSlippages.length,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
202
191
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
192
|
+
// 2. Time-of-day clustering - if >patternThreshold of outlier trades occur in same time bucket
|
|
193
|
+
const tradesWithHour = trades.filter((t) => t.hourOfDay !== null);
|
|
194
|
+
if (tradesWithHour.length >= minSamples) {
|
|
195
|
+
// Define time buckets: morning (9-11), midday (11-14), afternoon (14-16)
|
|
196
|
+
const buckets = {
|
|
197
|
+
morning: tradesWithHour.filter(
|
|
198
|
+
(a) => a.hourOfDay !== null && a.hourOfDay >= 9 && a.hourOfDay < 11,
|
|
199
|
+
),
|
|
200
|
+
midday: tradesWithHour.filter(
|
|
201
|
+
(a) => a.hourOfDay !== null && a.hourOfDay >= 11 && a.hourOfDay < 14,
|
|
202
|
+
),
|
|
203
|
+
afternoon: tradesWithHour.filter(
|
|
204
|
+
(a) => a.hourOfDay !== null && a.hourOfDay >= 14 && a.hourOfDay <= 16,
|
|
205
|
+
),
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Find outliers (beyond 1.5 * IQR)
|
|
209
|
+
const sorted = [...slippages].sort((a, b) => a - b);
|
|
210
|
+
const q1 = sorted[Math.floor(sorted.length * 0.25)];
|
|
211
|
+
const q3 = sorted[Math.floor(sorted.length * 0.75)];
|
|
212
|
+
const iqr = q3 - q1;
|
|
213
|
+
const outlierThresholdLow = q1 - 1.5 * iqr;
|
|
214
|
+
const outlierThresholdHigh = q3 + 1.5 * iqr;
|
|
215
|
+
|
|
216
|
+
const outlierTrades = tradesWithHour.filter(
|
|
213
217
|
(a) =>
|
|
214
|
-
a.
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
a.totalSlippage < outlierThresholdLow || a.totalSlippage > outlierThresholdHigh,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
if (outlierTrades.length >= 3) {
|
|
222
|
+
for (const [bucketName, bucketTrades] of Object.entries(buckets)) {
|
|
223
|
+
const outlierInBucket = outlierTrades.filter((o) => bucketTrades.includes(o));
|
|
224
|
+
const bucketRate = outlierInBucket.length / outlierTrades.length;
|
|
225
|
+
|
|
226
|
+
if (bucketRate >= patternThreshold && outlierInBucket.length >= 3) {
|
|
227
|
+
patterns.push({
|
|
228
|
+
pattern: `Time clustering: ${formatPercent(bucketRate * 100)} of outlier trades occur during ${bucketName} hours`,
|
|
229
|
+
metric: "time_clustering_rate",
|
|
230
|
+
value: bucketRate,
|
|
231
|
+
sampleSize: outlierTrades.length,
|
|
232
|
+
});
|
|
233
|
+
break; // Only report strongest time pattern
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
221
238
|
|
|
222
|
-
//
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
const q3 = sorted[Math.floor(sorted.length * 0.75)];
|
|
226
|
-
const iqr = q3 - q1;
|
|
227
|
-
const outlierThresholdLow = q1 - 1.5 * iqr;
|
|
228
|
-
const outlierThresholdHigh = q3 + 1.5 * iqr;
|
|
229
|
-
|
|
230
|
-
const outlierTrades = tradesWithHour.filter(
|
|
231
|
-
(a) =>
|
|
232
|
-
a.totalSlippage < outlierThresholdLow ||
|
|
233
|
-
a.totalSlippage > outlierThresholdHigh
|
|
239
|
+
// 3. VIX sensitivity - correlation with openingVix if available
|
|
240
|
+
const vixTrades = trades.filter(
|
|
241
|
+
(t) => t.openingVix !== undefined && t.openingVix !== null,
|
|
234
242
|
);
|
|
243
|
+
if (vixTrades.length >= minSamples) {
|
|
244
|
+
const vixValues = vixTrades.map((t) => t.openingVix!);
|
|
245
|
+
const vixSlippages = vixTrades.map((t) => t.totalSlippage);
|
|
235
246
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
metric: "time_clustering_rate",
|
|
252
|
-
value: bucketRate,
|
|
253
|
-
sampleSize: outlierTrades.length,
|
|
254
|
-
});
|
|
255
|
-
break; // Only report strongest time pattern
|
|
256
|
-
}
|
|
247
|
+
const correlation =
|
|
248
|
+
correlationMethod === "pearson"
|
|
249
|
+
? pearsonCorrelation(vixSlippages, vixValues)
|
|
250
|
+
: kendallTau(vixSlippages, vixValues);
|
|
251
|
+
|
|
252
|
+
const absCorr = Math.abs(correlation);
|
|
253
|
+
if (absCorr >= 0.3) {
|
|
254
|
+
// Only report if moderate or stronger
|
|
255
|
+
const direction = correlation > 0 ? "positive" : "negative";
|
|
256
|
+
patterns.push({
|
|
257
|
+
pattern: `VIX sensitivity: ${direction} correlation (${correlation.toFixed(3)}) between slippage and opening VIX`,
|
|
258
|
+
metric: "vix_correlation",
|
|
259
|
+
value: correlation,
|
|
260
|
+
sampleSize: vixTrades.length,
|
|
261
|
+
});
|
|
257
262
|
}
|
|
258
263
|
}
|
|
259
|
-
}
|
|
260
264
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
(t) => t.openingVix !== undefined && t.openingVix !== null
|
|
264
|
-
);
|
|
265
|
-
if (vixTrades.length >= minSamples) {
|
|
266
|
-
const vixValues = vixTrades.map((t) => t.openingVix!);
|
|
267
|
-
const vixSlippages = vixTrades.map((t) => t.totalSlippage);
|
|
268
|
-
|
|
269
|
-
const correlation =
|
|
270
|
-
correlationMethod === "pearson"
|
|
271
|
-
? pearsonCorrelation(vixSlippages, vixValues)
|
|
272
|
-
: kendallTau(vixSlippages, vixValues);
|
|
273
|
-
|
|
274
|
-
const absCorr = Math.abs(correlation);
|
|
275
|
-
if (absCorr >= 0.3) {
|
|
276
|
-
// Only report if moderate or stronger
|
|
277
|
-
const direction = correlation > 0 ? "positive" : "negative";
|
|
278
|
-
patterns.push({
|
|
279
|
-
pattern: `VIX sensitivity: ${direction} correlation (${correlation.toFixed(3)}) between slippage and opening VIX`,
|
|
280
|
-
metric: "vix_correlation",
|
|
281
|
-
value: correlation,
|
|
282
|
-
sampleSize: vixTrades.length,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
}
|
|
265
|
+
return patterns;
|
|
266
|
+
};
|
|
286
267
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const calculateCorrelations = (
|
|
292
|
-
trades: MatchedTradeData[]
|
|
293
|
-
): Array<{
|
|
294
|
-
field: string;
|
|
295
|
-
coefficient: number;
|
|
296
|
-
sampleSize: number;
|
|
297
|
-
}> => {
|
|
298
|
-
const results: Array<{
|
|
268
|
+
// Calculate correlations (only returns significant correlations with |r| >= 0.3)
|
|
269
|
+
const calculateCorrelations = (
|
|
270
|
+
trades: MatchedTradeData[],
|
|
271
|
+
): Array<{
|
|
299
272
|
field: string;
|
|
300
273
|
coefficient: number;
|
|
301
274
|
sampleSize: number;
|
|
302
|
-
}>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
275
|
+
}> => {
|
|
276
|
+
const results: Array<{
|
|
277
|
+
field: string;
|
|
278
|
+
coefficient: number;
|
|
279
|
+
sampleSize: number;
|
|
280
|
+
}> = [];
|
|
281
|
+
|
|
282
|
+
const correlationFields: Array<{
|
|
283
|
+
name: string;
|
|
284
|
+
getValue: (t: MatchedTradeData) => number | undefined | null;
|
|
285
|
+
}> = [
|
|
286
|
+
{ name: "openingVix", getValue: (t) => t.openingVix },
|
|
287
|
+
{ name: "closingVix", getValue: (t) => t.closingVix },
|
|
288
|
+
{ name: "gap", getValue: (t) => t.gap },
|
|
289
|
+
{ name: "movement", getValue: (t) => t.movement },
|
|
290
|
+
{ name: "hourOfDay", getValue: (t) => t.hourOfDay },
|
|
291
|
+
{ name: "contracts", getValue: (t) => t.contracts },
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
for (const { name, getValue } of correlationFields) {
|
|
295
|
+
const validPairs: Array<{ slippage: number; field: number }> = [];
|
|
296
|
+
|
|
297
|
+
for (const trade of trades) {
|
|
298
|
+
const fieldValue = getValue(trade);
|
|
299
|
+
if (fieldValue !== undefined && fieldValue !== null && isFinite(fieldValue)) {
|
|
300
|
+
validPairs.push({
|
|
301
|
+
slippage: trade.totalSlippage,
|
|
302
|
+
field: fieldValue,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
315
306
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
307
|
+
if (validPairs.length >= minSamples) {
|
|
308
|
+
const slippagesArr = validPairs.map((p) => p.slippage);
|
|
309
|
+
const fieldValues = validPairs.map((p) => p.field);
|
|
310
|
+
|
|
311
|
+
const coefficient =
|
|
312
|
+
correlationMethod === "pearson"
|
|
313
|
+
? pearsonCorrelation(slippagesArr, fieldValues)
|
|
314
|
+
: kendallTau(slippagesArr, fieldValues);
|
|
315
|
+
|
|
316
|
+
// Only include significant correlations (|r| >= 0.3)
|
|
317
|
+
if (Math.abs(coefficient) >= 0.3) {
|
|
318
|
+
results.push({
|
|
319
|
+
field: name,
|
|
320
|
+
coefficient: Math.round(coefficient * 10000) / 10000,
|
|
321
|
+
sampleSize: validPairs.length,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
330
324
|
}
|
|
331
325
|
}
|
|
332
326
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const fieldValues = validPairs.map((p) => p.field);
|
|
327
|
+
// Sort by absolute coefficient descending
|
|
328
|
+
results.sort((a, b) => Math.abs(b.coefficient) - Math.abs(a.coefficient));
|
|
336
329
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
? pearsonCorrelation(slippagesArr, fieldValues)
|
|
340
|
-
: kendallTau(slippagesArr, fieldValues);
|
|
341
|
-
|
|
342
|
-
// Only include significant correlations (|r| >= 0.3)
|
|
343
|
-
if (Math.abs(coefficient) >= 0.3) {
|
|
344
|
-
results.push({
|
|
345
|
-
field: name,
|
|
346
|
-
coefficient: Math.round(coefficient * 10000) / 10000,
|
|
347
|
-
sampleSize: validPairs.length,
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
330
|
+
return results;
|
|
331
|
+
};
|
|
352
332
|
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
);
|
|
333
|
+
// Portfolio-wide patterns and correlations
|
|
334
|
+
const portfolioPatterns = detectPatterns(matchedTrades);
|
|
335
|
+
const portfolioCorrelations = calculateCorrelations(matchedTrades);
|
|
357
336
|
|
|
358
|
-
|
|
359
|
-
|
|
337
|
+
// Per-strategy breakdown (always included, simplified output)
|
|
338
|
+
const byStrategy = new Map<string, MatchedTradeData[]>();
|
|
339
|
+
for (const trade of matchedTrades) {
|
|
340
|
+
const existing = byStrategy.get(trade.strategy) ?? [];
|
|
341
|
+
existing.push(trade);
|
|
342
|
+
byStrategy.set(trade.strategy, existing);
|
|
343
|
+
}
|
|
360
344
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
345
|
+
const perStrategy: Array<{
|
|
346
|
+
strategy: string;
|
|
347
|
+
tradeCount: number;
|
|
348
|
+
totalSlippage: number;
|
|
349
|
+
avgSlippage: number;
|
|
350
|
+
}> = [];
|
|
364
351
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
existing.push(trade);
|
|
370
|
-
byStrategy.set(trade.strategy, existing);
|
|
371
|
-
}
|
|
352
|
+
for (const [strategyName, trades] of byStrategy) {
|
|
353
|
+
const stratSlippages = trades.map((t) => t.totalSlippage);
|
|
354
|
+
const stratTotal = stratSlippages.reduce((sum, s) => sum + s, 0);
|
|
355
|
+
const stratAvg = trades.length > 0 ? stratTotal / trades.length : 0;
|
|
372
356
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
for (const [strategyName, trades] of byStrategy) {
|
|
381
|
-
const stratSlippages = trades.map((t) => t.totalSlippage);
|
|
382
|
-
const stratTotal = stratSlippages.reduce((sum, s) => sum + s, 0);
|
|
383
|
-
const stratAvg = trades.length > 0 ? stratTotal / trades.length : 0;
|
|
384
|
-
|
|
385
|
-
perStrategy.push({
|
|
386
|
-
strategy: strategyName,
|
|
387
|
-
tradeCount: trades.length,
|
|
388
|
-
totalSlippage: stratTotal,
|
|
389
|
-
avgSlippage: stratAvg,
|
|
390
|
-
});
|
|
391
|
-
}
|
|
357
|
+
perStrategy.push({
|
|
358
|
+
strategy: strategyName,
|
|
359
|
+
tradeCount: trades.length,
|
|
360
|
+
totalSlippage: stratTotal,
|
|
361
|
+
avgSlippage: stratAvg,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
392
364
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
(a, b) => Math.abs(b.totalSlippage) - Math.abs(a.totalSlippage)
|
|
396
|
-
);
|
|
365
|
+
// Sort by absolute total slippage descending
|
|
366
|
+
perStrategy.sort((a, b) => Math.abs(b.totalSlippage) - Math.abs(a.totalSlippage));
|
|
397
367
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
368
|
+
// Build summary string
|
|
369
|
+
const summaryParts = [
|
|
370
|
+
`Slippage analysis: ${matchedTrades.length} matched trades`,
|
|
371
|
+
`Total slippage: ${formatCurrency(totalSlippage)}`,
|
|
372
|
+
`Avg per trade: ${formatCurrency(avgSlippagePerTrade)}`,
|
|
373
|
+
];
|
|
404
374
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
375
|
+
if (portfolioPatterns.length > 0) {
|
|
376
|
+
summaryParts.push(`${portfolioPatterns.length} patterns detected`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const summary = summaryParts.join(" | ");
|
|
380
|
+
|
|
381
|
+
const structuredData = {
|
|
382
|
+
summary: {
|
|
383
|
+
matchedTrades: matchedTrades.length,
|
|
384
|
+
unmatchedBacktest: unmatchedBacktestCount,
|
|
385
|
+
unmatchedActual: unmatchedActualCount,
|
|
386
|
+
totalSlippage,
|
|
387
|
+
avgSlippagePerTrade,
|
|
388
|
+
dateRange: dateRangeResult,
|
|
389
|
+
},
|
|
390
|
+
patterns: portfolioPatterns,
|
|
391
|
+
correlations: {
|
|
392
|
+
method: correlationMethod,
|
|
393
|
+
results: portfolioCorrelations,
|
|
394
|
+
note:
|
|
395
|
+
portfolioCorrelations.length === 0
|
|
396
|
+
? "No significant correlations found (|r| >= 0.3)"
|
|
397
|
+
: undefined,
|
|
398
|
+
},
|
|
399
|
+
perStrategy,
|
|
400
|
+
};
|
|
408
401
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const structuredData = {
|
|
412
|
-
summary: {
|
|
413
|
-
matchedTrades: matchedTrades.length,
|
|
414
|
-
unmatchedBacktest: unmatchedBacktestCount,
|
|
415
|
-
unmatchedActual: unmatchedActualCount,
|
|
416
|
-
totalSlippage,
|
|
417
|
-
avgSlippagePerTrade,
|
|
418
|
-
dateRange: dateRangeResult,
|
|
419
|
-
},
|
|
420
|
-
patterns: portfolioPatterns,
|
|
421
|
-
correlations: {
|
|
422
|
-
method: correlationMethod,
|
|
423
|
-
results: portfolioCorrelations,
|
|
424
|
-
note:
|
|
425
|
-
portfolioCorrelations.length === 0
|
|
426
|
-
? "No significant correlations found (|r| >= 0.3)"
|
|
427
|
-
: undefined,
|
|
428
|
-
},
|
|
429
|
-
perStrategy,
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
return createToolOutput(summary, structuredData);
|
|
402
|
+
return createToolOutput(summary, structuredData);
|
|
433
403
|
} catch (error) {
|
|
434
404
|
return {
|
|
435
405
|
content: [
|
|
@@ -441,7 +411,7 @@ export function registerDiscrepancyTool(
|
|
|
441
411
|
isError: true,
|
|
442
412
|
};
|
|
443
413
|
}
|
|
444
|
-
}
|
|
445
|
-
)
|
|
414
|
+
},
|
|
415
|
+
),
|
|
446
416
|
);
|
|
447
417
|
}
|