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
|
@@ -34,21 +34,10 @@ export function registerFieldTools(server: McpServer, baseDir: string): void {
|
|
|
34
34
|
blockId: z.string().describe("Block folder name"),
|
|
35
35
|
field: z
|
|
36
36
|
.string()
|
|
37
|
-
.describe(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
.string()
|
|
42
|
-
.optional()
|
|
43
|
-
.describe("Filter by strategy name (case-insensitive)"),
|
|
44
|
-
startDate: z
|
|
45
|
-
.string()
|
|
46
|
-
.optional()
|
|
47
|
-
.describe("Filter by start date (YYYY-MM-DD)"),
|
|
48
|
-
endDate: z
|
|
49
|
-
.string()
|
|
50
|
-
.optional()
|
|
51
|
-
.describe("Filter by end date (YYYY-MM-DD)"),
|
|
37
|
+
.describe("Field name to analyze (e.g., 'openingVix', 'pl', 'rom', 'mfePercent')"),
|
|
38
|
+
strategy: z.string().optional().describe("Filter by strategy name (case-insensitive)"),
|
|
39
|
+
startDate: z.string().optional().describe("Filter by start date (YYYY-MM-DD)"),
|
|
40
|
+
endDate: z.string().optional().describe("Filter by end date (YYYY-MM-DD)"),
|
|
52
41
|
histogramBuckets: z
|
|
53
42
|
.number()
|
|
54
43
|
.min(3)
|
|
@@ -59,14 +48,7 @@ export function registerFieldTools(server: McpServer, baseDir: string): void {
|
|
|
59
48
|
},
|
|
60
49
|
withSyncedBlock(
|
|
61
50
|
baseDir,
|
|
62
|
-
async ({
|
|
63
|
-
blockId,
|
|
64
|
-
field,
|
|
65
|
-
strategy,
|
|
66
|
-
startDate,
|
|
67
|
-
endDate,
|
|
68
|
-
histogramBuckets,
|
|
69
|
-
}) => {
|
|
51
|
+
async ({ blockId, field, strategy, startDate, endDate, histogramBuckets }) => {
|
|
70
52
|
try {
|
|
71
53
|
const block = await loadBlock(baseDir, blockId);
|
|
72
54
|
let trades = block.trades;
|
|
@@ -168,7 +150,7 @@ export function registerFieldTools(server: McpServer, baseDir: string): void {
|
|
|
168
150
|
isError: true,
|
|
169
151
|
};
|
|
170
152
|
}
|
|
171
|
-
}
|
|
172
|
-
)
|
|
153
|
+
},
|
|
154
|
+
),
|
|
173
155
|
);
|
|
174
156
|
}
|
|
@@ -52,15 +52,11 @@ function computeDurationHours(trade: Trade): number | undefined {
|
|
|
52
52
|
if (!trade.dateClosed || !trade.timeClosed) return undefined;
|
|
53
53
|
try {
|
|
54
54
|
const openingDate = new Date(trade.dateOpened);
|
|
55
|
-
const [openHours, openMinutes, openSeconds] = trade.timeOpened
|
|
56
|
-
.split(":")
|
|
57
|
-
.map(Number);
|
|
55
|
+
const [openHours, openMinutes, openSeconds] = trade.timeOpened.split(":").map(Number);
|
|
58
56
|
openingDate.setHours(openHours, openMinutes, openSeconds || 0, 0);
|
|
59
57
|
|
|
60
58
|
const closingDate = new Date(trade.dateClosed);
|
|
61
|
-
const [closeHours, closeMinutes, closeSeconds] = trade.timeClosed
|
|
62
|
-
.split(":")
|
|
63
|
-
.map(Number);
|
|
59
|
+
const [closeHours, closeMinutes, closeSeconds] = trade.timeClosed.split(":").map(Number);
|
|
64
60
|
closingDate.setHours(closeHours, closeMinutes, closeSeconds || 0, 0);
|
|
65
61
|
|
|
66
62
|
const diffMs = closingDate.getTime() - openingDate.getTime();
|
|
@@ -105,9 +101,7 @@ function getISOWeekNumber(date: Date): number {
|
|
|
105
101
|
const dayNum = d.getDay() || 7;
|
|
106
102
|
d.setDate(d.getDate() + 4 - dayNum);
|
|
107
103
|
const yearStart = new Date(d.getFullYear(), 0, 1);
|
|
108
|
-
const weekNo = Math.ceil(
|
|
109
|
-
((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7
|
|
110
|
-
);
|
|
104
|
+
const weekNo = Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
|
|
111
105
|
return weekNo;
|
|
112
106
|
}
|
|
113
107
|
|
|
@@ -119,31 +113,22 @@ function getISOWeekNumber(date: Date): number {
|
|
|
119
113
|
export function enrichTrades(trades: Trade[]): EnrichedTrade[] {
|
|
120
114
|
return trades.map((trade, index) => {
|
|
121
115
|
const dateOpened = new Date(trade.dateOpened);
|
|
122
|
-
const totalFees =
|
|
123
|
-
trade.openingCommissionsFees + (trade.closingCommissionsFees ?? 0);
|
|
116
|
+
const totalFees = trade.openingCommissionsFees + (trade.closingCommissionsFees ?? 0);
|
|
124
117
|
const netPl = trade.pl - totalFees;
|
|
125
118
|
|
|
126
119
|
// VIX changes
|
|
127
|
-
const hasVixData =
|
|
128
|
-
|
|
129
|
-
const vixChange = hasVixData
|
|
130
|
-
? trade.closingVix! - trade.openingVix!
|
|
131
|
-
: undefined;
|
|
120
|
+
const hasVixData = trade.openingVix != null && trade.closingVix != null;
|
|
121
|
+
const vixChange = hasVixData ? trade.closingVix! - trade.openingVix! : undefined;
|
|
132
122
|
const vixChangePct =
|
|
133
123
|
hasVixData && trade.openingVix !== 0
|
|
134
124
|
? ((trade.closingVix! - trade.openingVix!) / trade.openingVix!) * 100
|
|
135
125
|
: undefined;
|
|
136
126
|
|
|
137
127
|
// Return metrics
|
|
138
|
-
const rom =
|
|
139
|
-
trade.marginReq > 0 ? (trade.pl / trade.marginReq) * 100 : undefined;
|
|
128
|
+
const rom = trade.marginReq > 0 ? (trade.pl / trade.marginReq) * 100 : undefined;
|
|
140
129
|
const totalPremium = trade.premium * trade.numContracts;
|
|
141
|
-
const plPct =
|
|
142
|
-
|
|
143
|
-
? (trade.pl / Math.abs(totalPremium)) * 100
|
|
144
|
-
: undefined;
|
|
145
|
-
const netPlPct =
|
|
146
|
-
totalPremium !== 0 ? (netPl / Math.abs(totalPremium)) * 100 : undefined;
|
|
130
|
+
const plPct = totalPremium !== 0 ? (trade.pl / Math.abs(totalPremium)) * 100 : undefined;
|
|
131
|
+
const netPlPct = totalPremium !== 0 ? (netPl / Math.abs(totalPremium)) * 100 : undefined;
|
|
147
132
|
|
|
148
133
|
// MFE/MAE approximation from maxProfit/maxLoss (if available in trade data)
|
|
149
134
|
let mfePercent: number | undefined;
|
|
@@ -158,18 +143,10 @@ export function enrichTrades(trades: Trade[]): EnrichedTrade[] {
|
|
|
158
143
|
if (trade.maxLoss !== undefined && totalPremium !== 0) {
|
|
159
144
|
maePercent = (Math.abs(trade.maxLoss) / Math.abs(totalPremium)) * 100;
|
|
160
145
|
}
|
|
161
|
-
if (
|
|
162
|
-
mfePercent !== undefined &&
|
|
163
|
-
trade.maxProfit &&
|
|
164
|
-
trade.maxProfit > 0
|
|
165
|
-
) {
|
|
146
|
+
if (mfePercent !== undefined && trade.maxProfit && trade.maxProfit > 0) {
|
|
166
147
|
profitCapturePercent = (trade.pl / trade.maxProfit) * 100;
|
|
167
148
|
}
|
|
168
|
-
if (
|
|
169
|
-
mfePercent !== undefined &&
|
|
170
|
-
maePercent !== undefined &&
|
|
171
|
-
maePercent > 0
|
|
172
|
-
) {
|
|
149
|
+
if (mfePercent !== undefined && maePercent !== undefined && maePercent > 0) {
|
|
173
150
|
excursionRatio = mfePercent / maePercent;
|
|
174
151
|
}
|
|
175
152
|
if (trade.maxLoss !== undefined && Math.abs(trade.maxLoss) > 0) {
|
|
@@ -214,10 +191,7 @@ export function enrichTrades(trades: Trade[]): EnrichedTrade[] {
|
|
|
214
191
|
* Get the value of a field from an enriched trade
|
|
215
192
|
* Returns null if the field doesn't exist or has no value
|
|
216
193
|
*/
|
|
217
|
-
export function getTradeFieldValue(
|
|
218
|
-
trade: EnrichedTrade,
|
|
219
|
-
field: string
|
|
220
|
-
): number | null {
|
|
194
|
+
export function getTradeFieldValue(trade: EnrichedTrade, field: string): number | null {
|
|
221
195
|
// Guard against undefined or non-string field
|
|
222
196
|
if (typeof field !== "string") {
|
|
223
197
|
return null;
|
|
@@ -260,7 +234,7 @@ export function evaluateOperator(
|
|
|
260
234
|
value: number,
|
|
261
235
|
operator: FilterOperator,
|
|
262
236
|
compareValue: number,
|
|
263
|
-
compareValue2?: number
|
|
237
|
+
compareValue2?: number,
|
|
264
238
|
): boolean {
|
|
265
239
|
switch (operator) {
|
|
266
240
|
case "eq":
|
|
@@ -299,7 +273,7 @@ export interface FilterCondition {
|
|
|
299
273
|
export function applyFilterConditions(
|
|
300
274
|
trades: EnrichedTrade[],
|
|
301
275
|
conditions: FilterCondition[],
|
|
302
|
-
logic: "and" | "or"
|
|
276
|
+
logic: "and" | "or",
|
|
303
277
|
): EnrichedTrade[] {
|
|
304
278
|
if (conditions.length === 0) {
|
|
305
279
|
return trades;
|
|
@@ -340,8 +314,7 @@ export function percentile(sorted: number[], p: number): number {
|
|
|
340
314
|
export function stdDev(values: number[], avg: number): number {
|
|
341
315
|
if (values.length < 2) return 0;
|
|
342
316
|
const squaredDiffs = values.map((v) => Math.pow(v - avg, 2));
|
|
343
|
-
const variance =
|
|
344
|
-
squaredDiffs.reduce((a, b) => a + b, 0) / (values.length - 1);
|
|
317
|
+
const variance = squaredDiffs.reduce((a, b) => a + b, 0) / (values.length - 1);
|
|
345
318
|
return Math.sqrt(variance);
|
|
346
319
|
}
|
|
347
320
|
|
|
@@ -350,7 +323,7 @@ export function stdDev(values: number[], avg: number): number {
|
|
|
350
323
|
*/
|
|
351
324
|
export function generateHistogram(
|
|
352
325
|
values: number[],
|
|
353
|
-
bucketCount: number = 10
|
|
326
|
+
bucketCount: number = 10,
|
|
354
327
|
): Array<{ min: number; max: number; count: number }> {
|
|
355
328
|
if (values.length === 0) return [];
|
|
356
329
|
|
|
@@ -362,16 +335,12 @@ export function generateHistogram(
|
|
|
362
335
|
const buckets: Array<{ min: number; max: number; count: number }> = [];
|
|
363
336
|
for (let i = 0; i < bucketCount; i++) {
|
|
364
337
|
const bucketMin = min + i * bucketSize;
|
|
365
|
-
const bucketMax =
|
|
366
|
-
i === bucketCount - 1 ? max + 0.001 : min + (i + 1) * bucketSize;
|
|
338
|
+
const bucketMax = i === bucketCount - 1 ? max + 0.001 : min + (i + 1) * bucketSize;
|
|
367
339
|
buckets.push({ min: bucketMin, max: bucketMax, count: 0 });
|
|
368
340
|
}
|
|
369
341
|
|
|
370
342
|
for (const value of values) {
|
|
371
|
-
const bucketIndex = Math.min(
|
|
372
|
-
Math.floor((value - min) / bucketSize),
|
|
373
|
-
bucketCount - 1
|
|
374
|
-
);
|
|
343
|
+
const bucketIndex = Math.min(Math.floor((value - min) / bucketSize), bucketCount - 1);
|
|
375
344
|
if (bucketIndex >= 0 && bucketIndex < buckets.length) {
|
|
376
345
|
buckets[bucketIndex].count++;
|
|
377
346
|
}
|
|
@@ -10,12 +10,7 @@ import { loadBlock } from "../../utils/block-loader.ts";
|
|
|
10
10
|
import { createToolOutput } from "../../utils/output-formatter.ts";
|
|
11
11
|
import { REPORT_FIELDS, pearsonCorrelation } from "@tradeblocks/lib";
|
|
12
12
|
import { filterByStrategy, filterByDateRange } from "../shared/filters.ts";
|
|
13
|
-
import {
|
|
14
|
-
enrichTrades,
|
|
15
|
-
getTradeFieldValue,
|
|
16
|
-
percentile,
|
|
17
|
-
type EnrichedTrade,
|
|
18
|
-
} from "./helpers.ts";
|
|
13
|
+
import { enrichTrades, getTradeFieldValue, percentile, type EnrichedTrade } from "./helpers.ts";
|
|
19
14
|
import { withSyncedBlock } from "../middleware/sync-middleware.ts";
|
|
20
15
|
import { getConnection } from "../../db/connection.ts";
|
|
21
16
|
import { getProfile } from "../../db/profile-schemas.ts";
|
|
@@ -23,10 +18,7 @@ import { getProfile } from "../../db/profile-schemas.ts";
|
|
|
23
18
|
/**
|
|
24
19
|
* Register predictive-related report tools
|
|
25
20
|
*/
|
|
26
|
-
export function registerPredictiveTools(
|
|
27
|
-
server: McpServer,
|
|
28
|
-
baseDir: string
|
|
29
|
-
): void {
|
|
21
|
+
export function registerPredictiveTools(server: McpServer, baseDir: string): void {
|
|
30
22
|
// Tool 5: find_predictive_fields
|
|
31
23
|
server.registerTool(
|
|
32
24
|
"find_predictive_fields",
|
|
@@ -35,22 +27,15 @@ export function registerPredictiveTools(
|
|
|
35
27
|
"Identify which trade entry conditions predict profitability by calculating Pearson correlations between all numeric fields and a target field (usually P/L). Returns fields ranked by predictive strength.",
|
|
36
28
|
inputSchema: z.object({
|
|
37
29
|
blockId: z.string().describe("Block folder name"),
|
|
38
|
-
strategy: z
|
|
39
|
-
.string()
|
|
40
|
-
.optional()
|
|
41
|
-
.describe("Pre-filter by strategy name (case-insensitive)"),
|
|
30
|
+
strategy: z.string().optional().describe("Pre-filter by strategy name (case-insensitive)"),
|
|
42
31
|
strategyName: z
|
|
43
32
|
.string()
|
|
44
33
|
.optional()
|
|
45
|
-
.describe(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
endDate: z
|
|
51
|
-
.string()
|
|
52
|
-
.optional()
|
|
53
|
-
.describe("Pre-filter by end date (YYYY-MM-DD)"),
|
|
34
|
+
.describe(
|
|
35
|
+
"Strategy profile name. When provided, auto-filters to that strategy's trades and adds profile context to output.",
|
|
36
|
+
),
|
|
37
|
+
startDate: z.string().optional().describe("Pre-filter by start date (YYYY-MM-DD)"),
|
|
38
|
+
endDate: z.string().optional().describe("Pre-filter by end date (YYYY-MM-DD)"),
|
|
54
39
|
targetField: z
|
|
55
40
|
.string()
|
|
56
41
|
.default("pl")
|
|
@@ -60,7 +45,7 @@ export function registerPredictiveTools(
|
|
|
60
45
|
.min(10)
|
|
61
46
|
.default(30)
|
|
62
47
|
.describe(
|
|
63
|
-
"Minimum trades with valid values for reliable correlation (default: 30, min: 10)"
|
|
48
|
+
"Minimum trades with valid values for reliable correlation (default: 30, min: 10)",
|
|
64
49
|
),
|
|
65
50
|
includeCustomFields: z
|
|
66
51
|
.boolean()
|
|
@@ -256,9 +241,7 @@ export function registerPredictiveTools(
|
|
|
256
241
|
const conn = await getConnection(baseDir);
|
|
257
242
|
const profile = await getProfile(conn, blockId, strategyName, baseDir);
|
|
258
243
|
if (profile && profile.entryFilters.length > 0) {
|
|
259
|
-
const profileFilterFields = new Set(
|
|
260
|
-
profile.entryFilters.map((f) => f.field)
|
|
261
|
-
);
|
|
244
|
+
const profileFilterFields = new Set(profile.entryFilters.map((f) => f.field));
|
|
262
245
|
const alignedFields = rankedFields
|
|
263
246
|
.filter((rf) => profileFilterFields.has(rf.field))
|
|
264
247
|
.map((rf) => ({
|
|
@@ -292,8 +275,8 @@ export function registerPredictiveTools(
|
|
|
292
275
|
isError: true,
|
|
293
276
|
};
|
|
294
277
|
}
|
|
295
|
-
}
|
|
296
|
-
)
|
|
278
|
+
},
|
|
279
|
+
),
|
|
297
280
|
);
|
|
298
281
|
|
|
299
282
|
// Tool 6: filter_curve
|
|
@@ -306,39 +289,28 @@ export function registerPredictiveTools(
|
|
|
306
289
|
blockId: z.string().describe("Block folder name"),
|
|
307
290
|
field: z
|
|
308
291
|
.string()
|
|
309
|
-
.describe(
|
|
310
|
-
"Field to sweep thresholds on (e.g., 'openingVix', 'durationHours')"
|
|
311
|
-
),
|
|
292
|
+
.describe("Field to sweep thresholds on (e.g., 'openingVix', 'durationHours')"),
|
|
312
293
|
mode: z
|
|
313
294
|
.enum(["lt", "gt", "both"])
|
|
314
295
|
.default("both")
|
|
315
296
|
.describe(
|
|
316
|
-
"Direction of filter: 'lt' (field < threshold), 'gt' (field > threshold), 'both' (show both directions)"
|
|
297
|
+
"Direction of filter: 'lt' (field < threshold), 'gt' (field > threshold), 'both' (show both directions)",
|
|
317
298
|
),
|
|
318
299
|
thresholds: z
|
|
319
300
|
.array(z.number())
|
|
320
301
|
.optional()
|
|
321
302
|
.describe(
|
|
322
|
-
"Custom threshold values to test. If omitted, auto-generates from field percentiles."
|
|
303
|
+
"Custom threshold values to test. If omitted, auto-generates from field percentiles.",
|
|
323
304
|
),
|
|
324
305
|
percentileSteps: z
|
|
325
306
|
.array(z.number())
|
|
326
307
|
.default([5, 10, 25, 50, 75, 90, 95])
|
|
327
308
|
.describe(
|
|
328
|
-
"Percentiles to use for auto-generated thresholds (default: [5, 10, 25, 50, 75, 90, 95])"
|
|
309
|
+
"Percentiles to use for auto-generated thresholds (default: [5, 10, 25, 50, 75, 90, 95])",
|
|
329
310
|
),
|
|
330
|
-
strategy: z
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
.describe("Pre-filter by strategy name (case-insensitive)"),
|
|
334
|
-
startDate: z
|
|
335
|
-
.string()
|
|
336
|
-
.optional()
|
|
337
|
-
.describe("Pre-filter by start date (YYYY-MM-DD)"),
|
|
338
|
-
endDate: z
|
|
339
|
-
.string()
|
|
340
|
-
.optional()
|
|
341
|
-
.describe("Pre-filter by end date (YYYY-MM-DD)"),
|
|
311
|
+
strategy: z.string().optional().describe("Pre-filter by strategy name (case-insensitive)"),
|
|
312
|
+
startDate: z.string().optional().describe("Pre-filter by start date (YYYY-MM-DD)"),
|
|
313
|
+
endDate: z.string().optional().describe("Pre-filter by end date (YYYY-MM-DD)"),
|
|
342
314
|
}),
|
|
343
315
|
},
|
|
344
316
|
withSyncedBlock(
|
|
@@ -402,9 +374,7 @@ export function registerPredictiveTools(
|
|
|
402
374
|
const baselinePls = tradesWithField.map((t) => t.pl);
|
|
403
375
|
const baselineTotalPl = baselinePls.reduce((a, b) => a + b, 0);
|
|
404
376
|
const baselineAvgPl = baselineTotalPl / tradesWithField.length;
|
|
405
|
-
const baselineWinners = tradesWithField.filter(
|
|
406
|
-
(t) => t.pl > 0
|
|
407
|
-
).length;
|
|
377
|
+
const baselineWinners = tradesWithField.filter((t) => t.pl > 0).length;
|
|
408
378
|
const baselineWinRate = baselineWinners / tradesWithField.length;
|
|
409
379
|
|
|
410
380
|
const baseline = {
|
|
@@ -469,8 +439,7 @@ export function registerPredictiveTools(
|
|
|
469
439
|
const avgPl = totalPl / filteredTrades.length;
|
|
470
440
|
const winners = filteredTrades.filter((t) => t.pl > 0).length;
|
|
471
441
|
const winRate = winners / filteredTrades.length;
|
|
472
|
-
const percentOfTrades =
|
|
473
|
-
(filteredTrades.length / tradesWithField.length) * 100;
|
|
442
|
+
const percentOfTrades = (filteredTrades.length / tradesWithField.length) * 100;
|
|
474
443
|
|
|
475
444
|
const result: ReturnType<typeof calculateMetrics> = {
|
|
476
445
|
count: filteredTrades.length,
|
|
@@ -478,16 +447,12 @@ export function registerPredictiveTools(
|
|
|
478
447
|
winRate: Math.round(winRate * 10000) / 10000,
|
|
479
448
|
avgPl: Math.round(avgPl * 100) / 100,
|
|
480
449
|
totalPl: Math.round(totalPl * 100) / 100,
|
|
481
|
-
winRateDelta:
|
|
482
|
-
Math.round((winRate - baseline.winRate) * 10000) / 10000,
|
|
450
|
+
winRateDelta: Math.round((winRate - baseline.winRate) * 10000) / 10000,
|
|
483
451
|
avgPlDelta: Math.round((avgPl - baseline.avgPl) * 100) / 100,
|
|
484
452
|
};
|
|
485
453
|
|
|
486
454
|
// Add warning for small sample sizes
|
|
487
|
-
if (
|
|
488
|
-
filteredTrades.length < MIN_SAMPLE_SIZE &&
|
|
489
|
-
filteredTrades.length > 0
|
|
490
|
-
) {
|
|
455
|
+
if (filteredTrades.length < MIN_SAMPLE_SIZE && filteredTrades.length > 0) {
|
|
491
456
|
result.lowSampleWarning = `${filteredTrades.length} trades may be insufficient for reliable statistics (recommend >= ${MIN_SAMPLE_SIZE})`;
|
|
492
457
|
}
|
|
493
458
|
|
|
@@ -541,11 +506,7 @@ export function registerPredictiveTools(
|
|
|
541
506
|
// Check lt direction
|
|
542
507
|
if (result.lt) {
|
|
543
508
|
const { winRateDelta, avgPlDelta, percentOfTrades } = result.lt;
|
|
544
|
-
if (
|
|
545
|
-
winRateDelta > 0 &&
|
|
546
|
-
avgPlDelta > 0 &&
|
|
547
|
-
percentOfTrades >= 20
|
|
548
|
-
) {
|
|
509
|
+
if (winRateDelta > 0 && avgPlDelta > 0 && percentOfTrades >= 20) {
|
|
549
510
|
const score = winRateDelta * avgPlDelta;
|
|
550
511
|
sweetSpots.push({
|
|
551
512
|
threshold: result.threshold,
|
|
@@ -561,11 +522,7 @@ export function registerPredictiveTools(
|
|
|
561
522
|
// Check gt direction
|
|
562
523
|
if (result.gt) {
|
|
563
524
|
const { winRateDelta, avgPlDelta, percentOfTrades } = result.gt;
|
|
564
|
-
if (
|
|
565
|
-
winRateDelta > 0 &&
|
|
566
|
-
avgPlDelta > 0 &&
|
|
567
|
-
percentOfTrades >= 20
|
|
568
|
-
) {
|
|
525
|
+
if (winRateDelta > 0 && avgPlDelta > 0 && percentOfTrades >= 20) {
|
|
569
526
|
const score = winRateDelta * avgPlDelta;
|
|
570
527
|
sweetSpots.push({
|
|
571
528
|
threshold: result.threshold,
|
|
@@ -618,7 +575,7 @@ export function registerPredictiveTools(
|
|
|
618
575
|
isError: true,
|
|
619
576
|
};
|
|
620
577
|
}
|
|
621
|
-
}
|
|
622
|
-
)
|
|
578
|
+
},
|
|
579
|
+
),
|
|
623
580
|
);
|
|
624
581
|
}
|