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
package/src/tools/replay.ts
CHANGED
|
@@ -43,10 +43,8 @@ export const replayTradeSchema = z.object({
|
|
|
43
43
|
type: z.enum(["C", "P"]).describe("Call or Put"),
|
|
44
44
|
expiry: z.string().describe("Expiration date YYYY-MM-DD"),
|
|
45
45
|
quantity: z.number().describe("Positive = long, negative = short"),
|
|
46
|
-
entry_price: z
|
|
47
|
-
|
|
48
|
-
.describe("Per-contract entry price (premium paid/received)"),
|
|
49
|
-
})
|
|
46
|
+
entry_price: z.number().describe("Per-contract entry price (premium paid/received)"),
|
|
47
|
+
}),
|
|
50
48
|
)
|
|
51
49
|
.optional()
|
|
52
50
|
.describe("Explicit leg definitions for hypothetical replay"),
|
|
@@ -56,22 +54,20 @@ export const replayTradeSchema = z.object({
|
|
|
56
54
|
trade_index: z
|
|
57
55
|
.number()
|
|
58
56
|
.optional()
|
|
59
|
-
.describe(
|
|
60
|
-
"0-based index of trade in block's tradelog (ordered by date_opened)"
|
|
61
|
-
),
|
|
57
|
+
.describe("0-based index of trade in block's tradelog (ordered by date_opened)"),
|
|
62
58
|
|
|
63
59
|
// Common fields
|
|
64
60
|
open_date: z
|
|
65
61
|
.string()
|
|
66
62
|
.optional()
|
|
67
63
|
.describe(
|
|
68
|
-
"Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)"
|
|
64
|
+
"Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)",
|
|
69
65
|
),
|
|
70
66
|
close_date: z
|
|
71
67
|
.string()
|
|
72
68
|
.optional()
|
|
73
69
|
.describe(
|
|
74
|
-
"Trade close date YYYY-MM-DD (required for hypothetical, auto-resolved for tradelog)"
|
|
70
|
+
"Trade close date YYYY-MM-DD (required for hypothetical, auto-resolved for tradelog)",
|
|
75
71
|
),
|
|
76
72
|
multiplier: z
|
|
77
73
|
.number()
|
|
@@ -82,21 +78,21 @@ export const replayTradeSchema = z.object({
|
|
|
82
78
|
.default("sampled")
|
|
83
79
|
.describe(
|
|
84
80
|
"Output format: 'sampled' returns path sampled at ~15min intervals (default), " +
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
"'full' returns complete minute-by-minute P&L path, " +
|
|
82
|
+
"'summary' returns MFE/MAE/P&L without minute-level path",
|
|
87
83
|
),
|
|
88
84
|
close_at: z
|
|
89
85
|
.enum(["trade", "expiry"])
|
|
90
86
|
.default("trade")
|
|
91
87
|
.describe(
|
|
92
88
|
"When to end the P&L path: 'trade' (default) truncates at the trade's actual close time, " +
|
|
93
|
-
|
|
89
|
+
"'expiry' shows full path through option expiry. Only applies to tradelog mode.",
|
|
94
90
|
),
|
|
95
91
|
skip_quotes: z
|
|
96
92
|
.boolean()
|
|
97
93
|
.default(false)
|
|
98
94
|
.describe(
|
|
99
|
-
"Skip NBBO quote enrichment for option bars. Faster, but uses cached trade bars / HL2 marks."
|
|
95
|
+
"Skip NBBO quote enrichment for option bars. Faster, but uses cached trade bars / HL2 marks.",
|
|
100
96
|
),
|
|
101
97
|
});
|
|
102
98
|
|
|
@@ -105,15 +101,25 @@ export const replayTradeSchema = z.object({
|
|
|
105
101
|
// ---------------------------------------------------------------------------
|
|
106
102
|
|
|
107
103
|
const MONTH_MAP: Record<string, string> = {
|
|
108
|
-
Jan:
|
|
109
|
-
|
|
104
|
+
Jan: "01",
|
|
105
|
+
Feb: "02",
|
|
106
|
+
Mar: "03",
|
|
107
|
+
Apr: "04",
|
|
108
|
+
May: "05",
|
|
109
|
+
Jun: "06",
|
|
110
|
+
Jul: "07",
|
|
111
|
+
Aug: "08",
|
|
112
|
+
Sep: "09",
|
|
113
|
+
Oct: "10",
|
|
114
|
+
Nov: "11",
|
|
115
|
+
Dec: "12",
|
|
110
116
|
};
|
|
111
117
|
|
|
112
118
|
/** Convert OO expiry hint "Mar 13" + year "2026" → "2026-03-13" */
|
|
113
119
|
function resolveOOExpiryHint(hint: string, year: string): string {
|
|
114
|
-
const [mon, day] = hint.split(
|
|
115
|
-
const mm = MONTH_MAP[mon] ??
|
|
116
|
-
const dd = day.padStart(2,
|
|
120
|
+
const [mon, day] = hint.split(" ");
|
|
121
|
+
const mm = MONTH_MAP[mon] ?? "01";
|
|
122
|
+
const dd = day.padStart(2, "0");
|
|
117
123
|
return `${year}-${mm}-${dd}`;
|
|
118
124
|
}
|
|
119
125
|
|
|
@@ -130,8 +136,8 @@ export function resolveOODateRange(
|
|
|
130
136
|
tradeOpenDate: string,
|
|
131
137
|
): { from: string; to: string } | null {
|
|
132
138
|
const hints = parsedLegs
|
|
133
|
-
.filter(l => l.expiryHint)
|
|
134
|
-
.map(l => resolveOOExpiryHint(l.expiryHint!, tradeYear));
|
|
139
|
+
.filter((l) => l.expiryHint)
|
|
140
|
+
.map((l) => resolveOOExpiryHint(l.expiryHint!, tradeYear));
|
|
135
141
|
|
|
136
142
|
if (hints.length === 0) return null;
|
|
137
143
|
|
|
@@ -151,16 +157,9 @@ export async function handleReplayTrade(
|
|
|
151
157
|
params: z.infer<typeof replayTradeSchema>,
|
|
152
158
|
baseDir: string,
|
|
153
159
|
stores: MarketStores,
|
|
154
|
-
injectedConn?: import("@duckdb/node-api").DuckDBConnection
|
|
160
|
+
injectedConn?: import("@duckdb/node-api").DuckDBConnection,
|
|
155
161
|
): Promise<ReplayResult> {
|
|
156
|
-
const {
|
|
157
|
-
legs: inputLegs,
|
|
158
|
-
block_id,
|
|
159
|
-
trade_index,
|
|
160
|
-
multiplier,
|
|
161
|
-
close_at,
|
|
162
|
-
skip_quotes,
|
|
163
|
-
} = params;
|
|
162
|
+
const { legs: inputLegs, block_id, trade_index, multiplier, close_at, skip_quotes } = params;
|
|
164
163
|
let { open_date, close_date } = params;
|
|
165
164
|
let tradeCloseTimestamp: string | undefined; // "YYYY-MM-DD HH:MM" when trade actually closed
|
|
166
165
|
|
|
@@ -169,9 +168,7 @@ export async function handleReplayTrade(
|
|
|
169
168
|
if (inputLegs && inputLegs.length > 0) {
|
|
170
169
|
// ----- Mode A: Hypothetical replay -----
|
|
171
170
|
if (!open_date || !close_date) {
|
|
172
|
-
throw new Error(
|
|
173
|
-
"open_date and close_date are required for hypothetical replay mode"
|
|
174
|
-
);
|
|
171
|
+
throw new Error("open_date and close_date are required for hypothetical replay mode");
|
|
175
172
|
}
|
|
176
173
|
|
|
177
174
|
replayLegs = inputLegs.map((leg) => ({
|
|
@@ -182,21 +179,19 @@ export async function handleReplayTrade(
|
|
|
182
179
|
}));
|
|
183
180
|
} else if (block_id !== undefined && trade_index !== undefined) {
|
|
184
181
|
// ----- Mode B: Tradelog replay -----
|
|
185
|
-
const conn = injectedConn ?? await getConnection(baseDir);
|
|
182
|
+
const conn = injectedConn ?? (await getConnection(baseDir));
|
|
186
183
|
|
|
187
184
|
const result = await conn.runAndReadAll(
|
|
188
185
|
`SELECT legs, premium, date_opened, date_closed, ticker, num_contracts, time_closed
|
|
189
186
|
FROM trades.trade_data
|
|
190
187
|
WHERE block_id = '${block_id.replace(/'/g, "''")}'
|
|
191
188
|
ORDER BY date_opened, rowid
|
|
192
|
-
LIMIT 1 OFFSET ${trade_index}
|
|
189
|
+
LIMIT 1 OFFSET ${trade_index}`,
|
|
193
190
|
);
|
|
194
191
|
|
|
195
192
|
const rows = result.getRows();
|
|
196
193
|
if (rows.length === 0) {
|
|
197
|
-
throw new Error(
|
|
198
|
-
`No trade found at index ${trade_index} in block "${block_id}"`
|
|
199
|
-
);
|
|
194
|
+
throw new Error(`No trade found at index ${trade_index} in block "${block_id}"`);
|
|
200
195
|
}
|
|
201
196
|
|
|
202
197
|
const row = rows[0];
|
|
@@ -225,20 +220,19 @@ export async function handleReplayTrade(
|
|
|
225
220
|
parsedLegs = parseLegsString(legsStr);
|
|
226
221
|
} catch {
|
|
227
222
|
throw new Error(
|
|
228
|
-
`Cannot parse legs "${legsStr}" from tradelog — use hypothetical mode with explicit strikes
|
|
223
|
+
`Cannot parse legs "${legsStr}" from tradelog — use hypothetical mode with explicit strikes`,
|
|
229
224
|
);
|
|
230
225
|
}
|
|
231
226
|
|
|
232
227
|
// Build ReplayLeg[] from parsed legs
|
|
233
228
|
const root = ticker || parsedLegs[0].root;
|
|
234
|
-
const perContractPremium =
|
|
235
|
-
numContracts > 0 ? premium / numContracts : premium;
|
|
229
|
+
const perContractPremium = numContracts > 0 ? premium / numContracts : premium;
|
|
236
230
|
|
|
237
231
|
// OO format provides per-leg entry price, contract count, and expiry hint
|
|
238
|
-
const hasOOData = parsedLegs.some(l => l.entryPrice !== undefined);
|
|
232
|
+
const hasOOData = parsedLegs.some((l) => l.entryPrice !== undefined);
|
|
239
233
|
|
|
240
234
|
// Resolve per-leg expiry: OO expiryHint ("Mar 13") + year from trade date
|
|
241
|
-
const tradeYear = (open_date || dateOpened).split(
|
|
235
|
+
const tradeYear = (open_date || dateOpened).split("-")[0];
|
|
242
236
|
|
|
243
237
|
// Override fetch date range from OO expiryHints when available
|
|
244
238
|
if (hasOOData) {
|
|
@@ -259,15 +253,13 @@ export async function handleReplayTrade(
|
|
|
259
253
|
quantity: hasOOData
|
|
260
254
|
? leg.quantity * (leg.contracts ?? 1)
|
|
261
255
|
: leg.quantity * (numContracts > 0 ? numContracts : 1),
|
|
262
|
-
entryPrice: hasOOData
|
|
263
|
-
? leg.entryPrice!
|
|
264
|
-
: perContractPremium / parsedLegs.length,
|
|
256
|
+
entryPrice: hasOOData ? leg.entryPrice! : perContractPremium / parsedLegs.length,
|
|
265
257
|
multiplier,
|
|
266
258
|
};
|
|
267
259
|
});
|
|
268
260
|
} else {
|
|
269
261
|
throw new Error(
|
|
270
|
-
"Provide either legs[] for hypothetical mode or block_id + trade_index for tradelog mode"
|
|
262
|
+
"Provide either legs[] for hypothetical mode or block_id + trade_index for tradelog mode",
|
|
271
263
|
);
|
|
272
264
|
}
|
|
273
265
|
|
|
@@ -313,7 +305,7 @@ export async function handleReplayTrade(
|
|
|
313
305
|
const barsByLeg: BarRow[][] = replayLegs.map((leg) => {
|
|
314
306
|
const quotes = quotesByOcc.get(leg.occTicker) ?? [];
|
|
315
307
|
return quotes.map((q) => {
|
|
316
|
-
const [date, time] = q.timestamp.split(
|
|
308
|
+
const [date, time] = q.timestamp.split(" ");
|
|
317
309
|
const mid = (q.bid + q.ask) / 2;
|
|
318
310
|
return {
|
|
319
311
|
ticker: q.occ_ticker,
|
|
@@ -333,15 +325,20 @@ export async function handleReplayTrade(
|
|
|
333
325
|
// ----- Fetch underlying bars + build greeks config -----
|
|
334
326
|
// Reverse-map weekly roots back to standard root for underlying fetch
|
|
335
327
|
const REVERSE_ROOT_MAP: Record<string, string> = {
|
|
336
|
-
SPXW:
|
|
328
|
+
SPXW: "SPX",
|
|
329
|
+
NDXP: "NDX",
|
|
330
|
+
RUTW: "RUT",
|
|
337
331
|
};
|
|
338
332
|
const DIVIDEND_YIELDS: Record<string, number> = {
|
|
339
|
-
SPX: 0.015,
|
|
333
|
+
SPX: 0.015,
|
|
334
|
+
SPXW: 0.015,
|
|
335
|
+
NDX: 0.015,
|
|
336
|
+
NDXP: 0.015,
|
|
340
337
|
};
|
|
341
338
|
|
|
342
339
|
// Extract root from first leg's OCC ticker
|
|
343
340
|
const firstRootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);
|
|
344
|
-
const rawRoot = firstRootMatch ? firstRootMatch[1] :
|
|
341
|
+
const rawRoot = firstRootMatch ? firstRootMatch[1] : "";
|
|
345
342
|
const underlyingTicker = REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;
|
|
346
343
|
const dividendYield = DIVIDEND_YIELDS[rawRoot] ?? 0;
|
|
347
344
|
|
|
@@ -356,11 +353,7 @@ export async function handleReplayTrade(
|
|
|
356
353
|
);
|
|
357
354
|
if (underlyingBars.length === 0) {
|
|
358
355
|
try {
|
|
359
|
-
underlyingBars = await stores.spot.readDailyBars(
|
|
360
|
-
underlyingTicker,
|
|
361
|
-
open_date!,
|
|
362
|
-
close_date!,
|
|
363
|
-
);
|
|
356
|
+
underlyingBars = await stores.spot.readDailyBars(underlyingTicker, open_date!, close_date!);
|
|
364
357
|
} catch {
|
|
365
358
|
// No fallback available — greeks will be omitted
|
|
366
359
|
}
|
|
@@ -375,17 +368,21 @@ export async function handleReplayTrade(
|
|
|
375
368
|
if (underlyingBars.length > 0) {
|
|
376
369
|
underlyingBars = underlyingBars.filter(
|
|
377
370
|
(b) =>
|
|
378
|
-
Number.isFinite(b.open) &&
|
|
379
|
-
|
|
380
|
-
Number.isFinite(b.
|
|
381
|
-
|
|
371
|
+
Number.isFinite(b.open) &&
|
|
372
|
+
b.open > 0 &&
|
|
373
|
+
Number.isFinite(b.high) &&
|
|
374
|
+
b.high > 0 &&
|
|
375
|
+
Number.isFinite(b.low) &&
|
|
376
|
+
b.low > 0 &&
|
|
377
|
+
Number.isFinite(b.close) &&
|
|
378
|
+
b.close > 0,
|
|
382
379
|
);
|
|
383
380
|
}
|
|
384
381
|
|
|
385
382
|
// Build underlying price map for greeks config
|
|
386
383
|
const underlyingPrices = new Map<string, number>();
|
|
387
384
|
for (const b of underlyingBars) {
|
|
388
|
-
const ts = `${b.date} ${b.time ??
|
|
385
|
+
const ts = `${b.date} ${b.time ?? ""}`.trim();
|
|
389
386
|
underlyingPrices.set(ts, markPrice(b));
|
|
390
387
|
}
|
|
391
388
|
|
|
@@ -394,7 +391,7 @@ export async function handleReplayTrade(
|
|
|
394
391
|
// (e.g. one source skipped a minute), greeks computation falls back to
|
|
395
392
|
// the nearest underlying timestamp within tolerance.
|
|
396
393
|
const sortedTimestamps = Array.from(underlyingPrices.keys())
|
|
397
|
-
.filter(k => k.includes(
|
|
394
|
+
.filter((k) => k.includes(" ")) // Only intraday timestamps, not date-only keys
|
|
398
395
|
.sort();
|
|
399
396
|
|
|
400
397
|
// VIX IVP lookup via EnrichedStore.read — used as an optional input to
|
|
@@ -427,12 +424,12 @@ export async function handleReplayTrade(
|
|
|
427
424
|
greeksConfig = {
|
|
428
425
|
underlyingPrices,
|
|
429
426
|
sortedTimestamps,
|
|
430
|
-
legs: replayLegs.map(leg => {
|
|
427
|
+
legs: replayLegs.map((leg) => {
|
|
431
428
|
// Extract strike, type, expiry from OCC ticker: ROOT{YYMMDD}{C|P}{strike*1000}
|
|
432
429
|
const occMatch = leg.occTicker.match(/^[A-Z]+(\d{6})([CP])(\d{8})$/);
|
|
433
|
-
if (!occMatch) return { strike: 0, type:
|
|
430
|
+
if (!occMatch) return { strike: 0, type: "C" as const, expiryDate: "" };
|
|
434
431
|
const yymmdd = occMatch[1];
|
|
435
|
-
const type = occMatch[2] as
|
|
432
|
+
const type = occMatch[2] as "C" | "P";
|
|
436
433
|
const strike = parseInt(occMatch[3], 10) / 1000;
|
|
437
434
|
const expiryDate = `20${yymmdd.slice(0, 2)}-${yymmdd.slice(2, 4)}-${yymmdd.slice(4, 6)}`;
|
|
438
435
|
return { strike, type, expiryDate };
|
|
@@ -445,8 +442,7 @@ export async function handleReplayTrade(
|
|
|
445
442
|
|
|
446
443
|
// ----- Compute P&L path + MFE/MAE -----
|
|
447
444
|
let fullPath = computeStrategyPnlPath(replayLegs, barsByLeg, greeksConfig);
|
|
448
|
-
let { mfe, mae, mfeTimestamp, maeTimestamp } =
|
|
449
|
-
computeReplayMfeMae(fullPath);
|
|
445
|
+
let { mfe, mae, mfeTimestamp, maeTimestamp } = computeReplayMfeMae(fullPath);
|
|
450
446
|
let totalPnl = fullPath.length > 0 ? fullPath[fullPath.length - 1].strategyPnl : 0;
|
|
451
447
|
|
|
452
448
|
// Surface a warning when >50% of leg-timestamps have null greeks — the
|
|
@@ -462,23 +458,30 @@ export async function handleReplayTrade(
|
|
|
462
458
|
}
|
|
463
459
|
}
|
|
464
460
|
}
|
|
465
|
-
const greeksWarning =
|
|
466
|
-
|
|
467
|
-
|
|
461
|
+
const greeksWarning =
|
|
462
|
+
greeksTotalCount > 0 && greeksNullCount / greeksTotalCount > 0.5
|
|
463
|
+
? `Greeks unavailable for ${greeksNullCount} of ${greeksTotalCount} leg-timestamps (0DTE options use Bachelier model; some legs may have insufficient time value for IV computation)`
|
|
464
|
+
: null;
|
|
468
465
|
|
|
469
466
|
// Apply format filter
|
|
470
467
|
// Truncate path at trade close timestamp when close_at === "trade" (default)
|
|
471
468
|
// This ensures decompose_greeks and exit triggers only analyze the actual holding period
|
|
472
469
|
if (close_at === "trade" && tradeCloseTimestamp && fullPath.length > 0) {
|
|
473
|
-
const truncIdx = fullPath.findIndex(p => p.timestamp > tradeCloseTimestamp!);
|
|
470
|
+
const truncIdx = fullPath.findIndex((p) => p.timestamp > tradeCloseTimestamp!);
|
|
474
471
|
if (truncIdx > 0) {
|
|
475
472
|
fullPath = fullPath.slice(0, truncIdx);
|
|
476
473
|
// Recompute MFE/MAE/totalPnl on truncated path
|
|
477
474
|
mfe = -Infinity;
|
|
478
475
|
mae = Infinity;
|
|
479
476
|
for (const p of fullPath) {
|
|
480
|
-
if (p.strategyPnl > mfe) {
|
|
481
|
-
|
|
477
|
+
if (p.strategyPnl > mfe) {
|
|
478
|
+
mfe = p.strategyPnl;
|
|
479
|
+
mfeTimestamp = p.timestamp;
|
|
480
|
+
}
|
|
481
|
+
if (p.strategyPnl < mae) {
|
|
482
|
+
mae = p.strategyPnl;
|
|
483
|
+
maeTimestamp = p.timestamp;
|
|
484
|
+
}
|
|
482
485
|
}
|
|
483
486
|
totalPnl = fullPath[fullPath.length - 1].strategyPnl;
|
|
484
487
|
}
|
|
@@ -494,12 +497,13 @@ export async function handleReplayTrade(
|
|
|
494
497
|
mfeTimestamp,
|
|
495
498
|
maeTimestamp,
|
|
496
499
|
]);
|
|
497
|
-
pnlPath = fullPath.filter(p => keyTimestamps.has(p.timestamp));
|
|
500
|
+
pnlPath = fullPath.filter((p) => keyTimestamps.has(p.timestamp));
|
|
498
501
|
} else if (format === "sampled") {
|
|
499
502
|
// Sample at ~15min intervals (keep every 15th bar, plus first/last/MFE/MAE)
|
|
500
503
|
const keyTimestamps = new Set([mfeTimestamp, maeTimestamp]);
|
|
501
|
-
pnlPath = fullPath.filter(
|
|
502
|
-
|
|
504
|
+
pnlPath = fullPath.filter(
|
|
505
|
+
(p, i) =>
|
|
506
|
+
i === 0 || i === fullPath.length - 1 || i % 15 === 0 || keyTimestamps.has(p.timestamp),
|
|
503
507
|
);
|
|
504
508
|
} else {
|
|
505
509
|
pnlPath = fullPath;
|
|
@@ -546,7 +550,7 @@ export function registerReplayTools(
|
|
|
546
550
|
const summary =
|
|
547
551
|
`Replayed ${result.legs.length}-leg strategy from ${params.open_date ?? "trade dates"} to ${params.close_date ?? "trade dates"}: ` +
|
|
548
552
|
`$${result.totalPnl.toFixed(2)} P&L, MFE=$${result.mfe.toFixed(2)}, MAE=$${result.mae.toFixed(2)}, ` +
|
|
549
|
-
`${result.pnlPath.length} minute bars, greeks=${result.pnlPath[0]?.legGreeks ?
|
|
553
|
+
`${result.pnlPath.length} minute bars, greeks=${result.pnlPath[0]?.legGreeks ? "yes" : "no"}`;
|
|
550
554
|
|
|
551
555
|
return createToolOutput(summary, result);
|
|
552
556
|
} catch (error) {
|
|
@@ -560,6 +564,6 @@ export function registerReplayTools(
|
|
|
560
564
|
isError: true,
|
|
561
565
|
};
|
|
562
566
|
}
|
|
563
|
-
}
|
|
567
|
+
},
|
|
564
568
|
);
|
|
565
569
|
}
|