tradeblocks-mcp 3.0.2 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -68
- package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
- package/dist/chunk-27S67XW3.js.map +1 -0
- package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
- package/dist/chunk-2E63THNI.js.map +1 -0
- package/dist/{chunk-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
- package/dist/chunk-6S37CXUA.js.map +1 -0
- package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
- package/dist/chunk-AP7IHUUR.js.map +1 -0
- package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
- package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
- package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
- package/dist/chunk-SEUZYQGQ.js.map +1 -0
- package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
- package/dist/chunk-UBUC5A66.js.map +1 -0
- package/dist/{chunk-XXYOUIZY.js → chunk-VDU25Z6X.js} +63 -80
- package/dist/chunk-VDU25Z6X.js.map +1 -0
- package/dist/daily-log-processor-BY3ISY6K.js +9 -0
- package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
- package/dist/iv-solver-worker.js +9 -1
- package/dist/market-provider-H3ARIVZ4.js +16 -0
- package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
- package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
- package/dist/test-exports.js +358 -254
- package/dist/test-exports.js.map +1 -1
- package/dist/trade-processor-EYA3I3XB.js +9 -0
- package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
- package/manifest.json +3 -11
- package/package.json +1 -1
- package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
- package/server/chunk-4P7D7YZP.js.map +1 -0
- package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
- package/server/chunk-BOPHW5M6.js.map +1 -0
- package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
- package/server/chunk-GH2552SE.js.map +1 -0
- package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
- package/server/chunk-OBYKFW2B.js.map +1 -0
- package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
- package/server/chunk-T66KH2XH.js.map +1 -0
- package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
- package/server/chunk-W5E7FHC4.js.map +1 -0
- package/server/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
- package/server/chunk-YUCOAJ4Z.js.map +1 -0
- package/server/{chunk-PNKG7RY7.js → chunk-ZBJCF4ZG.js} +63 -80
- package/server/chunk-ZBJCF4ZG.js.map +1 -0
- package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
- package/server/config-DK7KOMNL.js.map +1 -0
- package/server/daily-log-processor-ENEUT22A.js +10 -0
- package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
- package/server/http-server.js +2 -5
- package/server/index.js +1771 -1538
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +9 -1
- package/server/market-provider-B437HKLW.js +17 -0
- package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
- package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
- package/server/trade-processor-L3PIQ5TG.js +10 -0
- package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
- package/src/auth/clients-store.ts +4 -4
- package/src/auth/code-store.ts +1 -1
- package/src/auth/config.ts +11 -12
- package/src/auth/login-page.ts +8 -10
- package/src/auth/provider.ts +35 -30
- package/src/auth/token.ts +17 -15
- package/src/db/backtest-schemas.ts +12 -4
- package/src/db/connection.ts +107 -40
- package/src/db/index.ts +37 -5
- package/src/db/json-adapters.ts +22 -31
- package/src/db/json-migration.ts +11 -15
- package/src/db/json-store.ts +5 -3
- package/src/db/market-datasets.ts +48 -15
- package/src/db/market-schemas.ts +11 -5
- package/src/db/market-views.ts +119 -32
- package/src/db/parquet-writer.ts +7 -7
- package/src/db/profile-schemas.ts +39 -22
- package/src/db/schemas.ts +2 -2
- package/src/http-server.ts +7 -12
- package/src/index.ts +8 -9
- package/src/market/ingestor/index.ts +1 -4
- package/src/market/ingestor/market-ingestor.ts +140 -67
- package/src/market/ingestor/types.ts +1 -3
- package/src/market/stores/chain-sql.ts +2 -8
- package/src/market/stores/chain-store.ts +1 -4
- package/src/market/stores/duckdb-chain-store.ts +3 -14
- package/src/market/stores/duckdb-enriched-store.ts +3 -13
- package/src/market/stores/duckdb-quote-store.ts +10 -27
- package/src/market/stores/duckdb-spot-store.ts +4 -20
- package/src/market/stores/enriched-sql.ts +3 -9
- package/src/market/stores/enriched-store.ts +2 -2
- package/src/market/stores/parquet-chain-store.ts +5 -22
- package/src/market/stores/parquet-enriched-store.ts +4 -16
- package/src/market/stores/parquet-oi-daily-store.ts +3 -14
- package/src/market/stores/parquet-quote-store.ts +9 -29
- package/src/market/stores/parquet-spot-store.ts +14 -33
- package/src/market/stores/quote-store.ts +2 -10
- package/src/market/stores/spot-sql.ts +3 -15
- package/src/market/stores/spot-store.ts +1 -1
- package/src/market/tickers/defaults.json +6 -6
- package/src/market/tickers/loader.ts +2 -7
- package/src/market/tickers/registry.ts +5 -15
- package/src/market/tickers/schemas.ts +2 -6
- package/src/models/strategy-profile.ts +39 -39
- package/src/sync/block-sync.ts +44 -65
- package/src/sync/index.ts +1 -4
- package/src/sync/metadata.ts +14 -20
- package/src/test-exports.ts +127 -123
- package/src/tools/analysis.ts +180 -159
- package/src/tools/batch-exit-analysis.ts +100 -59
- package/src/tools/blocks/analysis.ts +116 -101
- package/src/tools/blocks/comparison.ts +281 -364
- package/src/tools/blocks/core.ts +208 -253
- package/src/tools/blocks/health.ts +571 -563
- package/src/tools/blocks/similarity.ts +316 -283
- package/src/tools/edge-decay.ts +124 -197
- package/src/tools/exit-analysis.ts +130 -77
- package/src/tools/greeks-attribution.ts +84 -35
- package/src/tools/guides.ts +4 -6
- package/src/tools/imports.ts +9 -12
- package/src/tools/market-data.ts +1067 -757
- package/src/tools/market-enrichment.ts +3 -3
- package/src/tools/market-fetch.ts +148 -67
- package/src/tools/market-imports.ts +12 -12
- package/src/tools/middleware/sync-middleware.ts +5 -6
- package/src/tools/performance.ts +185 -302
- package/src/tools/profile-analysis.ts +52 -66
- package/src/tools/profiles.ts +106 -69
- package/src/tools/regime-advisor.ts +20 -45
- package/src/tools/replay.ts +81 -77
- package/src/tools/reports/discrepancies.ts +298 -328
- package/src/tools/reports/fields.ts +7 -25
- package/src/tools/reports/helpers.ts +18 -49
- package/src/tools/reports/predictive.ts +27 -70
- package/src/tools/reports/slippage-trends.ts +315 -345
- package/src/tools/reports/slippage.ts +1 -4
- package/src/tools/reports/strategy-matches.ts +399 -441
- package/src/tools/schema.ts +43 -40
- package/src/tools/shared/filters.ts +3 -9
- package/src/tools/snapshot.ts +9 -30
- package/src/tools/sql.ts +15 -14
- package/src/tools/tickers.ts +1 -4
- package/src/utils/batch-exit-analysis.ts +31 -42
- package/src/utils/black-scholes.ts +39 -29
- package/src/utils/block-loader.ts +68 -82
- package/src/utils/calibration-probe.ts +1 -2
- package/src/utils/chain-loader.ts +3 -3
- package/src/utils/csv-discovery.ts +16 -22
- package/src/utils/data-quality.ts +24 -36
- package/src/utils/exit-triggers.ts +91 -96
- package/src/utils/field-timing.ts +94 -79
- package/src/utils/filter-predicates.ts +13 -9
- package/src/utils/flatfile-importer.ts +94 -64
- package/src/utils/greeks-decomposition.ts +152 -100
- package/src/utils/iv-solver-pool.ts +55 -25
- package/src/utils/iv-solver-worker.ts +5 -5
- package/src/utils/market-enricher.ts +528 -497
- package/src/utils/market-importer.ts +31 -12
- package/src/utils/market-provider.ts +21 -23
- package/src/utils/massive-tier.ts +5 -7
- package/src/utils/migrate-option-data-helpers.ts +2 -8
- package/src/utils/option-quote-greeks.ts +25 -31
- package/src/utils/option-time.ts +4 -8
- package/src/utils/output-formatter.ts +1 -4
- package/src/utils/provider-capabilities.ts +1 -4
- package/src/utils/providers/massive.ts +59 -93
- package/src/utils/providers/thetadata/backfill.ts +14 -23
- package/src/utils/providers/thetadata/client.ts +12 -8
- package/src/utils/providers/thetadata/decode.ts +2 -20
- package/src/utils/providers/thetadata/endpoints.ts +32 -40
- package/src/utils/providers/thetadata/join.ts +11 -10
- package/src/utils/providers/thetadata/proto.ts +12 -10
- package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
- package/src/utils/providers/thetadata.ts +11 -10
- package/src/utils/quote-enricher.ts +4 -4
- package/src/utils/quote-parquet-projection.ts +3 -11
- package/src/utils/sample-date-selector.ts +3 -5
- package/src/utils/schema-metadata.ts +102 -70
- package/src/utils/ticker.ts +5 -9
- package/src/utils/trade-replay.ts +77 -68
- package/dist/chunk-4BLCXNQ6.js.map +0 -1
- package/dist/chunk-BKQ4PM4Y.js.map +0 -1
- package/dist/chunk-FGZH632F.js.map +0 -1
- package/dist/chunk-LDKTV7GW.js.map +0 -1
- package/dist/chunk-PRAYH3RT.js.map +0 -1
- package/dist/chunk-W2PP3LEH.js.map +0 -1
- package/dist/chunk-XXYOUIZY.js.map +0 -1
- package/dist/daily-log-processor-6MWJ23JK.js +0 -9
- package/dist/market-provider-VDRJUEF2.js +0 -16
- package/dist/trade-processor-NHU4VWRX.js +0 -9
- package/server/chunk-5EBXHT6C.js.map +0 -1
- package/server/chunk-72GKJE2U.js.map +0 -1
- package/server/chunk-CSDVJPBB.js.map +0 -1
- package/server/chunk-FBNDMCT5.js.map +0 -1
- package/server/chunk-NRFXAJF7.js.map +0 -1
- package/server/chunk-NZO6PT64.js.map +0 -1
- package/server/chunk-PNKG7RY7.js.map +0 -1
- package/server/chunk-WA5AAPCH.js.map +0 -1
- package/server/config-6IZXEFEX.js.map +0 -1
- package/server/daily-log-processor-Y3PVSVBM.js +0 -10
- package/server/market-provider-VOYYVYWT.js +0 -17
- package/server/trade-processor-JWVS37KM.js +0 -10
- /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-BY3ISY6K.js.map} +0 -0
- /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
- /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-H3ARIVZ4.js.map} +0 -0
- /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
- /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
- /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.js.map} +0 -0
- /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
- /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-ENEUT22A.js.map} +0 -0
- /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
- /package/server/{market-provider-VOYYVYWT.js.map → market-provider-B437HKLW.js.map} +0 -0
- /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
- /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
- /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.js.map} +0 -0
- /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
|
@@ -20,10 +20,7 @@ import {
|
|
|
20
20
|
type ExitTriggerConfig,
|
|
21
21
|
type LegGroupConfig,
|
|
22
22
|
} from "../utils/exit-triggers.ts";
|
|
23
|
-
import {
|
|
24
|
-
decomposeGreeks,
|
|
25
|
-
type LegGroupDef,
|
|
26
|
-
} from "../utils/greeks-decomposition.ts";
|
|
23
|
+
import { decomposeGreeks, type LegGroupDef } from "../utils/greeks-decomposition.ts";
|
|
27
24
|
import { markPrice } from "../utils/trade-replay.ts";
|
|
28
25
|
|
|
29
26
|
// ---------------------------------------------------------------------------
|
|
@@ -31,34 +28,55 @@ import { markPrice } from "../utils/trade-replay.ts";
|
|
|
31
28
|
// ---------------------------------------------------------------------------
|
|
32
29
|
|
|
33
30
|
const triggerTypeEnum = z.enum([
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
"profitTarget",
|
|
32
|
+
"stopLoss",
|
|
33
|
+
"trailingStop",
|
|
34
|
+
"profitAction",
|
|
35
|
+
"dteExit",
|
|
36
|
+
"ditExit",
|
|
37
|
+
"clockTimeExit",
|
|
38
|
+
"underlyingPriceMove",
|
|
39
|
+
"positionDelta",
|
|
40
|
+
"perLegDelta",
|
|
41
|
+
"vixMove",
|
|
42
|
+
"vix9dMove",
|
|
43
|
+
"vix9dVixRatio",
|
|
44
|
+
"slRatioThreshold",
|
|
45
|
+
"slRatioMove",
|
|
39
46
|
]);
|
|
40
47
|
|
|
41
48
|
const triggerConfigSchema = z.object({
|
|
42
49
|
type: triggerTypeEnum,
|
|
43
50
|
threshold: z.number(),
|
|
44
|
-
unit: z.enum([
|
|
51
|
+
unit: z.enum(["percent", "dollar"]).default("dollar").optional(),
|
|
45
52
|
expiry: z.string().optional(),
|
|
46
53
|
openDate: z.string().optional(),
|
|
47
54
|
clockTime: z.string().optional(),
|
|
48
55
|
trailAmount: z.number().optional(),
|
|
49
|
-
steps: z
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
steps: z
|
|
57
|
+
.array(
|
|
58
|
+
z.object({
|
|
59
|
+
armAt: z.number(),
|
|
60
|
+
stopAt: z.number(),
|
|
61
|
+
closeAllocationPct: z
|
|
62
|
+
.number()
|
|
63
|
+
.min(0)
|
|
64
|
+
.max(1)
|
|
65
|
+
.optional()
|
|
66
|
+
.describe("Fraction of REMAINING position to close at this milestone (0-1)"),
|
|
67
|
+
}),
|
|
68
|
+
)
|
|
69
|
+
.optional(),
|
|
55
70
|
spreadWidth: z.number().optional(),
|
|
56
71
|
contracts: z.number().optional(),
|
|
57
|
-
legIndex: z
|
|
72
|
+
legIndex: z
|
|
73
|
+
.number()
|
|
74
|
+
.optional()
|
|
58
75
|
.describe("0-based leg index for perLegDelta — targets specific leg"),
|
|
59
|
-
exitAbove: z.number().optional()
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
exitAbove: z.number().optional().describe("Fire when value exceeds this (directional, no abs)"),
|
|
77
|
+
exitBelow: z
|
|
78
|
+
.number()
|
|
79
|
+
.optional()
|
|
62
80
|
.describe("Fire when value drops below this (directional, no abs)"),
|
|
63
81
|
});
|
|
64
82
|
|
|
@@ -88,19 +106,27 @@ export const analyzeExitTriggersSchema = z.object({
|
|
|
88
106
|
close_date: z.string().optional(),
|
|
89
107
|
multiplier: z.number().default(100),
|
|
90
108
|
|
|
91
|
-
triggers: z.array(triggerConfigSchema)
|
|
92
|
-
.describe("Exit triggers to evaluate against the P&L path"),
|
|
109
|
+
triggers: z.array(triggerConfigSchema).describe("Exit triggers to evaluate against the P&L path"),
|
|
93
110
|
|
|
94
|
-
actual_exit_timestamp: z
|
|
111
|
+
actual_exit_timestamp: z
|
|
112
|
+
.string()
|
|
113
|
+
.optional()
|
|
95
114
|
.describe("Actual exit time for comparison (format: YYYY-MM-DD HH:MM)"),
|
|
96
115
|
|
|
97
|
-
leg_groups: z
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
116
|
+
leg_groups: z
|
|
117
|
+
.array(
|
|
118
|
+
z.object({
|
|
119
|
+
label: z.string(),
|
|
120
|
+
leg_indices: z.array(z.number()),
|
|
121
|
+
triggers: z.array(triggerConfigSchema),
|
|
122
|
+
}),
|
|
123
|
+
)
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Per-leg-group exit triggers for multi-structure strategies"),
|
|
126
|
+
|
|
127
|
+
format: z
|
|
128
|
+
.enum(["summary", "full"])
|
|
129
|
+
.default("summary")
|
|
104
130
|
.describe("'summary' omits per-step trigger states, 'full' includes all fire events"),
|
|
105
131
|
});
|
|
106
132
|
|
|
@@ -117,12 +143,19 @@ export const decomposeGreeksSchema = z.object({
|
|
|
117
143
|
close_date: z.string().optional(),
|
|
118
144
|
multiplier: z.number().default(100),
|
|
119
145
|
|
|
120
|
-
leg_groups: z
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
146
|
+
leg_groups: z
|
|
147
|
+
.array(
|
|
148
|
+
z.object({
|
|
149
|
+
label: z.string(),
|
|
150
|
+
leg_indices: z.array(z.number()),
|
|
151
|
+
}),
|
|
152
|
+
)
|
|
153
|
+
.optional()
|
|
154
|
+
.describe("Leg grouping for per-group vega attribution (e.g., front_month vs back_month)"),
|
|
155
|
+
|
|
156
|
+
format: z
|
|
157
|
+
.enum(["summary", "full"])
|
|
158
|
+
.default("summary")
|
|
126
159
|
.describe("'summary' shows ranked factors, 'full' includes per-step contributions"),
|
|
127
160
|
skip_quotes: z
|
|
128
161
|
.boolean()
|
|
@@ -136,7 +169,9 @@ export const decomposeGreeksSchema = z.object({
|
|
|
136
169
|
|
|
137
170
|
// Reverse-map weekly roots to standard root for underlying/VIX fetching
|
|
138
171
|
const REVERSE_ROOT_MAP: Record<string, string> = {
|
|
139
|
-
SPXW:
|
|
172
|
+
SPXW: "SPX",
|
|
173
|
+
NDXP: "NDX",
|
|
174
|
+
RUTW: "RUT",
|
|
140
175
|
};
|
|
141
176
|
|
|
142
177
|
/**
|
|
@@ -145,7 +180,7 @@ const REVERSE_ROOT_MAP: Record<string, string> = {
|
|
|
145
180
|
*/
|
|
146
181
|
function extractUnderlyingTicker(occTicker: string): string {
|
|
147
182
|
const rootMatch = occTicker.match(/^([A-Z]+)/);
|
|
148
|
-
const rawRoot = rootMatch ? rootMatch[1] :
|
|
183
|
+
const rawRoot = rootMatch ? rootMatch[1] : "";
|
|
149
184
|
return REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;
|
|
150
185
|
}
|
|
151
186
|
|
|
@@ -182,12 +217,17 @@ async function fetchPriceMap(
|
|
|
182
217
|
// applied at the underlying-consumer site.
|
|
183
218
|
for (const b of bars) {
|
|
184
219
|
if (
|
|
185
|
-
!Number.isFinite(b.open)
|
|
186
|
-
|
|
187
|
-
!Number.isFinite(b.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
220
|
+
!Number.isFinite(b.open) ||
|
|
221
|
+
b.open <= 0 ||
|
|
222
|
+
!Number.isFinite(b.high) ||
|
|
223
|
+
b.high <= 0 ||
|
|
224
|
+
!Number.isFinite(b.low) ||
|
|
225
|
+
b.low <= 0 ||
|
|
226
|
+
!Number.isFinite(b.close) ||
|
|
227
|
+
b.close <= 0
|
|
228
|
+
)
|
|
229
|
+
continue;
|
|
230
|
+
const ts = `${b.date} ${b.time ?? ""}`.trim();
|
|
191
231
|
map.set(ts, markPrice(b));
|
|
192
232
|
}
|
|
193
233
|
} catch {
|
|
@@ -207,9 +247,15 @@ export async function handleAnalyzeExitTriggers(
|
|
|
207
247
|
injectedConn?: import("@duckdb/node-api").DuckDBConnection,
|
|
208
248
|
): Promise<ReturnType<typeof analyzeExitTriggers>> {
|
|
209
249
|
const {
|
|
210
|
-
legs: inputLegs,
|
|
211
|
-
|
|
212
|
-
|
|
250
|
+
legs: inputLegs,
|
|
251
|
+
block_id,
|
|
252
|
+
trade_index,
|
|
253
|
+
open_date,
|
|
254
|
+
close_date,
|
|
255
|
+
multiplier,
|
|
256
|
+
triggers,
|
|
257
|
+
actual_exit_timestamp,
|
|
258
|
+
leg_groups,
|
|
213
259
|
} = params;
|
|
214
260
|
|
|
215
261
|
// 1. Run replay to get full P&L path with greeks
|
|
@@ -221,8 +267,8 @@ export async function handleAnalyzeExitTriggers(
|
|
|
221
267
|
open_date,
|
|
222
268
|
close_date,
|
|
223
269
|
multiplier,
|
|
224
|
-
format:
|
|
225
|
-
close_at:
|
|
270
|
+
format: "full",
|
|
271
|
+
close_at: "trade",
|
|
226
272
|
skip_quotes: false,
|
|
227
273
|
},
|
|
228
274
|
baseDir,
|
|
@@ -243,7 +289,7 @@ export async function handleAnalyzeExitTriggers(
|
|
|
243
289
|
overall: {
|
|
244
290
|
triggers: [],
|
|
245
291
|
firstToFire: null,
|
|
246
|
-
summary:
|
|
292
|
+
summary: "No P&L data available from replay.",
|
|
247
293
|
},
|
|
248
294
|
};
|
|
249
295
|
}
|
|
@@ -253,9 +299,9 @@ export async function handleAnalyzeExitTriggers(
|
|
|
253
299
|
const lastDate = pnlPath[pnlPath.length - 1].timestamp.slice(0, 10);
|
|
254
300
|
|
|
255
301
|
// 3. Check which external data maps are needed
|
|
256
|
-
const allTriggerTypes = new Set(triggers.map(t => t.type));
|
|
302
|
+
const allTriggerTypes = new Set(triggers.map((t) => t.type));
|
|
257
303
|
const groupTriggerTypes = new Set(
|
|
258
|
-
(leg_groups ?? []).flatMap(g => g.triggers.map(t => t.type))
|
|
304
|
+
(leg_groups ?? []).flatMap((g) => g.triggers.map((t) => t.type)),
|
|
259
305
|
);
|
|
260
306
|
for (const t of groupTriggerTypes) allTriggerTypes.add(t);
|
|
261
307
|
|
|
@@ -267,24 +313,22 @@ export async function handleAnalyzeExitTriggers(
|
|
|
267
313
|
let vix9dPrices: Map<string, number> | undefined;
|
|
268
314
|
let underlyingPrices: Map<string, number> | undefined;
|
|
269
315
|
|
|
270
|
-
const needsVix = allTriggerTypes.has(
|
|
271
|
-
const needsVix9d = allTriggerTypes.has(
|
|
272
|
-
const needsUnderlying = allTriggerTypes.has(
|
|
316
|
+
const needsVix = allTriggerTypes.has("vixMove") || allTriggerTypes.has("vix9dVixRatio");
|
|
317
|
+
const needsVix9d = allTriggerTypes.has("vix9dMove") || allTriggerTypes.has("vix9dVixRatio");
|
|
318
|
+
const needsUnderlying = allTriggerTypes.has("underlyingPriceMove");
|
|
273
319
|
|
|
274
320
|
if (needsVix) {
|
|
275
|
-
vixPrices = await fetchPriceMap(stores,
|
|
321
|
+
vixPrices = await fetchPriceMap(stores, "VIX", firstDate, lastDate);
|
|
276
322
|
}
|
|
277
323
|
if (needsVix9d) {
|
|
278
|
-
vix9dPrices = await fetchPriceMap(stores,
|
|
324
|
+
vix9dPrices = await fetchPriceMap(stores, "VIX9D", firstDate, lastDate);
|
|
279
325
|
}
|
|
280
326
|
if (needsUnderlying) {
|
|
281
|
-
underlyingPrices = await fetchPriceMap(
|
|
282
|
-
stores, underlyingTicker, firstDate, lastDate,
|
|
283
|
-
);
|
|
327
|
+
underlyingPrices = await fetchPriceMap(stores, underlyingTicker, firstDate, lastDate);
|
|
284
328
|
}
|
|
285
329
|
|
|
286
330
|
// 4. Map tool trigger params to ExitTriggerConfig[] with data maps
|
|
287
|
-
const exitTriggers: ExitTriggerConfig[] = triggers.map(t => ({
|
|
331
|
+
const exitTriggers: ExitTriggerConfig[] = triggers.map((t) => ({
|
|
288
332
|
type: t.type,
|
|
289
333
|
threshold: t.threshold,
|
|
290
334
|
unit: t.unit,
|
|
@@ -303,10 +347,10 @@ export async function handleAnalyzeExitTriggers(
|
|
|
303
347
|
}));
|
|
304
348
|
|
|
305
349
|
// 5. Map leg groups with their triggers
|
|
306
|
-
const legGroupConfigs: LegGroupConfig[] | undefined = leg_groups?.map(g => ({
|
|
350
|
+
const legGroupConfigs: LegGroupConfig[] | undefined = leg_groups?.map((g) => ({
|
|
307
351
|
label: g.label,
|
|
308
352
|
legIndices: g.leg_indices,
|
|
309
|
-
triggers: g.triggers.map(t => ({
|
|
353
|
+
triggers: g.triggers.map((t) => ({
|
|
310
354
|
type: t.type,
|
|
311
355
|
threshold: t.threshold,
|
|
312
356
|
unit: t.unit,
|
|
@@ -346,9 +390,15 @@ export async function handleDecomposeGreeks(
|
|
|
346
390
|
injectedConn?: import("@duckdb/node-api").DuckDBConnection,
|
|
347
391
|
): Promise<import("../utils/greeks-decomposition.ts").GreeksDecompositionResult> {
|
|
348
392
|
const {
|
|
349
|
-
legs: inputLegs,
|
|
350
|
-
|
|
351
|
-
|
|
393
|
+
legs: inputLegs,
|
|
394
|
+
block_id,
|
|
395
|
+
trade_index,
|
|
396
|
+
open_date,
|
|
397
|
+
close_date,
|
|
398
|
+
multiplier,
|
|
399
|
+
leg_groups,
|
|
400
|
+
format,
|
|
401
|
+
skip_quotes,
|
|
352
402
|
} = params;
|
|
353
403
|
|
|
354
404
|
// 1. Run replay to get full P&L path with greeks
|
|
@@ -360,8 +410,8 @@ export async function handleDecomposeGreeks(
|
|
|
360
410
|
open_date,
|
|
361
411
|
close_date,
|
|
362
412
|
multiplier,
|
|
363
|
-
format:
|
|
364
|
-
close_at:
|
|
413
|
+
format: "full",
|
|
414
|
+
close_at: "trade",
|
|
365
415
|
skip_quotes,
|
|
366
416
|
},
|
|
367
417
|
baseDir,
|
|
@@ -375,7 +425,7 @@ export async function handleDecomposeGreeks(
|
|
|
375
425
|
// 2. Check greeks data availability
|
|
376
426
|
if (pnlPath.length > 0 && !pnlPath[0].legGreeks) {
|
|
377
427
|
throw new Error(
|
|
378
|
-
"No greeks data available. Use the data-pipeline tools to backfill the option-leg quotes and underlying price data into the local cache."
|
|
428
|
+
"No greeks data available. Use the data-pipeline tools to backfill the option-leg quotes and underlying price data into the local cache.",
|
|
379
429
|
);
|
|
380
430
|
}
|
|
381
431
|
|
|
@@ -388,25 +438,28 @@ export async function handleDecomposeGreeks(
|
|
|
388
438
|
}
|
|
389
439
|
|
|
390
440
|
// 4. Map leg groups
|
|
391
|
-
const legGroupDefs: LegGroupDef[] | undefined = leg_groups?.map(g => ({
|
|
441
|
+
const legGroupDefs: LegGroupDef[] | undefined = leg_groups?.map((g) => ({
|
|
392
442
|
label: g.label,
|
|
393
443
|
legIndices: g.leg_indices,
|
|
394
444
|
}));
|
|
395
445
|
|
|
396
446
|
// 5. Build leg pricing inputs from OCC tickers for full revaluation
|
|
397
447
|
const DIVIDEND_YIELDS: Record<string, number> = {
|
|
398
|
-
SPX: 0.015,
|
|
448
|
+
SPX: 0.015,
|
|
449
|
+
SPXW: 0.015,
|
|
450
|
+
NDX: 0.015,
|
|
451
|
+
NDXP: 0.015,
|
|
399
452
|
};
|
|
400
453
|
const rootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);
|
|
401
|
-
const rawRoot = rootMatch ? rootMatch[1] :
|
|
454
|
+
const rawRoot = rootMatch ? rootMatch[1] : "";
|
|
402
455
|
const divYield = DIVIDEND_YIELDS[rawRoot] ?? 0;
|
|
403
456
|
|
|
404
|
-
const legPricingInputs = replayLegs.map(leg => {
|
|
457
|
+
const legPricingInputs = replayLegs.map((leg) => {
|
|
405
458
|
const m = leg.occTicker.match(/^[A-Z]+(\d{6})([CP])(\d{8})$/);
|
|
406
|
-
if (!m) return { strike: 0, type:
|
|
459
|
+
if (!m) return { strike: 0, type: "C" as const, expiryDate: "" };
|
|
407
460
|
return {
|
|
408
461
|
strike: parseInt(m[3], 10) / 1000,
|
|
409
|
-
type: m[2] as
|
|
462
|
+
type: m[2] as "C" | "P",
|
|
410
463
|
expiryDate: `20${m[1].slice(0, 2)}-${m[1].slice(2, 4)}-${m[1].slice(4, 6)}`,
|
|
411
464
|
};
|
|
412
465
|
});
|
|
@@ -475,7 +528,7 @@ export function registerExitAnalysisTools(
|
|
|
475
528
|
isError: true,
|
|
476
529
|
};
|
|
477
530
|
}
|
|
478
|
-
}
|
|
531
|
+
},
|
|
479
532
|
);
|
|
480
533
|
|
|
481
534
|
server.registerTool(
|
|
@@ -507,6 +560,6 @@ export function registerExitAnalysisTools(
|
|
|
507
560
|
isError: true,
|
|
508
561
|
};
|
|
509
562
|
}
|
|
510
|
-
}
|
|
563
|
+
},
|
|
511
564
|
);
|
|
512
565
|
}
|
|
@@ -81,11 +81,15 @@ export const getGreeksAttributionSchema = z.object({
|
|
|
81
81
|
.int()
|
|
82
82
|
.min(0)
|
|
83
83
|
.optional()
|
|
84
|
-
.describe(
|
|
84
|
+
.describe(
|
|
85
|
+
"Trade index (required for instance mode). Use get_block_info to find trade indices.",
|
|
86
|
+
),
|
|
85
87
|
skip_quotes: z
|
|
86
88
|
.boolean()
|
|
87
89
|
.default(true)
|
|
88
|
-
.describe(
|
|
90
|
+
.describe(
|
|
91
|
+
"Use cached bar data only (fast). Set false to fetch NBBO quotes for higher precision.",
|
|
92
|
+
),
|
|
89
93
|
detailed: z
|
|
90
94
|
.boolean()
|
|
91
95
|
.default(false)
|
|
@@ -105,7 +109,16 @@ const COLLAPSE_MAP: Record<string, string> = {
|
|
|
105
109
|
vanna: "vega",
|
|
106
110
|
};
|
|
107
111
|
|
|
108
|
-
const FACTOR_ORDER: string[] = [
|
|
112
|
+
const FACTOR_ORDER: string[] = [
|
|
113
|
+
"theta",
|
|
114
|
+
"vega",
|
|
115
|
+
"delta",
|
|
116
|
+
"gamma",
|
|
117
|
+
"residual",
|
|
118
|
+
"time_and_vol",
|
|
119
|
+
"charm",
|
|
120
|
+
"vanna",
|
|
121
|
+
];
|
|
109
122
|
|
|
110
123
|
export function collapseFactors(
|
|
111
124
|
factors: FactorContribution[],
|
|
@@ -182,10 +195,26 @@ export async function handleGetGreeksAttribution(
|
|
|
182
195
|
if (trade_index == null) {
|
|
183
196
|
throw new Error("trade_index is required for instance mode");
|
|
184
197
|
}
|
|
185
|
-
return handleInstanceMode(
|
|
198
|
+
return handleInstanceMode(
|
|
199
|
+
block_id,
|
|
200
|
+
trade_index,
|
|
201
|
+
skip_quotes,
|
|
202
|
+
detailed,
|
|
203
|
+
baseDir,
|
|
204
|
+
stores,
|
|
205
|
+
injectedConn,
|
|
206
|
+
);
|
|
186
207
|
}
|
|
187
208
|
|
|
188
|
-
return handleSummaryMode(
|
|
209
|
+
return handleSummaryMode(
|
|
210
|
+
block_id,
|
|
211
|
+
skip_quotes,
|
|
212
|
+
detailed,
|
|
213
|
+
strategy,
|
|
214
|
+
baseDir,
|
|
215
|
+
stores,
|
|
216
|
+
injectedConn,
|
|
217
|
+
);
|
|
189
218
|
}
|
|
190
219
|
|
|
191
220
|
async function handleSummaryMode(
|
|
@@ -197,7 +226,7 @@ async function handleSummaryMode(
|
|
|
197
226
|
stores: MarketStores,
|
|
198
227
|
injectedConn?: import("@duckdb/node-api").DuckDBConnection,
|
|
199
228
|
): Promise<AttributionSummaryResult> {
|
|
200
|
-
const conn = injectedConn ?? await getConnection(baseDir);
|
|
229
|
+
const conn = injectedConn ?? (await getConnection(baseDir));
|
|
201
230
|
|
|
202
231
|
const selectedTradesQuery = strategy
|
|
203
232
|
? `SELECT trade_index, pl
|
|
@@ -224,7 +253,7 @@ async function handleSummaryMode(
|
|
|
224
253
|
throw new Error(
|
|
225
254
|
strategy
|
|
226
255
|
? `No trades found for block "${block_id}" with strategy "${strategy}"`
|
|
227
|
-
: `No trades found for block "${block_id}"
|
|
256
|
+
: `No trades found for block "${block_id}"`,
|
|
228
257
|
);
|
|
229
258
|
}
|
|
230
259
|
|
|
@@ -254,16 +283,21 @@ async function handleSummaryMode(
|
|
|
254
283
|
baseDir,
|
|
255
284
|
stores,
|
|
256
285
|
injectedConn,
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
286
|
+
)
|
|
287
|
+
.then((result) => {
|
|
288
|
+
for (const factor of result.factors) {
|
|
289
|
+
accumulated.set(
|
|
290
|
+
factor.factor,
|
|
291
|
+
(accumulated.get(factor.factor) ?? 0) + factor.totalPnl,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
actualTotalPnl += trade.actualPl;
|
|
295
|
+
markTotalPnl += result.totalPnlChange;
|
|
296
|
+
decomposed++;
|
|
297
|
+
})
|
|
298
|
+
.catch(() => {
|
|
299
|
+
skipped++;
|
|
300
|
+
}),
|
|
267
301
|
);
|
|
268
302
|
}
|
|
269
303
|
await Promise.allSettled(promises);
|
|
@@ -306,16 +340,15 @@ async function handleSummaryMode(
|
|
|
306
340
|
// low-quality market data rather than genuine fill advantage.
|
|
307
341
|
const hints: string[] = [];
|
|
308
342
|
if (hint) hints.push(hint);
|
|
309
|
-
const edgeRatio =
|
|
310
|
-
? Math.abs(executionEdge) / Math.abs(actualTotalPnl)
|
|
311
|
-
: 0;
|
|
343
|
+
const edgeRatio =
|
|
344
|
+
Math.abs(actualTotalPnl) > 0.01 ? Math.abs(executionEdge) / Math.abs(actualTotalPnl) : 0;
|
|
312
345
|
if (edgeRatio > 3) {
|
|
313
346
|
hints.push(
|
|
314
347
|
`Execution edge is ${Math.round(edgeRatio)}x the actual P&L — ` +
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
348
|
+
`mark-to-market pricing may be based on sparse or low-quality data. ` +
|
|
349
|
+
(skip_quotes
|
|
350
|
+
? `Re-run with skip_quotes=false for NBBO-based marks.`
|
|
351
|
+
: `Consider whether intraday bar coverage is sufficient for this date range.`),
|
|
319
352
|
);
|
|
320
353
|
}
|
|
321
354
|
|
|
@@ -330,7 +363,7 @@ async function handleSummaryMode(
|
|
|
330
363
|
gross_attribution_flow: Math.round(grossAttributionFlow * 100) / 100,
|
|
331
364
|
attribution,
|
|
332
365
|
precision,
|
|
333
|
-
...(hints.length > 0 ? { hint: hints.join(
|
|
366
|
+
...(hints.length > 0 ? { hint: hints.join(" ") } : {}),
|
|
334
367
|
};
|
|
335
368
|
}
|
|
336
369
|
|
|
@@ -343,7 +376,7 @@ async function handleInstanceMode(
|
|
|
343
376
|
stores: MarketStores,
|
|
344
377
|
injectedConn?: import("@duckdb/node-api").DuckDBConnection,
|
|
345
378
|
): Promise<AttributionInstanceResult> {
|
|
346
|
-
const conn = injectedConn ?? await getConnection(baseDir);
|
|
379
|
+
const conn = injectedConn ?? (await getConnection(baseDir));
|
|
347
380
|
|
|
348
381
|
// Get trade date for the response
|
|
349
382
|
const tradeResult = await conn.runAndReadAll(
|
|
@@ -351,7 +384,7 @@ async function handleInstanceMode(
|
|
|
351
384
|
WHERE block_id = $1
|
|
352
385
|
ORDER BY date_opened, rowid
|
|
353
386
|
LIMIT 1 OFFSET $2`,
|
|
354
|
-
[block_id, trade_index]
|
|
387
|
+
[block_id, trade_index],
|
|
355
388
|
);
|
|
356
389
|
const tradeRows = tradeResult.getRows();
|
|
357
390
|
if (tradeRows.length === 0) {
|
|
@@ -398,10 +431,20 @@ async function handleInstanceMode(
|
|
|
398
431
|
for (let i = 0; i <= stepCount; i++) {
|
|
399
432
|
const entry: AttributionStepEntry = {
|
|
400
433
|
date: getStepDate(i),
|
|
401
|
-
delta: getStepValue(
|
|
434
|
+
delta: getStepValue(
|
|
435
|
+
factorSteps,
|
|
436
|
+
"delta",
|
|
437
|
+
i,
|
|
438
|
+
detailed ? 0 : (factorSteps.get("charm")?.[i] ?? 0),
|
|
439
|
+
),
|
|
402
440
|
gamma: getStepValue(factorSteps, "gamma", i, 0),
|
|
403
441
|
theta: getStepValue(factorSteps, "theta", i, 0),
|
|
404
|
-
vega: getStepValue(
|
|
442
|
+
vega: getStepValue(
|
|
443
|
+
factorSteps,
|
|
444
|
+
"vega",
|
|
445
|
+
i,
|
|
446
|
+
detailed ? 0 : (factorSteps.get("vanna")?.[i] ?? 0),
|
|
447
|
+
),
|
|
405
448
|
residual: getStepValue(factorSteps, "residual", i, 0),
|
|
406
449
|
};
|
|
407
450
|
// time_and_vol: present when numerical fallback was used (theta/vega couldn't be separated)
|
|
@@ -441,10 +484,16 @@ async function handleInstanceMode(
|
|
|
441
484
|
* Keeps the output compact and useful.
|
|
442
485
|
*/
|
|
443
486
|
export function filterSparseSteps(steps: AttributionStepEntry[]): AttributionStepEntry[] {
|
|
444
|
-
return steps.filter(
|
|
445
|
-
s
|
|
446
|
-
|
|
447
|
-
|
|
487
|
+
return steps.filter(
|
|
488
|
+
(s) =>
|
|
489
|
+
s.delta !== 0 ||
|
|
490
|
+
s.gamma !== 0 ||
|
|
491
|
+
s.theta !== 0 ||
|
|
492
|
+
s.vega !== 0 ||
|
|
493
|
+
s.residual !== 0 ||
|
|
494
|
+
(s.time_and_vol ?? 0) !== 0 ||
|
|
495
|
+
(s.charm ?? 0) !== 0 ||
|
|
496
|
+
(s.vanna ?? 0) !== 0,
|
|
448
497
|
);
|
|
449
498
|
}
|
|
450
499
|
|
|
@@ -482,8 +531,8 @@ export function registerGreeksAttributionTools(
|
|
|
482
531
|
|
|
483
532
|
const isSummary = !("steps" in result);
|
|
484
533
|
const summary = isSummary
|
|
485
|
-
? `Block "${params.block_id}" attribution (${(result as AttributionSummaryResult).trades_decomposed}/${(result as AttributionSummaryResult).trades_total} trades): ${(result as AttributionSummaryResult).attribution.map(a => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(", ")}, actual P&L ${(result as AttributionSummaryResult).total_pnl}, execution edge ${(result as AttributionSummaryResult).execution_edge}`
|
|
486
|
-
: `Trade #${(result as AttributionInstanceResult).trade_index} attribution: ${(result as AttributionInstanceResult).attribution.map(a => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(", ")}, actual P&L ${(result as AttributionInstanceResult).total_pnl}, execution edge ${(result as AttributionInstanceResult).execution_edge}`;
|
|
534
|
+
? `Block "${params.block_id}" attribution (${(result as AttributionSummaryResult).trades_decomposed}/${(result as AttributionSummaryResult).trades_total} trades): ${(result as AttributionSummaryResult).attribution.map((a) => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(", ")}, actual P&L ${(result as AttributionSummaryResult).total_pnl}, execution edge ${(result as AttributionSummaryResult).execution_edge}`
|
|
535
|
+
: `Trade #${(result as AttributionInstanceResult).trade_index} attribution: ${(result as AttributionInstanceResult).attribution.map((a) => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(", ")}, actual P&L ${(result as AttributionInstanceResult).total_pnl}, execution edge ${(result as AttributionInstanceResult).execution_edge}`;
|
|
487
536
|
|
|
488
537
|
return createToolOutput(summary, result);
|
|
489
538
|
} catch (error) {
|
package/src/tools/guides.ts
CHANGED
|
@@ -348,7 +348,7 @@ export function registerGuideTools(server: McpServer): void {
|
|
|
348
348
|
.string()
|
|
349
349
|
.optional()
|
|
350
350
|
.describe(
|
|
351
|
-
"Optional keyword to narrow results within the topic (e.g., 'trailing' within exit_conditions)"
|
|
351
|
+
"Optional keyword to narrow results within the topic (e.g., 'trailing' within exit_conditions)",
|
|
352
352
|
),
|
|
353
353
|
}),
|
|
354
354
|
},
|
|
@@ -367,9 +367,7 @@ export function registerGuideTools(server: McpServer): void {
|
|
|
367
367
|
if (subtopic) {
|
|
368
368
|
const keyword = subtopic.toLowerCase();
|
|
369
369
|
const sections = content.split(/\n(?=## )/);
|
|
370
|
-
const matched = sections.filter((s) =>
|
|
371
|
-
s.toLowerCase().includes(keyword)
|
|
372
|
-
);
|
|
370
|
+
const matched = sections.filter((s) => s.toLowerCase().includes(keyword));
|
|
373
371
|
if (matched.length > 0) {
|
|
374
372
|
content = matched.join("\n\n");
|
|
375
373
|
}
|
|
@@ -380,8 +378,8 @@ export function registerGuideTools(server: McpServer): void {
|
|
|
380
378
|
|
|
381
379
|
return createToolOutput(
|
|
382
380
|
`${entry.title} — use other topics for more detail: ${allTopics.filter((t) => t !== topic).join(", ")}`,
|
|
383
|
-
{ topic, title: entry.title, content, availableTopics: allTopics }
|
|
381
|
+
{ topic, title: entry.title, content, availableTopics: allTopics },
|
|
384
382
|
);
|
|
385
|
-
}
|
|
383
|
+
},
|
|
386
384
|
);
|
|
387
385
|
}
|