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/dist/test-exports.js
CHANGED
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
resolveCanonicalMarketPartitionDir,
|
|
58
58
|
resolveCanonicalMarketPartitionFile,
|
|
59
59
|
resolveCanonicalMarketPartitionPath,
|
|
60
|
+
resolveDbPath,
|
|
60
61
|
resolveMarketDir,
|
|
61
62
|
resolveTradeTicker,
|
|
62
63
|
setDataRoot,
|
|
@@ -80,7 +81,7 @@ import {
|
|
|
80
81
|
writeParquetPartition,
|
|
81
82
|
writeQuoteMinutesPartition,
|
|
82
83
|
writeSpotPartition
|
|
83
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-4HZLVUOZ.js";
|
|
84
85
|
import {
|
|
85
86
|
BACHELIER_DTE_THRESHOLD,
|
|
86
87
|
IvSolverPool,
|
|
@@ -156,42 +157,54 @@ import {
|
|
|
156
157
|
stockHistoryOhlc,
|
|
157
158
|
thetaTimestampToEtMinute,
|
|
158
159
|
toMassiveTicker
|
|
159
|
-
} from "./chunk-
|
|
160
|
+
} from "./chunk-NWNSKALN.js";
|
|
160
161
|
import {
|
|
161
162
|
PortfolioStatsCalculator,
|
|
162
163
|
calculateCorrelationMatrix,
|
|
164
|
+
holdingPeriodBlockDays,
|
|
165
|
+
pairedBlockBootstrap,
|
|
163
166
|
performTailRiskAnalysis
|
|
164
|
-
} from "./chunk-
|
|
165
|
-
import "./chunk-
|
|
166
|
-
import "./chunk-
|
|
167
|
-
import "./chunk-
|
|
168
|
-
import "./chunk-
|
|
169
|
-
import "./chunk-
|
|
167
|
+
} from "./chunk-2I3S2ZLT.js";
|
|
168
|
+
import "./chunk-WNTYA7ER.js";
|
|
169
|
+
import "./chunk-ZTJXC3LS.js";
|
|
170
|
+
import "./chunk-YZA4RS76.js";
|
|
171
|
+
import "./chunk-27S67XW3.js";
|
|
172
|
+
import "./chunk-2E63THNI.js";
|
|
170
173
|
import "./chunk-5WRI5ZAA.js";
|
|
171
174
|
|
|
172
|
-
// src/
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
175
|
+
// src/tools/blocks/paired-comparison.ts
|
|
176
|
+
import { z } from "zod";
|
|
177
|
+
|
|
178
|
+
// src/utils/output-formatter.ts
|
|
179
|
+
function createToolOutput(summary, data) {
|
|
180
|
+
return {
|
|
181
|
+
content: [
|
|
182
|
+
{ type: "text", text: summary },
|
|
183
|
+
{
|
|
184
|
+
type: "resource",
|
|
185
|
+
resource: {
|
|
186
|
+
uri: "data:application/json",
|
|
187
|
+
mimeType: "application/json",
|
|
188
|
+
text: JSON.stringify(data)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function formatCurrency(value) {
|
|
195
|
+
const isNegative = value < 0;
|
|
196
|
+
const absValue = Math.abs(value);
|
|
197
|
+
const formatted = absValue.toLocaleString("en-US", {
|
|
198
|
+
minimumFractionDigits: 2,
|
|
199
|
+
maximumFractionDigits: 2
|
|
200
|
+
});
|
|
201
|
+
return isNegative ? `-$${formatted}` : `$${formatted}`;
|
|
187
202
|
}
|
|
188
203
|
|
|
189
204
|
// src/tools/shared/filters.ts
|
|
190
205
|
function filterByStrategy(trades, strategy) {
|
|
191
206
|
if (!strategy) return trades;
|
|
192
|
-
return trades.filter(
|
|
193
|
-
(t) => t.strategy.toLowerCase() === strategy.toLowerCase()
|
|
194
|
-
);
|
|
207
|
+
return trades.filter((t) => t.strategy.toLowerCase() === strategy.toLowerCase());
|
|
195
208
|
}
|
|
196
209
|
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
197
210
|
function validateDateParam(date) {
|
|
@@ -234,6 +247,294 @@ function filterDailyLogsByDateRange(dailyLogs, startDate, endDate) {
|
|
|
234
247
|
return filtered;
|
|
235
248
|
}
|
|
236
249
|
|
|
250
|
+
// src/tools/middleware/sync-middleware.ts
|
|
251
|
+
function withSyncedBlock(baseDir, handler) {
|
|
252
|
+
return async (input) => {
|
|
253
|
+
await upgradeToReadWrite(baseDir, { fallbackToReadOnly: true });
|
|
254
|
+
let syncResult;
|
|
255
|
+
if (getConnectionMode() === "read_write") {
|
|
256
|
+
try {
|
|
257
|
+
syncResult = await syncBlock(input.blockId, baseDir);
|
|
258
|
+
} finally {
|
|
259
|
+
await downgradeToReadOnly(baseDir);
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
syncResult = { blockId: input.blockId, status: "unchanged" };
|
|
263
|
+
}
|
|
264
|
+
if (syncResult.status === "deleted") {
|
|
265
|
+
return {
|
|
266
|
+
content: [
|
|
267
|
+
{
|
|
268
|
+
type: "text",
|
|
269
|
+
text: `Block '${input.blockId}' no longer exists (folder was deleted). Call list_blocks to see available blocks.`
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
isError: true
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (syncResult.status === "error" && syncResult.error) {
|
|
276
|
+
return {
|
|
277
|
+
content: [
|
|
278
|
+
{
|
|
279
|
+
type: "text",
|
|
280
|
+
text: `Sync error for block '${input.blockId}': ${syncResult.error}. Call list_blocks to see available blocks.`
|
|
281
|
+
}
|
|
282
|
+
],
|
|
283
|
+
isError: true
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return handler(input, { syncResult, baseDir });
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/tools/blocks/paired-comparison.ts
|
|
291
|
+
var ATTRIBUTION_NOTE = "A trade's P&L is attributed evenly across the trading days it was open (dateOpened through dateClosed). Trading days are derived from the block's own trade data (the union of every trade's open and close dates) -- no market calendar is assumed, so weekends or holidays appear only if a trade spans them.";
|
|
292
|
+
var DEFAULT_CI_LEVEL = 0.95;
|
|
293
|
+
var DEFAULT_RESAMPLES = 2e3;
|
|
294
|
+
var DEFAULT_SEED = 42;
|
|
295
|
+
var SENSITIVITY_MULTIPLIERS = [0.5, 2];
|
|
296
|
+
var pairedComparisonInputSchema = z.object({
|
|
297
|
+
blockId: z.string().describe("Block folder name to analyze"),
|
|
298
|
+
strategyA: z.string().describe("Strategy name for arm A (case-insensitive)"),
|
|
299
|
+
strategyB: z.string().optional().describe(
|
|
300
|
+
"Strategy name for arm B (case-insensitive). If omitted, arm A is compared against a constant 0 -- i.e. is the edge distinguishable from nothing."
|
|
301
|
+
),
|
|
302
|
+
statistic: z.enum(["mean_daily_pnl", "median_daily_pnl"]).default("mean_daily_pnl").describe("Which daily-P&L functional to compare. Default mean_daily_pnl."),
|
|
303
|
+
dateRange: z.object({
|
|
304
|
+
start: z.string().describe("Start date (YYYY-MM-DD)"),
|
|
305
|
+
end: z.string().describe("End date (YYYY-MM-DD)")
|
|
306
|
+
}).optional().describe("Optional date range filter applied to both arms (by trade open date)."),
|
|
307
|
+
ciLevel: z.number().default(DEFAULT_CI_LEVEL).describe("Confidence level in (0, 1). Default 0.95 (deterministic)."),
|
|
308
|
+
resamples: z.number().int().default(DEFAULT_RESAMPLES).describe("Number of bootstrap resamples. Default 2000 (deterministic)."),
|
|
309
|
+
seed: z.number().int().default(DEFAULT_SEED).describe("PRNG seed. Same inputs + seed -> identical result. Default 42 (deterministic)."),
|
|
310
|
+
blockDays: z.number().int().min(1).optional().describe(
|
|
311
|
+
"Override the block length. By default it is derived from this block's own holding-period distribution (95th percentile of days-open)."
|
|
312
|
+
),
|
|
313
|
+
effectiveNFloorBlocks: z.number().min(0).optional().describe(
|
|
314
|
+
"Optional: refuse with notComparable when the shared overlap yields fewer distinct blocks than this floor. When omitted, only structural degeneracy refuses (underpowered)."
|
|
315
|
+
)
|
|
316
|
+
});
|
|
317
|
+
function toCalendarDateStr2(date) {
|
|
318
|
+
const y = date.getUTCFullYear();
|
|
319
|
+
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
320
|
+
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
321
|
+
return `${y}-${m}-${d}`;
|
|
322
|
+
}
|
|
323
|
+
function buildBlockTradingDayIndex(trades) {
|
|
324
|
+
const days = /* @__PURE__ */ new Set();
|
|
325
|
+
for (const t of trades) {
|
|
326
|
+
days.add(toCalendarDateStr2(t.dateOpened));
|
|
327
|
+
days.add(toCalendarDateStr2(t.dateClosed ?? t.dateOpened));
|
|
328
|
+
}
|
|
329
|
+
return Array.from(days).sort();
|
|
330
|
+
}
|
|
331
|
+
function gridSpan(grid, openStr, closeStr) {
|
|
332
|
+
let lo = grid.findIndex((d) => d >= openStr);
|
|
333
|
+
if (lo === -1) lo = grid.length;
|
|
334
|
+
let hi = -1;
|
|
335
|
+
for (let i = grid.length - 1; i >= 0; i--) {
|
|
336
|
+
if (grid[i] <= closeStr) {
|
|
337
|
+
hi = i;
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return [lo, hi];
|
|
342
|
+
}
|
|
343
|
+
function armHoldingPeriods(armTrades, grid) {
|
|
344
|
+
return armTrades.map((t) => {
|
|
345
|
+
const openStr = toCalendarDateStr2(t.dateOpened);
|
|
346
|
+
const closeStr = toCalendarDateStr2(t.dateClosed ?? t.dateOpened);
|
|
347
|
+
const [lo, hi] = gridSpan(grid, openStr, closeStr);
|
|
348
|
+
return Math.max(1, hi - lo + 1);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
function buildArmDaySeries(armTrades, grid) {
|
|
352
|
+
const values = new Array(grid.length).fill(0);
|
|
353
|
+
const observedMask = new Array(grid.length).fill(false);
|
|
354
|
+
for (const t of armTrades) {
|
|
355
|
+
const openStr = toCalendarDateStr2(t.dateOpened);
|
|
356
|
+
const closeStr = toCalendarDateStr2(t.dateClosed ?? t.dateOpened);
|
|
357
|
+
const [lo, hi] = gridSpan(grid, openStr, closeStr);
|
|
358
|
+
if (hi < lo) continue;
|
|
359
|
+
const span = hi - lo + 1;
|
|
360
|
+
const perDay = t.pl / span;
|
|
361
|
+
for (let i = lo; i <= hi; i++) {
|
|
362
|
+
values[i] += perDay;
|
|
363
|
+
observedMask[i] = true;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return { index: grid, values, observedMask };
|
|
367
|
+
}
|
|
368
|
+
function countObserved(series) {
|
|
369
|
+
return series.observedMask.reduce((n, o) => n + (o ? 1 : 0), 0);
|
|
370
|
+
}
|
|
371
|
+
function meanOf(values) {
|
|
372
|
+
return values.reduce((s, v) => s + v, 0) / values.length;
|
|
373
|
+
}
|
|
374
|
+
function medianOf(values) {
|
|
375
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
376
|
+
const mid = Math.floor(sorted.length / 2);
|
|
377
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
378
|
+
}
|
|
379
|
+
var STATISTIC_FNS = {
|
|
380
|
+
mean_daily_pnl: meanOf,
|
|
381
|
+
median_daily_pnl: medianOf
|
|
382
|
+
};
|
|
383
|
+
var STATISTIC_LABELS = {
|
|
384
|
+
mean_daily_pnl: "mean daily P&L",
|
|
385
|
+
median_daily_pnl: "median daily P&L"
|
|
386
|
+
};
|
|
387
|
+
function runPairedBootstrapComparison(params) {
|
|
388
|
+
const {
|
|
389
|
+
armATrades,
|
|
390
|
+
armBTrades,
|
|
391
|
+
blockTrades,
|
|
392
|
+
statistic,
|
|
393
|
+
ciLevel,
|
|
394
|
+
resamples,
|
|
395
|
+
seed,
|
|
396
|
+
blockDays: blockDaysOverride,
|
|
397
|
+
effectiveNFloorBlocks,
|
|
398
|
+
strategyA,
|
|
399
|
+
strategyB
|
|
400
|
+
} = params;
|
|
401
|
+
if (armATrades.length === 0) {
|
|
402
|
+
throw new Error(`No trades found for strategy "${strategyA}".`);
|
|
403
|
+
}
|
|
404
|
+
if (armBTrades !== null && armBTrades.length === 0) {
|
|
405
|
+
throw new Error(`No trades found for strategy "${strategyB}".`);
|
|
406
|
+
}
|
|
407
|
+
const grid = buildBlockTradingDayIndex(blockTrades);
|
|
408
|
+
const armA = buildArmDaySeries(armATrades, grid);
|
|
409
|
+
const armB = armBTrades !== null ? buildArmDaySeries(armBTrades, grid) : null;
|
|
410
|
+
const hpA = holdingPeriodBlockDays(armHoldingPeriods(armATrades, grid));
|
|
411
|
+
const hpB = armBTrades !== null ? holdingPeriodBlockDays(armHoldingPeriods(armBTrades, grid)) : hpA;
|
|
412
|
+
const derivedBlockDays = Math.max(hpA, hpB);
|
|
413
|
+
const derivedFromArm = hpB > hpA ? "B" : "A";
|
|
414
|
+
const blockDays = blockDaysOverride ?? derivedBlockDays;
|
|
415
|
+
const blockDaysNote = blockDaysOverride !== void 0 ? `block length = ${blockDays} trading day(s), caller-supplied override` : armB !== null ? `block length = ${blockDays} trading day(s), derived from the 95th-percentile holding period of arm ${derivedFromArm} (the longer-held arm -- the conservative choice)` : `block length = ${blockDays} trading day(s), derived from the 95th-percentile holding period of arm A`;
|
|
416
|
+
const result = pairedBlockBootstrap({
|
|
417
|
+
armA,
|
|
418
|
+
armB: armB ?? { constant: 0 },
|
|
419
|
+
statistic: STATISTIC_FNS[statistic],
|
|
420
|
+
holdingRule: { blockDays, sensitivity: SENSITIVITY_MULTIPLIERS },
|
|
421
|
+
ciLevel,
|
|
422
|
+
resamples,
|
|
423
|
+
seed,
|
|
424
|
+
effectiveNFloorBlocks
|
|
425
|
+
});
|
|
426
|
+
const statLabel = STATISTIC_LABELS[statistic];
|
|
427
|
+
const comparisonLabel = armB !== null ? `${strategyA} minus ${strategyB}` : `${strategyA} vs 0`;
|
|
428
|
+
let summary;
|
|
429
|
+
let refusalMessage = null;
|
|
430
|
+
if (result.status === "notComparable") {
|
|
431
|
+
refusalMessage = `Not comparable: effective sample (${result.effectiveN.toFixed(2)} blocks) is below the required power floor. A confidence interval is withheld.`;
|
|
432
|
+
summary = `Paired comparison (${comparisonLabel}): ${refusalMessage}`;
|
|
433
|
+
} else if (result.status === "underpowered") {
|
|
434
|
+
refusalMessage = `Underpowered: the shared overlap is too short to resample at a block length of ${blockDays} trading day(s) (fewer than two drawable blocks). A confidence interval is withheld.`;
|
|
435
|
+
summary = `Paired comparison (${comparisonLabel}): ${refusalMessage}`;
|
|
436
|
+
} else {
|
|
437
|
+
const point = result.point ?? 0;
|
|
438
|
+
const low = result.ci.low ?? 0;
|
|
439
|
+
const high = result.ci.high ?? 0;
|
|
440
|
+
const includesZero = low <= 0 && high >= 0;
|
|
441
|
+
const zeroClause = includesZero ? "includes zero (not distinguishable)" : "excludes zero (distinguishable)";
|
|
442
|
+
const subject = armB !== null ? `the difference in ${statLabel} (${comparisonLabel})` : `${statLabel}`;
|
|
443
|
+
summary = `Paired comparison: ${subject} is ${formatCurrency(point)}, ${(ciLevel * 100).toFixed(0)}% CI [${formatCurrency(low)}, ${formatCurrency(high)}] -- ${zeroClause}.`;
|
|
444
|
+
}
|
|
445
|
+
const data = {
|
|
446
|
+
comparison: {
|
|
447
|
+
strategyA,
|
|
448
|
+
strategyB: strategyB ?? null,
|
|
449
|
+
mode: armB !== null ? "two-arm" : "single-arm-vs-zero",
|
|
450
|
+
description: comparisonLabel
|
|
451
|
+
},
|
|
452
|
+
statistic,
|
|
453
|
+
point: result.point,
|
|
454
|
+
ci: result.ci,
|
|
455
|
+
status: result.status,
|
|
456
|
+
refusal: refusalMessage,
|
|
457
|
+
effectiveN: result.effectiveN,
|
|
458
|
+
blockDays: result.blockDays,
|
|
459
|
+
blockDaysDerivation: blockDaysNote,
|
|
460
|
+
overlapWindow: result.overlapWindow,
|
|
461
|
+
observedDays: {
|
|
462
|
+
armA: countObserved(armA),
|
|
463
|
+
armB: armB !== null ? countObserved(armB) : null
|
|
464
|
+
},
|
|
465
|
+
tradeCounts: {
|
|
466
|
+
armA: armATrades.length,
|
|
467
|
+
armB: armBTrades !== null ? armBTrades.length : null
|
|
468
|
+
},
|
|
469
|
+
sensitivity: result.sensitivity,
|
|
470
|
+
resamples,
|
|
471
|
+
seed: result.seed,
|
|
472
|
+
ciLevel,
|
|
473
|
+
attributionMethodology: ATTRIBUTION_NOTE
|
|
474
|
+
};
|
|
475
|
+
return { summary, data, result };
|
|
476
|
+
}
|
|
477
|
+
function registerPairedComparisonTool(server, baseDir) {
|
|
478
|
+
server.registerTool(
|
|
479
|
+
"paired_bootstrap_comparison",
|
|
480
|
+
{
|
|
481
|
+
description: "Honest confidence intervals for 'is strategy A actually different from strategy B (or from zero)' -- day-block resampling that accounts for multi-day positions, dormant periods, and paired comparison on shared days. " + ATTRIBUTION_NOTE,
|
|
482
|
+
inputSchema: pairedComparisonInputSchema
|
|
483
|
+
},
|
|
484
|
+
withSyncedBlock(baseDir, async (input) => {
|
|
485
|
+
try {
|
|
486
|
+
const { blockId, strategyA, strategyB, statistic, dateRange } = input;
|
|
487
|
+
const block = await loadBlock(baseDir, blockId);
|
|
488
|
+
const inRange = (trades) => dateRange ? filterByDateRange(trades, dateRange.start, dateRange.end) : trades;
|
|
489
|
+
const blockTrades = inRange(block.trades);
|
|
490
|
+
const armATrades = inRange(filterByStrategy(block.trades, strategyA));
|
|
491
|
+
const armBTrades = strategyB !== void 0 ? inRange(filterByStrategy(block.trades, strategyB)) : null;
|
|
492
|
+
const { summary, data } = runPairedBootstrapComparison({
|
|
493
|
+
armATrades,
|
|
494
|
+
armBTrades,
|
|
495
|
+
blockTrades,
|
|
496
|
+
statistic,
|
|
497
|
+
ciLevel: input.ciLevel,
|
|
498
|
+
resamples: input.resamples,
|
|
499
|
+
seed: input.seed,
|
|
500
|
+
blockDays: input.blockDays,
|
|
501
|
+
effectiveNFloorBlocks: input.effectiveNFloorBlocks,
|
|
502
|
+
strategyA,
|
|
503
|
+
strategyB
|
|
504
|
+
});
|
|
505
|
+
return createToolOutput(summary, { blockId, ...data });
|
|
506
|
+
} catch (error) {
|
|
507
|
+
return {
|
|
508
|
+
content: [
|
|
509
|
+
{
|
|
510
|
+
type: "text",
|
|
511
|
+
text: `Error running paired bootstrap comparison: ${error.message}`
|
|
512
|
+
}
|
|
513
|
+
],
|
|
514
|
+
isError: true
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
})
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/utils/trading-dates.ts
|
|
522
|
+
function yesterdayET(now = /* @__PURE__ */ new Date()) {
|
|
523
|
+
const todayET = new Intl.DateTimeFormat("en-CA", {
|
|
524
|
+
timeZone: "America/New_York",
|
|
525
|
+
year: "numeric",
|
|
526
|
+
month: "2-digit",
|
|
527
|
+
day: "2-digit"
|
|
528
|
+
}).format(now);
|
|
529
|
+
const [y, m, d] = todayET.split("-").map(Number);
|
|
530
|
+
const prior = new Date(Date.UTC(y, m - 1, d));
|
|
531
|
+
prior.setUTCDate(prior.getUTCDate() - 1);
|
|
532
|
+
const py = prior.getUTCFullYear();
|
|
533
|
+
const pm = String(prior.getUTCMonth() + 1).padStart(2, "0");
|
|
534
|
+
const pd = String(prior.getUTCDate()).padStart(2, "0");
|
|
535
|
+
return `${py}-${pm}-${pd}`;
|
|
536
|
+
}
|
|
537
|
+
|
|
237
538
|
// src/utils/schema-metadata.ts
|
|
238
539
|
var SCHEMA_DESCRIPTIONS = {
|
|
239
540
|
trades: {
|
|
@@ -798,10 +1099,22 @@ var VIX_OHLCV_MAPPINGS = [
|
|
|
798
1099
|
{ alias: "VIX_Low", tableAlias: "vix", sourceCol: "low", ticker: "VIX", timing: "close" },
|
|
799
1100
|
// VIX9D
|
|
800
1101
|
{ alias: "VIX9D_Open", tableAlias: "vix9d", sourceCol: "open", ticker: "VIX9D", timing: "open" },
|
|
801
|
-
{
|
|
1102
|
+
{
|
|
1103
|
+
alias: "VIX9D_Close",
|
|
1104
|
+
tableAlias: "vix9d",
|
|
1105
|
+
sourceCol: "close",
|
|
1106
|
+
ticker: "VIX9D",
|
|
1107
|
+
timing: "close"
|
|
1108
|
+
},
|
|
802
1109
|
// VIX3M
|
|
803
1110
|
{ alias: "VIX3M_Open", tableAlias: "vix3m", sourceCol: "open", ticker: "VIX3M", timing: "open" },
|
|
804
|
-
{
|
|
1111
|
+
{
|
|
1112
|
+
alias: "VIX3M_Close",
|
|
1113
|
+
tableAlias: "vix3m",
|
|
1114
|
+
sourceCol: "close",
|
|
1115
|
+
ticker: "VIX3M",
|
|
1116
|
+
timing: "close"
|
|
1117
|
+
}
|
|
805
1118
|
];
|
|
806
1119
|
var VIX_ENRICHED_MAPPINGS = [
|
|
807
1120
|
{ alias: "VIX_IVR", tableAlias: "evix", sourceCol: "ivr", ticker: "VIX", timing: "close" },
|
|
@@ -811,10 +1124,7 @@ var VIX_ENRICHED_MAPPINGS = [
|
|
|
811
1124
|
{ alias: "VIX3M_IVR", tableAlias: "evix3m", sourceCol: "ivr", ticker: "VIX3M", timing: "close" },
|
|
812
1125
|
{ alias: "VIX3M_IVP", tableAlias: "evix3m", sourceCol: "ivp", ticker: "VIX3M", timing: "close" }
|
|
813
1126
|
];
|
|
814
|
-
var VIX_ALL_MAPPINGS = [
|
|
815
|
-
...VIX_OHLCV_MAPPINGS,
|
|
816
|
-
...VIX_ENRICHED_MAPPINGS
|
|
817
|
-
];
|
|
1127
|
+
var VIX_ALL_MAPPINGS = [...VIX_OHLCV_MAPPINGS, ...VIX_ENRICHED_MAPPINGS];
|
|
818
1128
|
var VIX_TICKER_ALIASES = [...new Set(VIX_OHLCV_MAPPINGS.map((m) => m.tableAlias))];
|
|
819
1129
|
var VIX_TICKER_FOR_ALIAS = Object.fromEntries([
|
|
820
1130
|
...VIX_OHLCV_MAPPINGS.map((m) => [m.tableAlias, m.ticker]),
|
|
@@ -851,9 +1161,7 @@ var CLOSE_KNOWN_FIELDS = /* @__PURE__ */ new Set([
|
|
|
851
1161
|
...DAILY_CLOSE_FIELDS,
|
|
852
1162
|
...CONTEXT_CLOSE_FIELDS
|
|
853
1163
|
]);
|
|
854
|
-
var STATIC_FIELDS = /* @__PURE__ */ new Set([
|
|
855
|
-
...DAILY_STATIC_FIELDS
|
|
856
|
-
]);
|
|
1164
|
+
var STATIC_FIELDS = /* @__PURE__ */ new Set([...DAILY_STATIC_FIELDS]);
|
|
857
1165
|
function buildVixJoinClause(tickerAliases, baseAlias = "d") {
|
|
858
1166
|
return tickerAliases.flatMap((alias) => {
|
|
859
1167
|
const ticker = VIX_TICKER_FOR_ALIAS[alias];
|
|
@@ -978,7 +1286,11 @@ function buildOutcomeQuery(tradeDatesOrKeys) {
|
|
|
978
1286
|
if (typeof tradeDatesOrKeys[0] === "string") {
|
|
979
1287
|
return buildOutcomeQueryForDates(tradeDatesOrKeys, vixCloseCols, derivedCloseCols);
|
|
980
1288
|
}
|
|
981
|
-
return buildOutcomeQueryForKeys(
|
|
1289
|
+
return buildOutcomeQueryForKeys(
|
|
1290
|
+
tradeDatesOrKeys,
|
|
1291
|
+
vixCloseCols,
|
|
1292
|
+
derivedCloseCols
|
|
1293
|
+
);
|
|
982
1294
|
}
|
|
983
1295
|
function buildTargetCloseCols(eAlias, sAlias) {
|
|
984
1296
|
return [...DAILY_CLOSE_FIELDS].map((f) => `${OHLCV_COLS.has(f) ? sAlias : eAlias}."${f}"`).join(", ");
|
|
@@ -1098,11 +1410,11 @@ function scoreDataQuality(input) {
|
|
|
1098
1410
|
async function queryCoverage(stores, underlying, fromDate, toDate) {
|
|
1099
1411
|
const spotCov = await stores.spot.getCoverage(underlying, fromDate, toDate);
|
|
1100
1412
|
const quoteCov = await stores.quote.getCoverage(underlying, fromDate, toDate);
|
|
1101
|
-
const dates = enumerateCoveredDates(spotCov.earliest, spotCov.latest).concat(
|
|
1102
|
-
const uniqueDates = [...new Set(dates)].sort();
|
|
1103
|
-
const quoteDateSet = new Set(
|
|
1413
|
+
const dates = enumerateCoveredDates(spotCov.earliest, spotCov.latest).concat(
|
|
1104
1414
|
enumerateCoveredDates(quoteCov.earliest, quoteCov.latest)
|
|
1105
1415
|
);
|
|
1416
|
+
const uniqueDates = [...new Set(dates)].sort();
|
|
1417
|
+
const quoteDateSet = new Set(enumerateCoveredDates(quoteCov.earliest, quoteCov.latest));
|
|
1106
1418
|
if (uniqueDates.length === 0) {
|
|
1107
1419
|
return {
|
|
1108
1420
|
totalBars: 0,
|
|
@@ -1168,9 +1480,7 @@ function formatCoverageReport(tickerPattern, coverage) {
|
|
|
1168
1480
|
}
|
|
1169
1481
|
if (current) groups.push(current);
|
|
1170
1482
|
for (const group of groups) {
|
|
1171
|
-
const avgBars = group.barCounts.length > 0 ? Math.round(
|
|
1172
|
-
group.barCounts.reduce((s, v) => s + v, 0) / group.barCounts.length
|
|
1173
|
-
) : 0;
|
|
1483
|
+
const avgBars = group.barCounts.length > 0 ? Math.round(group.barCounts.reduce((s, v) => s + v, 0) / group.barCounts.length) : 0;
|
|
1174
1484
|
const rangeStr = group.fromDate === group.toDate ? group.fromDate : `${group.fromDate} to ${group.toDate}`;
|
|
1175
1485
|
let detail;
|
|
1176
1486
|
if (group.density === "dense") {
|
|
@@ -1445,7 +1755,9 @@ async function setupParquetWorkingTables(conn, dataDir) {
|
|
|
1445
1755
|
const enrichedTickerGlob = path.join(enrichedDir, "ticker=*", "data.parquet");
|
|
1446
1756
|
const enrichedContextPath = path.join(enrichedDir, "context", "data.parquet");
|
|
1447
1757
|
if (existsSync(dailyPath)) {
|
|
1448
|
-
await conn.run(
|
|
1758
|
+
await conn.run(
|
|
1759
|
+
`CREATE TEMP TABLE "${dailyTable}" AS SELECT * FROM read_parquet('${dailyPath}')`
|
|
1760
|
+
);
|
|
1449
1761
|
await alignDailyWorkingTableColumns(conn, dailyTable);
|
|
1450
1762
|
} else if (hasEnrichedTickerFiles(enrichedDir)) {
|
|
1451
1763
|
await conn.run(
|
|
@@ -1460,9 +1772,7 @@ async function setupParquetWorkingTables(conn, dataDir) {
|
|
|
1460
1772
|
}
|
|
1461
1773
|
await alignDailyWorkingTableColumns(conn, dailyTable);
|
|
1462
1774
|
} else {
|
|
1463
|
-
await conn.run(
|
|
1464
|
-
`CREATE TEMP TABLE "${dailyTable}" AS SELECT * FROM market.enriched WHERE 1=0`
|
|
1465
|
-
);
|
|
1775
|
+
await conn.run(`CREATE TEMP TABLE "${dailyTable}" AS SELECT * FROM market.enriched WHERE 1=0`);
|
|
1466
1776
|
for (const ohlcv of ["open", "high", "low", "close"]) {
|
|
1467
1777
|
try {
|
|
1468
1778
|
await conn.run(`ALTER TABLE "${dailyTable}" ADD COLUMN "${ohlcv}" DOUBLE`);
|
|
@@ -1485,7 +1795,9 @@ async function setupParquetWorkingTables(conn, dataDir) {
|
|
|
1485
1795
|
} catch {
|
|
1486
1796
|
}
|
|
1487
1797
|
if (existsSync(dateContextPath)) {
|
|
1488
|
-
await conn.run(
|
|
1798
|
+
await conn.run(
|
|
1799
|
+
`CREATE TEMP TABLE "${dateContextTable}" AS SELECT * FROM read_parquet('${dateContextPath}')`
|
|
1800
|
+
);
|
|
1489
1801
|
} else if (existsSync(enrichedContextPath)) {
|
|
1490
1802
|
await conn.run(
|
|
1491
1803
|
`CREATE TEMP TABLE "${dateContextTable}" AS SELECT * FROM read_parquet('${enrichedContextPath}')`
|
|
@@ -1496,7 +1808,9 @@ async function setupParquetWorkingTables(conn, dataDir) {
|
|
|
1496
1808
|
Trend_Direction VARCHAR, VIX_Spike_Pct DOUBLE, VIX_Gap_Pct DOUBLE
|
|
1497
1809
|
)`);
|
|
1498
1810
|
}
|
|
1499
|
-
await conn.run(
|
|
1811
|
+
await conn.run(
|
|
1812
|
+
`CREATE UNIQUE INDEX "idx_${dateContextTable}_date" ON "${dateContextTable}"(date)`
|
|
1813
|
+
);
|
|
1500
1814
|
await conn.run(`CREATE UNIQUE INDEX "idx_${dailyTable}_pk" ON "${dailyTable}"(ticker, date)`);
|
|
1501
1815
|
return { dailyTable, dateContextTable };
|
|
1502
1816
|
}
|
|
@@ -1523,12 +1837,7 @@ async function flushEnrichedToParquet(conn, dataDir, ticker, tables) {
|
|
|
1523
1837
|
targetPath: tickerFile,
|
|
1524
1838
|
selectQuery: `SELECT ticker, date, ${enrichedColList} FROM "${tables.dailyTable}" WHERE ticker = '${ticker}' ORDER BY date`
|
|
1525
1839
|
});
|
|
1526
|
-
const contextFile = path.join(
|
|
1527
|
-
resolveMarketDir(dataDir),
|
|
1528
|
-
"enriched",
|
|
1529
|
-
"context",
|
|
1530
|
-
"data.parquet"
|
|
1531
|
-
);
|
|
1840
|
+
const contextFile = path.join(resolveMarketDir(dataDir), "enriched", "context", "data.parquet");
|
|
1532
1841
|
await writeParquetAtomic(conn, {
|
|
1533
1842
|
targetPath: contextFile,
|
|
1534
1843
|
selectQuery: `SELECT * FROM "${tables.dateContextTable}" ORDER BY date`
|
|
@@ -1564,7 +1873,9 @@ async function runTier2(conn, targets, spotStore) {
|
|
|
1564
1873
|
const BATCH_SIZE = 500;
|
|
1565
1874
|
for (let start = 0; start < bars.length; start += BATCH_SIZE) {
|
|
1566
1875
|
const batch = bars.slice(start, start + BATCH_SIZE);
|
|
1567
|
-
const placeholders = batch.map(
|
|
1876
|
+
const placeholders = batch.map(
|
|
1877
|
+
(_, i) => `($${i * 6 + 1},$${i * 6 + 2},$${i * 6 + 3},$${i * 6 + 4},$${i * 6 + 5},$${i * 6 + 6})`
|
|
1878
|
+
).join(",");
|
|
1568
1879
|
const params = batch.flatMap((b) => [b.ticker, b.date, b.open, b.high, b.low, b.close]);
|
|
1569
1880
|
await conn.run(
|
|
1570
1881
|
`INSERT INTO "${vixTempTable}" VALUES ${placeholders}`,
|
|
@@ -1696,12 +2007,21 @@ async function runTier2(conn, targets, spotStore) {
|
|
|
1696
2007
|
};
|
|
1697
2008
|
});
|
|
1698
2009
|
const enrichedContext = computeVIXDerivedFields(contextRows);
|
|
1699
|
-
const derivedCols = [
|
|
2010
|
+
const derivedCols = [
|
|
2011
|
+
"date",
|
|
2012
|
+
"Vol_Regime",
|
|
2013
|
+
"Term_Structure_State",
|
|
2014
|
+
"Trend_Direction",
|
|
2015
|
+
"VIX_Spike_Pct",
|
|
2016
|
+
"VIX_Gap_Pct"
|
|
2017
|
+
];
|
|
1700
2018
|
const BATCH_SIZE = 500;
|
|
1701
2019
|
for (let start = 0; start < enrichedContext.length; start += BATCH_SIZE) {
|
|
1702
2020
|
const batch = enrichedContext.slice(start, start + BATCH_SIZE);
|
|
1703
2021
|
const placeholders = batch.map((_, rowIdx) => {
|
|
1704
|
-
const params2 = derivedCols.map(
|
|
2022
|
+
const params2 = derivedCols.map(
|
|
2023
|
+
(__, colIdx) => `$${rowIdx * derivedCols.length + colIdx + 1}`
|
|
2024
|
+
);
|
|
1705
2025
|
return `(${params2.join(", ")})`;
|
|
1706
2026
|
}).join(", ");
|
|
1707
2027
|
const sql = `INSERT OR REPLACE INTO ${dateContextTarget} (${derivedCols.join(", ")}) VALUES ${placeholders}`;
|
|
@@ -1735,10 +2055,9 @@ async function hasTier3Data(conn, ticker, spotStore) {
|
|
|
1735
2055
|
const cov = await spotStore.getCoverage(ticker, "1970-01-01", "9999-12-31");
|
|
1736
2056
|
return cov.totalDates > 0;
|
|
1737
2057
|
}
|
|
1738
|
-
const r = await conn.runAndReadAll(
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
);
|
|
2058
|
+
const r = await conn.runAndReadAll(`SELECT COUNT(*) FROM market.spot WHERE ticker = $1 LIMIT 1`, [
|
|
2059
|
+
ticker
|
|
2060
|
+
]);
|
|
1742
2061
|
return Number(r.getRows()[0]?.[0] ?? 0) > 0;
|
|
1743
2062
|
}
|
|
1744
2063
|
async function runContextEnrichment(conn, targets) {
|
|
@@ -1778,7 +2097,10 @@ async function runEnrichment(conn, ticker, opts = {}, io) {
|
|
|
1778
2097
|
fetchParams.push(lookbackStart);
|
|
1779
2098
|
}
|
|
1780
2099
|
fetchSql += ` ORDER BY date ASC`;
|
|
1781
|
-
const rawReader = await conn.runAndReadAll(
|
|
2100
|
+
const rawReader = await conn.runAndReadAll(
|
|
2101
|
+
fetchSql,
|
|
2102
|
+
fetchParams
|
|
2103
|
+
);
|
|
1782
2104
|
rawRows = rawReader.getRows();
|
|
1783
2105
|
}
|
|
1784
2106
|
if (rawRows.length === 0) {
|
|
@@ -1995,7 +2317,8 @@ function computeIntradayTimingFields(bars) {
|
|
|
1995
2317
|
const highInAfternoon = highTime >= 12;
|
|
1996
2318
|
const lowInAfternoon = lowTime >= 12;
|
|
1997
2319
|
let reversalType = 0;
|
|
1998
|
-
if (highInMorning && lowInAfternoon)
|
|
2320
|
+
if (highInMorning && lowInAfternoon)
|
|
2321
|
+
reversalType = 1;
|
|
1999
2322
|
else if (lowInMorning && highInAfternoon) reversalType = -1;
|
|
2000
2323
|
const openingBars = bars.filter((b) => hhmmToDecimalHours(b.time) < 10);
|
|
2001
2324
|
let openingDriveStrength = 0;
|
|
@@ -2020,7 +2343,14 @@ function computeIntradayTimingFields(bars) {
|
|
|
2020
2343
|
intradayRealizedVol = barStdDev * Math.sqrt(bars.length * 252);
|
|
2021
2344
|
}
|
|
2022
2345
|
}
|
|
2023
|
-
return {
|
|
2346
|
+
return {
|
|
2347
|
+
highTime,
|
|
2348
|
+
lowTime,
|
|
2349
|
+
highBeforeLow,
|
|
2350
|
+
reversalType,
|
|
2351
|
+
openingDriveStrength,
|
|
2352
|
+
intradayRealizedVol
|
|
2353
|
+
};
|
|
2024
2354
|
}
|
|
2025
2355
|
async function runTier3(conn, ticker, dates, dailyTarget = "market.enriched", spotStore) {
|
|
2026
2356
|
const hasData = await hasTier3Data(conn, ticker, spotStore);
|
|
@@ -2086,7 +2416,14 @@ async function runTier3(conn, ticker, dates, dailyTarget = "market.enriched", sp
|
|
|
2086
2416
|
reason: "intraday data exists but no bars overlap with enrichment date range"
|
|
2087
2417
|
};
|
|
2088
2418
|
}
|
|
2089
|
-
const tier3Cols = [
|
|
2419
|
+
const tier3Cols = [
|
|
2420
|
+
"High_Time",
|
|
2421
|
+
"Low_Time",
|
|
2422
|
+
"High_Before_Low",
|
|
2423
|
+
"Reversal_Type",
|
|
2424
|
+
"Opening_Drive_Strength",
|
|
2425
|
+
"Intraday_Realized_Vol"
|
|
2426
|
+
];
|
|
2090
2427
|
const enrichedRows = [];
|
|
2091
2428
|
for (const [dateStr, bars] of barsByDate) {
|
|
2092
2429
|
const timing = computeIntradayTimingFields(bars);
|
|
@@ -2205,7 +2542,9 @@ function applyColumnMapping(rows, columnMapping, ticker) {
|
|
|
2205
2542
|
if (hasNullDate) continue;
|
|
2206
2543
|
if (!("date" in mapped)) continue;
|
|
2207
2544
|
if (!("time" in mapped)) {
|
|
2208
|
-
const dateSourceCol = Object.entries(columnMapping).find(
|
|
2545
|
+
const dateSourceCol = Object.entries(columnMapping).find(
|
|
2546
|
+
([, schema]) => schema === "date"
|
|
2547
|
+
)?.[0];
|
|
2209
2548
|
if (dateSourceCol) {
|
|
2210
2549
|
const rawDateValue = row[dateSourceCol] ?? "";
|
|
2211
2550
|
const numericDate = Number(rawDateValue);
|
|
@@ -2553,134 +2892,126 @@ function buildFilterPredicate(filter) {
|
|
|
2553
2892
|
}
|
|
2554
2893
|
|
|
2555
2894
|
// src/tools/profiles.ts
|
|
2556
|
-
import { z } from "zod";
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
content: [
|
|
2562
|
-
{ type: "text", text: summary },
|
|
2563
|
-
{
|
|
2564
|
-
type: "resource",
|
|
2565
|
-
resource: {
|
|
2566
|
-
uri: "data:application/json",
|
|
2567
|
-
mimeType: "application/json",
|
|
2568
|
-
text: JSON.stringify(data)
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
|
-
]
|
|
2572
|
-
};
|
|
2573
|
-
}
|
|
2574
|
-
|
|
2575
|
-
// src/tools/profiles.ts
|
|
2576
|
-
var profileStrategySchema = z.object({
|
|
2577
|
-
blockId: z.string().describe("Block ID (block_id) to associate the profile with"),
|
|
2578
|
-
strategyName: z.string().describe("Human-readable strategy name (e.g., 'Pickle RIC v2')"),
|
|
2579
|
-
structureType: z.string().describe(
|
|
2895
|
+
import { z as z2 } from "zod";
|
|
2896
|
+
var profileStrategySchema = z2.object({
|
|
2897
|
+
blockId: z2.string().describe("Block ID (block_id) to associate the profile with"),
|
|
2898
|
+
strategyName: z2.string().describe("Human-readable strategy name (e.g., 'Pickle RIC v2')"),
|
|
2899
|
+
structureType: z2.string().describe(
|
|
2580
2900
|
"Option structure type: iron_condor, calendar_spread, double_calendar, vertical_spread, butterfly, reverse_iron_condor, short_put_spread, short_call_spread, straddle, strangle, etc."
|
|
2581
2901
|
),
|
|
2582
|
-
greeksBias:
|
|
2902
|
+
greeksBias: z2.string().describe(
|
|
2583
2903
|
"Primary greeks exposure: theta_positive, vega_negative, delta_neutral, delta_positive, delta_negative, gamma_scalp, etc."
|
|
2584
2904
|
),
|
|
2585
|
-
thesis:
|
|
2586
|
-
legs:
|
|
2587
|
-
|
|
2588
|
-
type:
|
|
2589
|
-
strike:
|
|
2590
|
-
expiry:
|
|
2591
|
-
quantity:
|
|
2592
|
-
strikeMethod:
|
|
2593
|
-
strikeValue:
|
|
2905
|
+
thesis: z2.string().default("").describe("Free-text description of the strategy thesis"),
|
|
2906
|
+
legs: z2.array(
|
|
2907
|
+
z2.object({
|
|
2908
|
+
type: z2.string().describe("Leg type: long_put, short_call, long_call, short_put, etc."),
|
|
2909
|
+
strike: z2.string().describe("Strike selection: ATM, 5-delta, 30-delta, etc."),
|
|
2910
|
+
expiry: z2.string().describe("Expiry selection: same-day, weekly, 45-DTE, etc."),
|
|
2911
|
+
quantity: z2.number().describe("Quantity (positive=long, negative=short)"),
|
|
2912
|
+
strikeMethod: z2.enum(["delta", "dollar_price", "offset", "percentage"]).optional().describe("How strike is selected"),
|
|
2913
|
+
strikeValue: z2.number().optional().describe("Numeric strike value (e.g., 25 for 25-delta)")
|
|
2594
2914
|
})
|
|
2595
2915
|
).default([]).describe("Structured leg descriptions"),
|
|
2596
|
-
entryFilters:
|
|
2597
|
-
|
|
2598
|
-
field:
|
|
2599
|
-
operator:
|
|
2600
|
-
value:
|
|
2601
|
-
description:
|
|
2602
|
-
source:
|
|
2916
|
+
entryFilters: z2.array(
|
|
2917
|
+
z2.object({
|
|
2918
|
+
field: z2.string().describe("Market data field: VIX_Close, RSI_14, Vol_Regime, etc."),
|
|
2919
|
+
operator: z2.string().describe("Comparison operator: >, <, >=, <=, ==, between, in"),
|
|
2920
|
+
value: z2.union([z2.string(), z2.number(), z2.array(z2.union([z2.string(), z2.number()]))]).describe("Filter value or array for between/in operators"),
|
|
2921
|
+
description: z2.string().optional().describe("Human-readable description of this filter"),
|
|
2922
|
+
source: z2.enum(["market", "execution"]).optional().describe(
|
|
2923
|
+
"Filter source: 'market' = testable against market data columns, 'execution' = platform-level (time windows, leg ratios). Defaults to 'market'. Execution filters are documented but skipped during validate_entry_filters analysis."
|
|
2924
|
+
)
|
|
2603
2925
|
})
|
|
2604
|
-
).default([]).describe(
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2926
|
+
).default([]).describe(
|
|
2927
|
+
"Entry condition filters. Tag each with source: 'market' (testable in analysis) or 'execution' (OO/platform-level, skipped in analysis)."
|
|
2928
|
+
),
|
|
2929
|
+
exitRules: z2.array(
|
|
2930
|
+
z2.object({
|
|
2931
|
+
type: z2.string().describe("Rule type: stop_loss, profit_target, time_exit, conditional"),
|
|
2932
|
+
trigger: z2.string().describe("Trigger condition: '200% of credit', '50% of max profit', '15:00 ET'"),
|
|
2933
|
+
description: z2.string().optional().describe("Human-readable description"),
|
|
2934
|
+
stopLossType: z2.enum(["percentage", "dollar", "sl_ratio", "debit_percentage"]).optional().describe("Stop loss calculation method"),
|
|
2935
|
+
stopLossValue: z2.number().optional().describe("Stop loss numeric value"),
|
|
2936
|
+
monitoring: z2.object({
|
|
2937
|
+
granularity: z2.enum(["intra_minute", "candle_close", "end_of_bar"]).optional().describe("Price check frequency"),
|
|
2938
|
+
priceSource: z2.enum(["nbbo", "mid", "last"]).optional().describe("Which price to use")
|
|
2615
2939
|
}).optional().describe("Monitoring configuration for this rule"),
|
|
2616
|
-
slippage:
|
|
2940
|
+
slippage: z2.number().optional().describe("Per-rule slippage override")
|
|
2617
2941
|
})
|
|
2618
2942
|
).default([]).describe("Exit rules and triggers"),
|
|
2619
|
-
expectedRegimes:
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2943
|
+
expectedRegimes: z2.array(z2.enum(["very_low", "low", "below_avg", "above_avg", "high", "extreme"])).default([]).describe(
|
|
2944
|
+
"VIX-based vol regimes this strategy targets. very_low=VIX<13, low=13-16, below_avg=16-20, above_avg=20-25, high=25-30, extreme=30+"
|
|
2945
|
+
),
|
|
2946
|
+
keyMetrics: z2.object({
|
|
2947
|
+
expectedWinRate: z2.number().optional().describe("Expected win rate (0-1)"),
|
|
2948
|
+
targetPremium: z2.number().optional().describe("Target premium collected ($)"),
|
|
2949
|
+
maxLoss: z2.number().optional().describe("Maximum loss per contract ($)"),
|
|
2950
|
+
profitTarget: z2.number().optional().describe("Profit target ($ or %)")
|
|
2625
2951
|
}).passthrough().default({}).describe("Performance benchmarks and strategy-specific metrics"),
|
|
2626
|
-
positionSizing:
|
|
2627
|
-
method:
|
|
2628
|
-
allocationPct:
|
|
2629
|
-
maxContracts:
|
|
2630
|
-
maxAllocationDollar:
|
|
2631
|
-
maxOpenPositions:
|
|
2632
|
-
description:
|
|
2633
|
-
backtestAllocationPct:
|
|
2634
|
-
liveAllocationPct:
|
|
2635
|
-
maxContractsPerTrade:
|
|
2636
|
-
}).optional().describe(
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2952
|
+
positionSizing: z2.object({
|
|
2953
|
+
method: z2.string().describe("Sizing method: pct_of_portfolio, fixed_contracts, fixed_dollar, discretionary"),
|
|
2954
|
+
allocationPct: z2.number().optional().describe("Portfolio allocation percentage (e.g., 2 for 2%)"),
|
|
2955
|
+
maxContracts: z2.number().optional().describe("Maximum contracts per trade"),
|
|
2956
|
+
maxAllocationDollar: z2.number().optional().describe("Maximum dollar allocation per trade"),
|
|
2957
|
+
maxOpenPositions: z2.number().optional().describe("Maximum concurrent open positions"),
|
|
2958
|
+
description: z2.string().optional().describe("Free-text sizing notes"),
|
|
2959
|
+
backtestAllocationPct: z2.number().optional().describe("Allocation % used in backtest"),
|
|
2960
|
+
liveAllocationPct: z2.number().optional().describe("Allocation % used in live portfolio"),
|
|
2961
|
+
maxContractsPerTrade: z2.number().optional().describe("Per-entry contract cap (distinct from maxContracts hard cap)")
|
|
2962
|
+
}).optional().describe(
|
|
2963
|
+
"Position sizing rules. Per-block \u2014 same strategy in backtest vs portfolio may have different sizing."
|
|
2964
|
+
),
|
|
2965
|
+
underlying: z2.string().optional().describe("Underlying symbol: SPX, QQQ, etc."),
|
|
2966
|
+
reEntry: z2.boolean().optional().describe("Strategy supports re-entry on same day"),
|
|
2967
|
+
capProfits: z2.boolean().optional().describe("Profits are capped by structure"),
|
|
2968
|
+
capLosses: z2.boolean().optional().describe("Losses are capped by structure"),
|
|
2969
|
+
requireTwoPricesPT: z2.boolean().optional().describe("Profit target requires two prices"),
|
|
2970
|
+
closeOnCompletion: z2.boolean().optional().describe("Close entire position when any leg hits target"),
|
|
2971
|
+
ignoreMarginReq: z2.boolean().optional().describe("Strategy ignores standard margin requirements")
|
|
2644
2972
|
});
|
|
2645
|
-
var getStrategyProfileSchema =
|
|
2646
|
-
blockId:
|
|
2647
|
-
strategyName:
|
|
2973
|
+
var getStrategyProfileSchema = z2.object({
|
|
2974
|
+
blockId: z2.string().describe("Block ID to look up"),
|
|
2975
|
+
strategyName: z2.string().describe("Strategy name to look up")
|
|
2648
2976
|
});
|
|
2649
|
-
var listProfilesSchema =
|
|
2650
|
-
blockId:
|
|
2977
|
+
var listProfilesSchema = z2.object({
|
|
2978
|
+
blockId: z2.string().optional().describe("Optional block ID filter. Omit to list all profiles across all blocks.")
|
|
2651
2979
|
});
|
|
2652
|
-
var deleteProfileSchema =
|
|
2653
|
-
blockId:
|
|
2654
|
-
strategyName:
|
|
2980
|
+
var deleteProfileSchema = z2.object({
|
|
2981
|
+
blockId: z2.string().describe("Block ID of the profile to delete"),
|
|
2982
|
+
strategyName: z2.string().describe("Strategy name of the profile to delete")
|
|
2655
2983
|
});
|
|
2656
2984
|
async function handleProfileStrategy(input, baseDir) {
|
|
2657
2985
|
await upgradeToReadWrite(baseDir);
|
|
2658
2986
|
try {
|
|
2659
2987
|
const conn = await getConnection(baseDir);
|
|
2660
|
-
const stored = await upsertProfile(
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2988
|
+
const stored = await upsertProfile(
|
|
2989
|
+
conn,
|
|
2990
|
+
{
|
|
2991
|
+
blockId: input.blockId,
|
|
2992
|
+
strategyName: input.strategyName,
|
|
2993
|
+
structureType: input.structureType,
|
|
2994
|
+
greeksBias: input.greeksBias,
|
|
2995
|
+
thesis: input.thesis,
|
|
2996
|
+
legs: input.legs,
|
|
2997
|
+
entryFilters: input.entryFilters,
|
|
2998
|
+
exitRules: input.exitRules,
|
|
2999
|
+
expectedRegimes: input.expectedRegimes,
|
|
3000
|
+
keyMetrics: input.keyMetrics,
|
|
3001
|
+
positionSizing: input.positionSizing,
|
|
3002
|
+
underlying: input.underlying,
|
|
3003
|
+
reEntry: input.reEntry,
|
|
3004
|
+
capProfits: input.capProfits,
|
|
3005
|
+
capLosses: input.capLosses,
|
|
3006
|
+
requireTwoPricesPT: input.requireTwoPricesPT,
|
|
3007
|
+
closeOnCompletion: input.closeOnCompletion,
|
|
3008
|
+
ignoreMarginReq: input.ignoreMarginReq
|
|
3009
|
+
},
|
|
3010
|
+
baseDir
|
|
2683
3011
|
);
|
|
3012
|
+
return createToolOutput(`Profile saved: ${input.strategyName} for block ${input.blockId}`, {
|
|
3013
|
+
profile: stored
|
|
3014
|
+
});
|
|
2684
3015
|
} finally {
|
|
2685
3016
|
await downgradeToReadOnly(baseDir);
|
|
2686
3017
|
}
|
|
@@ -2694,10 +3025,7 @@ async function handleGetStrategyProfile(input, baseDir) {
|
|
|
2694
3025
|
{ profile: null }
|
|
2695
3026
|
);
|
|
2696
3027
|
}
|
|
2697
|
-
return createToolOutput(
|
|
2698
|
-
`Profile: ${input.strategyName} in block ${input.blockId}`,
|
|
2699
|
-
{ profile }
|
|
2700
|
-
);
|
|
3028
|
+
return createToolOutput(`Profile: ${input.strategyName} in block ${input.blockId}`, { profile });
|
|
2701
3029
|
}
|
|
2702
3030
|
async function handleListProfiles(input, baseDir) {
|
|
2703
3031
|
const conn = await getConnection(baseDir);
|
|
@@ -2737,7 +3065,7 @@ async function handleDeleteProfile(input, baseDir) {
|
|
|
2737
3065
|
}
|
|
2738
3066
|
|
|
2739
3067
|
// src/tools/profile-analysis.ts
|
|
2740
|
-
import { z as
|
|
3068
|
+
import { z as z3 } from "zod";
|
|
2741
3069
|
function formatTradeDate(date) {
|
|
2742
3070
|
if (typeof date === "string") {
|
|
2743
3071
|
const match = date.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
@@ -2893,10 +3221,10 @@ function findBucket(value, buckets) {
|
|
|
2893
3221
|
}
|
|
2894
3222
|
return null;
|
|
2895
3223
|
}
|
|
2896
|
-
var analyzeStructureFitSchema =
|
|
2897
|
-
blockId:
|
|
2898
|
-
strategyName:
|
|
2899
|
-
minTrades:
|
|
3224
|
+
var analyzeStructureFitSchema = z3.object({
|
|
3225
|
+
blockId: z3.string().describe("Block ID to analyze"),
|
|
3226
|
+
strategyName: z3.string().describe("Strategy name matching a stored profile"),
|
|
3227
|
+
minTrades: z3.number().optional().default(10).describe("Minimum trades per bucket for reliable stats (thin-data warning threshold)")
|
|
2900
3228
|
});
|
|
2901
3229
|
async function handleAnalyzeStructureFit(input, baseDir) {
|
|
2902
3230
|
const { blockId, strategyName } = input;
|
|
@@ -3069,11 +3397,11 @@ async function handleAnalyzeStructureFit(input, baseDir) {
|
|
|
3069
3397
|
}
|
|
3070
3398
|
});
|
|
3071
3399
|
}
|
|
3072
|
-
var validateEntryFiltersSchema =
|
|
3073
|
-
blockId:
|
|
3074
|
-
strategyName:
|
|
3075
|
-
minTrades:
|
|
3076
|
-
maxAblationFilters:
|
|
3400
|
+
var validateEntryFiltersSchema = z3.object({
|
|
3401
|
+
blockId: z3.string().describe("Block ID to analyze"),
|
|
3402
|
+
strategyName: z3.string().describe("Strategy name matching a stored profile"),
|
|
3403
|
+
minTrades: z3.number().optional().default(10).describe("Minimum trades per group for reliable stats"),
|
|
3404
|
+
maxAblationFilters: z3.number().optional().default(8).describe("Maximum number of filters for pairwise ablation (cap for combinatorial explosion)")
|
|
3077
3405
|
});
|
|
3078
3406
|
async function handleValidateEntryFilters(input, baseDir) {
|
|
3079
3407
|
const { blockId, strategyName } = input;
|
|
@@ -3286,14 +3614,16 @@ async function handleValidateEntryFilters(input, baseDir) {
|
|
|
3286
3614
|
single: ablationSingle,
|
|
3287
3615
|
pairs: ablationPairs
|
|
3288
3616
|
},
|
|
3289
|
-
execution_filters_skipped: executionFilters.map(
|
|
3617
|
+
execution_filters_skipped: executionFilters.map(
|
|
3618
|
+
(f) => f.description || `${f.field} ${f.operator} ${f.value}`
|
|
3619
|
+
),
|
|
3290
3620
|
profile_update_hints: profileUpdateHints,
|
|
3291
3621
|
warnings
|
|
3292
3622
|
});
|
|
3293
3623
|
}
|
|
3294
|
-
var portfolioStructureMapSchema =
|
|
3295
|
-
blockId:
|
|
3296
|
-
minTrades:
|
|
3624
|
+
var portfolioStructureMapSchema = z3.object({
|
|
3625
|
+
blockId: z3.string().optional().describe("Block ID to analyze. When omitted, aggregate across all blocks."),
|
|
3626
|
+
minTrades: z3.number().optional().default(10).describe("Thin-data warning threshold (default: 10)")
|
|
3297
3627
|
});
|
|
3298
3628
|
async function handlePortfolioStructureMap(input, baseDir) {
|
|
3299
3629
|
try {
|
|
@@ -3317,7 +3647,9 @@ async function handlePortfolioStructureMap(input, baseDir) {
|
|
|
3317
3647
|
try {
|
|
3318
3648
|
block = await loadBlock(baseDir, profile.blockId);
|
|
3319
3649
|
} catch {
|
|
3320
|
-
warnings.push(
|
|
3650
|
+
warnings.push(
|
|
3651
|
+
`Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'`
|
|
3652
|
+
);
|
|
3321
3653
|
continue;
|
|
3322
3654
|
}
|
|
3323
3655
|
let trades = filterByStrategy(block.trades, profile.strategyName);
|
|
@@ -3328,7 +3660,9 @@ async function handlePortfolioStructureMap(input, baseDir) {
|
|
|
3328
3660
|
}
|
|
3329
3661
|
}
|
|
3330
3662
|
if (trades.length === 0) {
|
|
3331
|
-
warnings.push(
|
|
3663
|
+
warnings.push(
|
|
3664
|
+
`No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'`
|
|
3665
|
+
);
|
|
3332
3666
|
continue;
|
|
3333
3667
|
}
|
|
3334
3668
|
const tradeKeys = uniqueTradeLookupKeys(trades);
|
|
@@ -3446,10 +3780,7 @@ async function handlePortfolioStructureMap(input, baseDir) {
|
|
|
3446
3780
|
);
|
|
3447
3781
|
}
|
|
3448
3782
|
const unknownTrendStats = unknownTrendPls.size > 0 ? Object.fromEntries(
|
|
3449
|
-
[...unknownTrendPls.entries()].map(([name, pls]) => [
|
|
3450
|
-
name,
|
|
3451
|
-
computeSliceStats(pls)
|
|
3452
|
-
])
|
|
3783
|
+
[...unknownTrendPls.entries()].map(([name, pls]) => [name, computeSliceStats(pls)])
|
|
3453
3784
|
) : void 0;
|
|
3454
3785
|
const coverageSummary = {
|
|
3455
3786
|
totalCells: 18,
|
|
@@ -3484,7 +3815,7 @@ async function handlePortfolioStructureMap(input, baseDir) {
|
|
|
3484
3815
|
}
|
|
3485
3816
|
|
|
3486
3817
|
// src/tools/regime-advisor.ts
|
|
3487
|
-
import { z as
|
|
3818
|
+
import { z as z4 } from "zod";
|
|
3488
3819
|
function formatTradeDate2(date) {
|
|
3489
3820
|
if (typeof date === "string") {
|
|
3490
3821
|
const match = date.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
@@ -3550,13 +3881,9 @@ var VOL_REGIME_LABELS2 = {
|
|
|
3550
3881
|
5: "high",
|
|
3551
3882
|
6: "extreme"
|
|
3552
3883
|
};
|
|
3553
|
-
var regimeAllocationAdvisorSchema =
|
|
3554
|
-
blockId:
|
|
3555
|
-
|
|
3556
|
-
),
|
|
3557
|
-
minTrades: z3.number().optional().default(5).describe(
|
|
3558
|
-
"Minimum trades per regime cell for reliable stats (default: 5)"
|
|
3559
|
-
)
|
|
3884
|
+
var regimeAllocationAdvisorSchema = z4.object({
|
|
3885
|
+
blockId: z4.string().optional().describe("Block ID to analyze. When omitted, aggregate across all profiled strategies."),
|
|
3886
|
+
minTrades: z4.number().optional().default(5).describe("Minimum trades per regime cell for reliable stats (default: 5)")
|
|
3560
3887
|
});
|
|
3561
3888
|
async function handleRegimeAllocationAdvisor(input, baseDir) {
|
|
3562
3889
|
const minTrades = input.minTrades ?? 5;
|
|
@@ -3648,9 +3975,7 @@ async function handleRegimeAllocationAdvisor(input, baseDir) {
|
|
|
3648
3975
|
if (!regimeAggPls[label]) regimeAggPls[label] = [];
|
|
3649
3976
|
regimeAggPls[label].push(trade.pl);
|
|
3650
3977
|
}
|
|
3651
|
-
const expectedSet = new Set(
|
|
3652
|
-
profile.expectedRegimes.map((r) => r.toLowerCase())
|
|
3653
|
-
);
|
|
3978
|
+
const expectedSet = new Set(profile.expectedRegimes.map((r) => r.toLowerCase()));
|
|
3654
3979
|
const regimePerformance = {};
|
|
3655
3980
|
for (const [label, pls] of Object.entries(regimePls)) {
|
|
3656
3981
|
const stats = computeSliceStats(pls);
|
|
@@ -3754,7 +4079,7 @@ function getResolvedProviderCapabilities(env = process.env) {
|
|
|
3754
4079
|
}
|
|
3755
4080
|
|
|
3756
4081
|
// src/tools/replay.ts
|
|
3757
|
-
import { z as
|
|
4082
|
+
import { z as z5 } from "zod";
|
|
3758
4083
|
|
|
3759
4084
|
// src/market/tickers/resolver.ts
|
|
3760
4085
|
var OCC_RE = /^([A-Z]+)\d{6}[CP]\d{6,11}$/;
|
|
@@ -3773,38 +4098,36 @@ function rootToUnderlying(input, registry) {
|
|
|
3773
4098
|
}
|
|
3774
4099
|
|
|
3775
4100
|
// src/tools/replay.ts
|
|
3776
|
-
var replayTradeSchema =
|
|
4101
|
+
var replayTradeSchema = z5.object({
|
|
3777
4102
|
// Mode A: Hypothetical / explicit legs
|
|
3778
|
-
legs:
|
|
3779
|
-
|
|
3780
|
-
ticker:
|
|
3781
|
-
strike:
|
|
3782
|
-
type:
|
|
3783
|
-
expiry:
|
|
3784
|
-
quantity:
|
|
3785
|
-
entry_price:
|
|
4103
|
+
legs: z5.array(
|
|
4104
|
+
z5.object({
|
|
4105
|
+
ticker: z5.string().describe("Underlying ticker, e.g., 'SPY', 'SPX'"),
|
|
4106
|
+
strike: z5.number().describe("Strike price"),
|
|
4107
|
+
type: z5.enum(["C", "P"]).describe("Call or Put"),
|
|
4108
|
+
expiry: z5.string().describe("Expiration date YYYY-MM-DD"),
|
|
4109
|
+
quantity: z5.number().describe("Positive = long, negative = short"),
|
|
4110
|
+
entry_price: z5.number().describe("Per-contract entry price (premium paid/received)")
|
|
3786
4111
|
})
|
|
3787
4112
|
).optional().describe("Explicit leg definitions for hypothetical replay"),
|
|
3788
4113
|
// Mode B: Tradelog replay
|
|
3789
|
-
block_id:
|
|
3790
|
-
trade_index:
|
|
3791
|
-
"0-based index of trade in block's tradelog (ordered by date_opened)"
|
|
3792
|
-
),
|
|
4114
|
+
block_id: z5.string().optional().describe("Block ID to load trade from"),
|
|
4115
|
+
trade_index: z5.number().optional().describe("0-based index of trade in block's tradelog (ordered by date_opened)"),
|
|
3793
4116
|
// Common fields
|
|
3794
|
-
open_date:
|
|
4117
|
+
open_date: z5.string().optional().describe(
|
|
3795
4118
|
"Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)"
|
|
3796
4119
|
),
|
|
3797
|
-
close_date:
|
|
4120
|
+
close_date: z5.string().optional().describe(
|
|
3798
4121
|
"Trade close date YYYY-MM-DD (required for hypothetical, auto-resolved for tradelog)"
|
|
3799
4122
|
),
|
|
3800
|
-
multiplier:
|
|
3801
|
-
format:
|
|
4123
|
+
multiplier: z5.number().default(100).describe("Contract multiplier (default 100 for standard options)"),
|
|
4124
|
+
format: z5.enum(["full", "summary", "sampled"]).default("sampled").describe(
|
|
3802
4125
|
"Output format: 'sampled' returns path sampled at ~15min intervals (default), 'full' returns complete minute-by-minute P&L path, 'summary' returns MFE/MAE/P&L without minute-level path"
|
|
3803
4126
|
),
|
|
3804
|
-
close_at:
|
|
4127
|
+
close_at: z5.enum(["trade", "expiry"]).default("trade").describe(
|
|
3805
4128
|
"When to end the P&L path: 'trade' (default) truncates at the trade's actual close time, 'expiry' shows full path through option expiry. Only applies to tradelog mode."
|
|
3806
4129
|
),
|
|
3807
|
-
skip_quotes:
|
|
4130
|
+
skip_quotes: z5.boolean().default(false).describe(
|
|
3808
4131
|
"Skip NBBO quote enrichment for option bars. Faster, but uses cached trade bars / HL2 marks."
|
|
3809
4132
|
)
|
|
3810
4133
|
});
|
|
@@ -3836,22 +4159,13 @@ function resolveOODateRange(parsedLegs, tradeYear, tradeOpenDate) {
|
|
|
3836
4159
|
return { from: tradeOpenDate, to: maxDate };
|
|
3837
4160
|
}
|
|
3838
4161
|
async function handleReplayTrade(params, baseDir, stores, injectedConn) {
|
|
3839
|
-
const {
|
|
3840
|
-
legs: inputLegs,
|
|
3841
|
-
block_id,
|
|
3842
|
-
trade_index,
|
|
3843
|
-
multiplier,
|
|
3844
|
-
close_at,
|
|
3845
|
-
skip_quotes
|
|
3846
|
-
} = params;
|
|
4162
|
+
const { legs: inputLegs, block_id, trade_index, multiplier, close_at, skip_quotes } = params;
|
|
3847
4163
|
let { open_date, close_date } = params;
|
|
3848
4164
|
let tradeCloseTimestamp;
|
|
3849
4165
|
let replayLegs;
|
|
3850
4166
|
if (inputLegs && inputLegs.length > 0) {
|
|
3851
4167
|
if (!open_date || !close_date) {
|
|
3852
|
-
throw new Error(
|
|
3853
|
-
"open_date and close_date are required for hypothetical replay mode"
|
|
3854
|
-
);
|
|
4168
|
+
throw new Error("open_date and close_date are required for hypothetical replay mode");
|
|
3855
4169
|
}
|
|
3856
4170
|
replayLegs = inputLegs.map((leg) => ({
|
|
3857
4171
|
occTicker: buildOccTicker(leg.ticker, leg.expiry, leg.type, leg.strike),
|
|
@@ -3870,9 +4184,7 @@ async function handleReplayTrade(params, baseDir, stores, injectedConn) {
|
|
|
3870
4184
|
);
|
|
3871
4185
|
const rows = result.getRows();
|
|
3872
4186
|
if (rows.length === 0) {
|
|
3873
|
-
throw new Error(
|
|
3874
|
-
`No trade found at index ${trade_index} in block "${block_id}"`
|
|
3875
|
-
);
|
|
4187
|
+
throw new Error(`No trade found at index ${trade_index} in block "${block_id}"`);
|
|
3876
4188
|
}
|
|
3877
4189
|
const row = rows[0];
|
|
3878
4190
|
const legsStr = String(row[0] ?? "");
|
|
@@ -3981,11 +4293,7 @@ async function handleReplayTrade(params, baseDir, stores, injectedConn) {
|
|
|
3981
4293
|
);
|
|
3982
4294
|
if (underlyingBars.length === 0) {
|
|
3983
4295
|
try {
|
|
3984
|
-
underlyingBars = await stores.spot.readDailyBars(
|
|
3985
|
-
underlyingTicker,
|
|
3986
|
-
open_date,
|
|
3987
|
-
close_date
|
|
3988
|
-
);
|
|
4296
|
+
underlyingBars = await stores.spot.readDailyBars(underlyingTicker, open_date, close_date);
|
|
3989
4297
|
} catch {
|
|
3990
4298
|
}
|
|
3991
4299
|
}
|
|
@@ -4104,17 +4412,15 @@ async function handleReplayTrade(params, baseDir, stores, injectedConn) {
|
|
|
4104
4412
|
}
|
|
4105
4413
|
|
|
4106
4414
|
// src/tools/snapshot.ts
|
|
4107
|
-
import { z as
|
|
4108
|
-
var getOptionSnapshotSchema =
|
|
4109
|
-
underlying:
|
|
4110
|
-
strike_price_gte:
|
|
4111
|
-
strike_price_lte:
|
|
4112
|
-
expiration_date_gte:
|
|
4113
|
-
expiration_date_lte:
|
|
4114
|
-
contract_type:
|
|
4115
|
-
limit:
|
|
4116
|
-
"Max contracts to return (default 50, use higher for full chain)"
|
|
4117
|
-
)
|
|
4415
|
+
import { z as z6 } from "zod";
|
|
4416
|
+
var getOptionSnapshotSchema = z6.object({
|
|
4417
|
+
underlying: z6.string().describe("Underlying ticker symbol (e.g., 'SPX', 'SPY', 'AAPL')"),
|
|
4418
|
+
strike_price_gte: z6.number().optional().describe("Minimum strike price filter"),
|
|
4419
|
+
strike_price_lte: z6.number().optional().describe("Maximum strike price filter"),
|
|
4420
|
+
expiration_date_gte: z6.string().optional().describe("Earliest expiration date (YYYY-MM-DD)"),
|
|
4421
|
+
expiration_date_lte: z6.string().optional().describe("Latest expiration date (YYYY-MM-DD)"),
|
|
4422
|
+
contract_type: z6.enum(["call", "put"]).optional().describe("Filter by call or put"),
|
|
4423
|
+
limit: z6.number().optional().default(50).describe("Max contracts to return (default 50, use higher for full chain)")
|
|
4118
4424
|
});
|
|
4119
4425
|
async function handleGetOptionSnapshot(params) {
|
|
4120
4426
|
try {
|
|
@@ -4231,7 +4537,9 @@ function numericalDecomposition(config, totalPnlChange, stepCount) {
|
|
|
4231
4537
|
...f,
|
|
4232
4538
|
pctOfTotal: totalAbsSum > 0 ? Math.abs(f.totalPnl) / totalAbsSum * 100 : 0
|
|
4233
4539
|
}));
|
|
4234
|
-
const summaryParts = factors.map(
|
|
4540
|
+
const summaryParts = factors.map(
|
|
4541
|
+
(f) => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`
|
|
4542
|
+
);
|
|
4235
4543
|
const summary = `P&L of ${totalPnlChange.toFixed(2)} (numerical): ${summaryParts.join(", ")}`;
|
|
4236
4544
|
return {
|
|
4237
4545
|
factors,
|
|
@@ -4266,7 +4574,15 @@ function priceOption(type, S, K, dte, r, q, iv) {
|
|
|
4266
4574
|
return bsPrice(bsType, S, K, T, r, q, iv);
|
|
4267
4575
|
}
|
|
4268
4576
|
function decomposeGreeks(config) {
|
|
4269
|
-
const {
|
|
4577
|
+
const {
|
|
4578
|
+
pnlPath,
|
|
4579
|
+
legs,
|
|
4580
|
+
underlyingPrices,
|
|
4581
|
+
legGroups,
|
|
4582
|
+
legPricingInputs,
|
|
4583
|
+
riskFreeRate,
|
|
4584
|
+
dividendYield
|
|
4585
|
+
} = config;
|
|
4270
4586
|
if (pnlPath.length <= 1) {
|
|
4271
4587
|
const emptyFactors = [
|
|
4272
4588
|
{ factor: "delta", totalPnl: 0, pctOfTotal: 0, steps: [] },
|
|
@@ -4426,7 +4742,9 @@ function decomposeGreeks(config) {
|
|
|
4426
4742
|
const summaryParts = factors.filter((f) => f.factor !== "residual").map((f) => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);
|
|
4427
4743
|
const residualFactor = factors.find((f) => f.factor === "residual");
|
|
4428
4744
|
if (residualFactor && Math.abs(residualFactor.totalPnl) > 0.01) {
|
|
4429
|
-
summaryParts.push(
|
|
4745
|
+
summaryParts.push(
|
|
4746
|
+
`residual ${residualFactor.totalPnl.toFixed(2)} (${residualFactor.pctOfTotal.toFixed(0)}%)`
|
|
4747
|
+
);
|
|
4430
4748
|
}
|
|
4431
4749
|
const summary = `P&L of ${totalPnlChange.toFixed(2)} (${methodLabel}): ${summaryParts.join(", ")}`;
|
|
4432
4750
|
const warning = residualPct > 0.5 ? `Residual ${(residualPct * 100).toFixed(0)}% \u2014 attribution limited for some legs.` : null;
|
|
@@ -4558,7 +4876,7 @@ function evaluateTrigger(trigger, pnlPath, legs) {
|
|
|
4558
4876
|
switch (type) {
|
|
4559
4877
|
case "profitTarget": {
|
|
4560
4878
|
if (trigger.unit === "percent" && trigger.entryCost == null) break;
|
|
4561
|
-
const requiredHits = trigger.requiredHits ??
|
|
4879
|
+
const requiredHits = trigger.requiredHits ?? 2;
|
|
4562
4880
|
const dollarThresholdPT = trigger.unit === "percent" ? threshold * Math.abs(trigger.entryCost) : threshold;
|
|
4563
4881
|
if (pnl >= dollarThresholdPT) {
|
|
4564
4882
|
if (point.allLegsSync !== false) profitTargetHits++;
|
|
@@ -4914,8 +5232,8 @@ function analyzeExitTriggers(config) {
|
|
|
4914
5232
|
}
|
|
4915
5233
|
|
|
4916
5234
|
// src/tools/exit-analysis.ts
|
|
4917
|
-
import { z as
|
|
4918
|
-
var triggerTypeEnum =
|
|
5235
|
+
import { z as z7 } from "zod";
|
|
5236
|
+
var triggerTypeEnum = z7.enum([
|
|
4919
5237
|
"profitTarget",
|
|
4920
5238
|
"stopLoss",
|
|
4921
5239
|
"trailingStop",
|
|
@@ -4932,64 +5250,70 @@ var triggerTypeEnum = z6.enum([
|
|
|
4932
5250
|
"slRatioThreshold",
|
|
4933
5251
|
"slRatioMove"
|
|
4934
5252
|
]);
|
|
4935
|
-
var triggerConfigSchema =
|
|
5253
|
+
var triggerConfigSchema = z7.object({
|
|
4936
5254
|
type: triggerTypeEnum,
|
|
4937
|
-
threshold:
|
|
4938
|
-
unit:
|
|
4939
|
-
expiry:
|
|
4940
|
-
openDate:
|
|
4941
|
-
clockTime:
|
|
4942
|
-
trailAmount:
|
|
4943
|
-
steps:
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
5255
|
+
threshold: z7.number(),
|
|
5256
|
+
unit: z7.enum(["percent", "dollar"]).default("dollar").optional(),
|
|
5257
|
+
expiry: z7.string().optional(),
|
|
5258
|
+
openDate: z7.string().optional(),
|
|
5259
|
+
clockTime: z7.string().optional(),
|
|
5260
|
+
trailAmount: z7.number().optional(),
|
|
5261
|
+
steps: z7.array(
|
|
5262
|
+
z7.object({
|
|
5263
|
+
armAt: z7.number(),
|
|
5264
|
+
stopAt: z7.number(),
|
|
5265
|
+
closeAllocationPct: z7.number().min(0).max(1).optional().describe("Fraction of REMAINING position to close at this milestone (0-1)")
|
|
5266
|
+
})
|
|
5267
|
+
).optional(),
|
|
5268
|
+
spreadWidth: z7.number().optional(),
|
|
5269
|
+
contracts: z7.number().optional(),
|
|
5270
|
+
legIndex: z7.number().optional().describe("0-based leg index for perLegDelta \u2014 targets specific leg"),
|
|
5271
|
+
exitAbove: z7.number().optional().describe("Fire when value exceeds this (directional, no abs)"),
|
|
5272
|
+
exitBelow: z7.number().optional().describe("Fire when value drops below this (directional, no abs)")
|
|
4953
5273
|
});
|
|
4954
|
-
var legSchema =
|
|
4955
|
-
ticker:
|
|
4956
|
-
strike:
|
|
4957
|
-
type:
|
|
4958
|
-
expiry:
|
|
4959
|
-
quantity:
|
|
4960
|
-
entry_price:
|
|
5274
|
+
var legSchema = z7.object({
|
|
5275
|
+
ticker: z7.string(),
|
|
5276
|
+
strike: z7.number(),
|
|
5277
|
+
type: z7.enum(["C", "P"]),
|
|
5278
|
+
expiry: z7.string(),
|
|
5279
|
+
quantity: z7.number(),
|
|
5280
|
+
entry_price: z7.number()
|
|
4961
5281
|
});
|
|
4962
|
-
var analyzeExitTriggersSchema =
|
|
5282
|
+
var analyzeExitTriggersSchema = z7.object({
|
|
4963
5283
|
// Replay inputs (same shape as replay_trade)
|
|
4964
|
-
legs:
|
|
4965
|
-
block_id:
|
|
4966
|
-
trade_index:
|
|
4967
|
-
open_date:
|
|
4968
|
-
close_date:
|
|
4969
|
-
multiplier:
|
|
4970
|
-
triggers:
|
|
4971
|
-
actual_exit_timestamp:
|
|
4972
|
-
leg_groups:
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
5284
|
+
legs: z7.array(legSchema).optional(),
|
|
5285
|
+
block_id: z7.string().optional(),
|
|
5286
|
+
trade_index: z7.number().optional(),
|
|
5287
|
+
open_date: z7.string().optional(),
|
|
5288
|
+
close_date: z7.string().optional(),
|
|
5289
|
+
multiplier: z7.number().default(100),
|
|
5290
|
+
triggers: z7.array(triggerConfigSchema).describe("Exit triggers to evaluate against the P&L path"),
|
|
5291
|
+
actual_exit_timestamp: z7.string().optional().describe("Actual exit time for comparison (format: YYYY-MM-DD HH:MM)"),
|
|
5292
|
+
leg_groups: z7.array(
|
|
5293
|
+
z7.object({
|
|
5294
|
+
label: z7.string(),
|
|
5295
|
+
leg_indices: z7.array(z7.number()),
|
|
5296
|
+
triggers: z7.array(triggerConfigSchema)
|
|
5297
|
+
})
|
|
5298
|
+
).optional().describe("Per-leg-group exit triggers for multi-structure strategies"),
|
|
5299
|
+
format: z7.enum(["summary", "full"]).default("summary").describe("'summary' omits per-step trigger states, 'full' includes all fire events")
|
|
4978
5300
|
});
|
|
4979
|
-
var decomposeGreeksSchema =
|
|
5301
|
+
var decomposeGreeksSchema = z7.object({
|
|
4980
5302
|
// Same replay inputs
|
|
4981
|
-
legs:
|
|
4982
|
-
block_id:
|
|
4983
|
-
trade_index:
|
|
4984
|
-
open_date:
|
|
4985
|
-
close_date:
|
|
4986
|
-
multiplier:
|
|
4987
|
-
leg_groups:
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
5303
|
+
legs: z7.array(legSchema).optional(),
|
|
5304
|
+
block_id: z7.string().optional(),
|
|
5305
|
+
trade_index: z7.number().optional(),
|
|
5306
|
+
open_date: z7.string().optional(),
|
|
5307
|
+
close_date: z7.string().optional(),
|
|
5308
|
+
multiplier: z7.number().default(100),
|
|
5309
|
+
leg_groups: z7.array(
|
|
5310
|
+
z7.object({
|
|
5311
|
+
label: z7.string(),
|
|
5312
|
+
leg_indices: z7.array(z7.number())
|
|
5313
|
+
})
|
|
5314
|
+
).optional().describe("Leg grouping for per-group vega attribution (e.g., front_month vs back_month)"),
|
|
5315
|
+
format: z7.enum(["summary", "full"]).default("summary").describe("'summary' shows ranked factors, 'full' includes per-step contributions"),
|
|
5316
|
+
skip_quotes: z7.boolean().default(false).describe("Skip NBBO quote enrichment for option bars. Faster, but lower precision.")
|
|
4993
5317
|
});
|
|
4994
5318
|
var REVERSE_ROOT_MAP = {
|
|
4995
5319
|
SPXW: "SPX",
|
|
@@ -5012,7 +5336,8 @@ async function fetchPriceMap(stores, ticker, from, to) {
|
|
|
5012
5336
|
}
|
|
5013
5337
|
}
|
|
5014
5338
|
for (const b of bars) {
|
|
5015
|
-
if (!Number.isFinite(b.open) || b.open <= 0 || !Number.isFinite(b.high) || b.high <= 0 || !Number.isFinite(b.low) || b.low <= 0 || !Number.isFinite(b.close) || b.close <= 0)
|
|
5339
|
+
if (!Number.isFinite(b.open) || b.open <= 0 || !Number.isFinite(b.high) || b.high <= 0 || !Number.isFinite(b.low) || b.low <= 0 || !Number.isFinite(b.close) || b.close <= 0)
|
|
5340
|
+
continue;
|
|
5016
5341
|
const ts = `${b.date} ${b.time ?? ""}`.trim();
|
|
5017
5342
|
map.set(ts, markPrice(b));
|
|
5018
5343
|
}
|
|
@@ -5083,12 +5408,7 @@ async function handleAnalyzeExitTriggers(params, baseDir, stores, injectedConn)
|
|
|
5083
5408
|
vix9dPrices = await fetchPriceMap(stores, "VIX9D", firstDate, lastDate);
|
|
5084
5409
|
}
|
|
5085
5410
|
if (needsUnderlying) {
|
|
5086
|
-
underlyingPrices = await fetchPriceMap(
|
|
5087
|
-
stores,
|
|
5088
|
-
underlyingTicker,
|
|
5089
|
-
firstDate,
|
|
5090
|
-
lastDate
|
|
5091
|
-
);
|
|
5411
|
+
underlyingPrices = await fetchPriceMap(stores, underlyingTicker, firstDate, lastDate);
|
|
5092
5412
|
}
|
|
5093
5413
|
const exitTriggers = triggers.map((t) => ({
|
|
5094
5414
|
type: t.type,
|
|
@@ -5419,7 +5739,7 @@ function analyzeBatch(trades, config) {
|
|
|
5419
5739
|
}
|
|
5420
5740
|
|
|
5421
5741
|
// src/tools/batch-exit-analysis.ts
|
|
5422
|
-
import { z as
|
|
5742
|
+
import { z as z8 } from "zod";
|
|
5423
5743
|
async function mapWithLimit(items, limit, fn) {
|
|
5424
5744
|
const results = new Array(items.length);
|
|
5425
5745
|
let idx = 0;
|
|
@@ -5433,7 +5753,7 @@ async function mapWithLimit(items, limit, fn) {
|
|
|
5433
5753
|
await Promise.all(workers);
|
|
5434
5754
|
return results;
|
|
5435
5755
|
}
|
|
5436
|
-
var triggerTypeEnum2 =
|
|
5756
|
+
var triggerTypeEnum2 = z8.enum([
|
|
5437
5757
|
"profitTarget",
|
|
5438
5758
|
"stopLoss",
|
|
5439
5759
|
"trailingStop",
|
|
@@ -5450,44 +5770,52 @@ var triggerTypeEnum2 = z7.enum([
|
|
|
5450
5770
|
"slRatioThreshold",
|
|
5451
5771
|
"slRatioMove"
|
|
5452
5772
|
]);
|
|
5453
|
-
var triggerConfigSchema2 =
|
|
5773
|
+
var triggerConfigSchema2 = z8.object({
|
|
5454
5774
|
type: triggerTypeEnum2,
|
|
5455
|
-
threshold:
|
|
5456
|
-
unit:
|
|
5457
|
-
expiry:
|
|
5458
|
-
openDate:
|
|
5459
|
-
clockTime:
|
|
5460
|
-
trailAmount:
|
|
5461
|
-
steps:
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5775
|
+
threshold: z8.number(),
|
|
5776
|
+
unit: z8.enum(["percent", "dollar"]).default("dollar").optional(),
|
|
5777
|
+
expiry: z8.string().optional(),
|
|
5778
|
+
openDate: z8.string().optional(),
|
|
5779
|
+
clockTime: z8.string().optional(),
|
|
5780
|
+
trailAmount: z8.number().optional(),
|
|
5781
|
+
steps: z8.array(
|
|
5782
|
+
z8.object({
|
|
5783
|
+
armAt: z8.number(),
|
|
5784
|
+
stopAt: z8.number(),
|
|
5785
|
+
closeAllocationPct: z8.number().min(0).max(1).optional().describe("Fraction of REMAINING position to close at this milestone (0-1)")
|
|
5786
|
+
})
|
|
5787
|
+
).optional(),
|
|
5788
|
+
spreadWidth: z8.number().optional(),
|
|
5789
|
+
contracts: z8.number().optional(),
|
|
5790
|
+
legIndex: z8.number().optional().describe("0-based leg index for perLegDelta \u2014 targets specific leg"),
|
|
5791
|
+
exitAbove: z8.number().optional().describe("Fire when value exceeds this (directional, no abs)"),
|
|
5792
|
+
exitBelow: z8.number().optional().describe("Fire when value drops below this (directional, no abs)")
|
|
5471
5793
|
});
|
|
5472
|
-
var batchExitAnalysisSchema =
|
|
5473
|
-
block_id:
|
|
5474
|
-
strategy:
|
|
5475
|
-
date_range:
|
|
5476
|
-
from:
|
|
5477
|
-
to:
|
|
5794
|
+
var batchExitAnalysisSchema = z8.object({
|
|
5795
|
+
block_id: z8.string().describe("Block ID to analyze trades from"),
|
|
5796
|
+
strategy: z8.string().optional().describe("Filter trades by strategy name (case-insensitive ILIKE)"),
|
|
5797
|
+
date_range: z8.object({
|
|
5798
|
+
from: z8.string().optional().describe("Start date YYYY-MM-DD"),
|
|
5799
|
+
to: z8.string().optional().describe("End date YYYY-MM-DD")
|
|
5478
5800
|
}).optional().describe("Filter trades by date range"),
|
|
5479
|
-
candidate_policy:
|
|
5480
|
-
leg_groups:
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5801
|
+
candidate_policy: z8.array(triggerConfigSchema2).describe("Candidate exit policy triggers to evaluate -- same schema as analyze_exit_triggers"),
|
|
5802
|
+
leg_groups: z8.array(
|
|
5803
|
+
z8.object({
|
|
5804
|
+
label: z8.string(),
|
|
5805
|
+
leg_indices: z8.array(z8.number()),
|
|
5806
|
+
triggers: z8.array(triggerConfigSchema2)
|
|
5807
|
+
})
|
|
5808
|
+
).optional().describe("Per-leg-group exit triggers for multi-structure strategies"),
|
|
5809
|
+
baseline_mode: z8.enum(["actual", "holdToEnd"]).default("actual").describe(
|
|
5810
|
+
"'actual' compares candidate vs trade's actual P&L; 'holdToEnd' compares vs last replay timestamp"
|
|
5811
|
+
),
|
|
5812
|
+
limit: z8.number().min(1).max(200).default(50).describe("Max trades to analyze. Most recent trades selected"),
|
|
5813
|
+
min_pl: z8.number().optional().describe("Only include trades with actual P&L >= this value"),
|
|
5814
|
+
max_pl: z8.number().optional().describe("Only include trades with actual P&L <= this value"),
|
|
5815
|
+
multiplier: z8.number().default(100).describe("Contract multiplier (default 100)"),
|
|
5816
|
+
format: z8.enum(["summary", "full"]).default("summary").describe(
|
|
5817
|
+
"'summary' returns aggregate stats + trigger attribution; 'full' adds per-trade breakdown"
|
|
5818
|
+
)
|
|
5491
5819
|
});
|
|
5492
5820
|
async function handleBatchExitAnalysis(params, baseDir, stores, injectedConn) {
|
|
5493
5821
|
const {
|
|
@@ -5650,9 +5978,7 @@ async function handleBatchExitAnalysis(params, baseDir, stores, injectedConn) {
|
|
|
5650
5978
|
if (profile) {
|
|
5651
5979
|
result.profileContext = {
|
|
5652
5980
|
structureType: profile.structureType,
|
|
5653
|
-
exitRules: profile.exitRules.map(
|
|
5654
|
-
(r) => r.description ?? `${r.type} ${r.trigger}`
|
|
5655
|
-
)
|
|
5981
|
+
exitRules: profile.exitRules.map((r) => r.description ?? `${r.type} ${r.trigger}`)
|
|
5656
5982
|
};
|
|
5657
5983
|
}
|
|
5658
5984
|
} catch {
|
|
@@ -5699,7 +6025,7 @@ function expandDateRange(fromDate, toDate) {
|
|
|
5699
6025
|
|
|
5700
6026
|
// src/tools/sql.ts
|
|
5701
6027
|
import * as path2 from "path";
|
|
5702
|
-
import { z as
|
|
6028
|
+
import { z as z9 } from "zod";
|
|
5703
6029
|
var BLOCKED_PATTERNS = [
|
|
5704
6030
|
// External access
|
|
5705
6031
|
{ pattern: /\bCOPY\b/i, operation: "COPY" },
|
|
@@ -5813,13 +6139,7 @@ function filterChain(contracts, filter) {
|
|
|
5813
6139
|
import { open, mkdir } from "fs/promises";
|
|
5814
6140
|
import { dirname, join as join2 } from "path";
|
|
5815
6141
|
function backfillManifestPath(dataRoot, runId) {
|
|
5816
|
-
return join2(
|
|
5817
|
-
dataRoot,
|
|
5818
|
-
"market",
|
|
5819
|
-
"_manifests",
|
|
5820
|
-
"thetadata-mdds-backfill",
|
|
5821
|
-
`${runId}.ndjson`
|
|
5822
|
-
);
|
|
6142
|
+
return join2(dataRoot, "market", "_manifests", "thetadata-mdds-backfill", `${runId}.ndjson`);
|
|
5823
6143
|
}
|
|
5824
6144
|
function backfillPartitionPath(dataRoot, underlying, date) {
|
|
5825
6145
|
return join2(
|
|
@@ -5969,15 +6289,15 @@ function collectBackfillConcreteFallbacks(input) {
|
|
|
5969
6289
|
return fallbacks;
|
|
5970
6290
|
}
|
|
5971
6291
|
async function appendBackfillManifestLineDurable(manifestPath, line) {
|
|
5972
|
-
const
|
|
6292
|
+
const path11 = requireNonEmpty("manifestPath", manifestPath);
|
|
5973
6293
|
const text = String(line);
|
|
5974
6294
|
if (!text.endsWith("\n")) {
|
|
5975
6295
|
throw new Error("manifest line must end with a newline");
|
|
5976
6296
|
}
|
|
5977
|
-
const parentDir = dirname(
|
|
6297
|
+
const parentDir = dirname(path11);
|
|
5978
6298
|
await mkdir(parentDir, { recursive: true });
|
|
5979
|
-
const existedBeforeOpen = await pathExists(
|
|
5980
|
-
const handle = await open(
|
|
6299
|
+
const existedBeforeOpen = await pathExists(path11);
|
|
6300
|
+
const handle = await open(path11, "a");
|
|
5981
6301
|
try {
|
|
5982
6302
|
await handle.writeFile(text);
|
|
5983
6303
|
await handle.sync();
|
|
@@ -6043,9 +6363,9 @@ function validateManifestStatus(value) {
|
|
|
6043
6363
|
}
|
|
6044
6364
|
throw new Error("status must be prepared, committed, failed, or committed_manifest_failed");
|
|
6045
6365
|
}
|
|
6046
|
-
async function pathExists(
|
|
6366
|
+
async function pathExists(path11) {
|
|
6047
6367
|
try {
|
|
6048
|
-
const handle = await open(
|
|
6368
|
+
const handle = await open(path11, "r");
|
|
6049
6369
|
await handle.close();
|
|
6050
6370
|
return true;
|
|
6051
6371
|
} catch {
|
|
@@ -6108,7 +6428,7 @@ function formatIsoDate(date) {
|
|
|
6108
6428
|
}
|
|
6109
6429
|
|
|
6110
6430
|
// src/tools/greeks-attribution.ts
|
|
6111
|
-
import { z as
|
|
6431
|
+
import { z as z10 } from "zod";
|
|
6112
6432
|
|
|
6113
6433
|
// src/utils/flatfile-importer.ts
|
|
6114
6434
|
function tradingDays(from, to) {
|
|
@@ -6126,19 +6446,32 @@ function tradingDays(from, to) {
|
|
|
6126
6446
|
}
|
|
6127
6447
|
|
|
6128
6448
|
// src/tools/greeks-attribution.ts
|
|
6129
|
-
var getGreeksAttributionSchema =
|
|
6130
|
-
block_id:
|
|
6131
|
-
mode:
|
|
6132
|
-
trade_index:
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6449
|
+
var getGreeksAttributionSchema = z10.object({
|
|
6450
|
+
block_id: z10.string().describe("Block ID to analyze"),
|
|
6451
|
+
mode: z10.enum(["summary", "instance"]).default("summary").describe("summary: block-level attribution. instance: single trade time-series."),
|
|
6452
|
+
trade_index: z10.number().int().min(0).optional().describe(
|
|
6453
|
+
"Trade index (required for instance mode). Use get_block_info to find trade indices."
|
|
6454
|
+
),
|
|
6455
|
+
skip_quotes: z10.boolean().default(true).describe(
|
|
6456
|
+
"Use cached bar data only (fast). Set false to fetch NBBO quotes for higher precision."
|
|
6457
|
+
),
|
|
6458
|
+
detailed: z10.boolean().default(false).describe("false: 5 factors (delta, gamma, theta, vega, residual). true: adds charm, vanna."),
|
|
6459
|
+
strategy: z10.string().optional().describe("Filter to trades matching this strategy name (case-insensitive).")
|
|
6136
6460
|
});
|
|
6137
6461
|
var COLLAPSE_MAP = {
|
|
6138
6462
|
charm: "delta",
|
|
6139
6463
|
vanna: "vega"
|
|
6140
6464
|
};
|
|
6141
|
-
var FACTOR_ORDER = [
|
|
6465
|
+
var FACTOR_ORDER = [
|
|
6466
|
+
"theta",
|
|
6467
|
+
"vega",
|
|
6468
|
+
"delta",
|
|
6469
|
+
"gamma",
|
|
6470
|
+
"residual",
|
|
6471
|
+
"time_and_vol",
|
|
6472
|
+
"charm",
|
|
6473
|
+
"vanna"
|
|
6474
|
+
];
|
|
6142
6475
|
function collapseFactors(factors, detailed) {
|
|
6143
6476
|
const totals = /* @__PURE__ */ new Map();
|
|
6144
6477
|
for (const f of factors) {
|
|
@@ -6188,9 +6521,25 @@ async function handleGetGreeksAttribution(params, baseDir, stores, injectedConn)
|
|
|
6188
6521
|
if (trade_index == null) {
|
|
6189
6522
|
throw new Error("trade_index is required for instance mode");
|
|
6190
6523
|
}
|
|
6191
|
-
return handleInstanceMode(
|
|
6524
|
+
return handleInstanceMode(
|
|
6525
|
+
block_id,
|
|
6526
|
+
trade_index,
|
|
6527
|
+
skip_quotes,
|
|
6528
|
+
detailed,
|
|
6529
|
+
baseDir,
|
|
6530
|
+
stores,
|
|
6531
|
+
injectedConn
|
|
6532
|
+
);
|
|
6192
6533
|
}
|
|
6193
|
-
return handleSummaryMode(
|
|
6534
|
+
return handleSummaryMode(
|
|
6535
|
+
block_id,
|
|
6536
|
+
skip_quotes,
|
|
6537
|
+
detailed,
|
|
6538
|
+
strategy,
|
|
6539
|
+
baseDir,
|
|
6540
|
+
stores,
|
|
6541
|
+
injectedConn
|
|
6542
|
+
);
|
|
6194
6543
|
}
|
|
6195
6544
|
async function handleSummaryMode(block_id, skip_quotes, detailed, strategy, baseDir, stores, injectedConn) {
|
|
6196
6545
|
const conn = injectedConn ?? await getConnection(baseDir);
|
|
@@ -6242,7 +6591,10 @@ async function handleSummaryMode(block_id, skip_quotes, detailed, strategy, base
|
|
|
6242
6591
|
injectedConn
|
|
6243
6592
|
).then((result) => {
|
|
6244
6593
|
for (const factor of result.factors) {
|
|
6245
|
-
accumulated.set(
|
|
6594
|
+
accumulated.set(
|
|
6595
|
+
factor.factor,
|
|
6596
|
+
(accumulated.get(factor.factor) ?? 0) + factor.totalPnl
|
|
6597
|
+
);
|
|
6246
6598
|
}
|
|
6247
6599
|
actualTotalPnl += trade.actualPl;
|
|
6248
6600
|
markTotalPnl += result.totalPnlChange;
|
|
@@ -6345,10 +6697,20 @@ async function handleInstanceMode(block_id, trade_index, skip_quotes, detailed,
|
|
|
6345
6697
|
for (let i = 0; i <= stepCount; i++) {
|
|
6346
6698
|
const entry = {
|
|
6347
6699
|
date: getStepDate(i),
|
|
6348
|
-
delta: getStepValue(
|
|
6700
|
+
delta: getStepValue(
|
|
6701
|
+
factorSteps,
|
|
6702
|
+
"delta",
|
|
6703
|
+
i,
|
|
6704
|
+
detailed ? 0 : factorSteps.get("charm")?.[i] ?? 0
|
|
6705
|
+
),
|
|
6349
6706
|
gamma: getStepValue(factorSteps, "gamma", i, 0),
|
|
6350
6707
|
theta: getStepValue(factorSteps, "theta", i, 0),
|
|
6351
|
-
vega: getStepValue(
|
|
6708
|
+
vega: getStepValue(
|
|
6709
|
+
factorSteps,
|
|
6710
|
+
"vega",
|
|
6711
|
+
i,
|
|
6712
|
+
detailed ? 0 : factorSteps.get("vanna")?.[i] ?? 0
|
|
6713
|
+
),
|
|
6352
6714
|
residual: getStepValue(factorSteps, "residual", i, 0)
|
|
6353
6715
|
};
|
|
6354
6716
|
if (factorSteps.has("time_and_vol")) {
|
|
@@ -6815,11 +7177,7 @@ var ParquetSpotStore = class extends SpotStore {
|
|
|
6815
7177
|
}));
|
|
6816
7178
|
}
|
|
6817
7179
|
async getCoverage(ticker, from, to) {
|
|
6818
|
-
const tickerDir = path5.join(
|
|
6819
|
-
resolveMarketDir(this.ctx.dataDir),
|
|
6820
|
-
"spot",
|
|
6821
|
-
`ticker=${ticker}`
|
|
6822
|
-
);
|
|
7180
|
+
const tickerDir = path5.join(resolveMarketDir(this.ctx.dataDir), "spot", `ticker=${ticker}`);
|
|
6823
7181
|
if (!existsSync4(tickerDir)) {
|
|
6824
7182
|
return { earliest: null, latest: null, missingDates: [], totalDates: 0 };
|
|
6825
7183
|
}
|
|
@@ -7044,9 +7402,7 @@ var ParquetEnrichedStore = class extends EnrichedStore {
|
|
|
7044
7402
|
});
|
|
7045
7403
|
const reader = await this.ctx.conn.runAndReadAll(sql);
|
|
7046
7404
|
const names = reader.columnNames();
|
|
7047
|
-
return reader.getRows().map(
|
|
7048
|
-
(row) => Object.fromEntries(names.map((n, i) => [n, row[i]]))
|
|
7049
|
-
);
|
|
7405
|
+
return reader.getRows().map((row) => Object.fromEntries(names.map((n, i) => [n, row[i]])));
|
|
7050
7406
|
}
|
|
7051
7407
|
async getCoverage(ticker) {
|
|
7052
7408
|
const filePath = path6.join(
|
|
@@ -7119,9 +7475,7 @@ var DuckdbEnrichedStore = class extends EnrichedStore {
|
|
|
7119
7475
|
});
|
|
7120
7476
|
const reader = await this.ctx.conn.runAndReadAll(sql);
|
|
7121
7477
|
const names = reader.columnNames();
|
|
7122
|
-
return reader.getRows().map(
|
|
7123
|
-
(row) => Object.fromEntries(names.map((n, i) => [n, row[i]]))
|
|
7124
|
-
);
|
|
7478
|
+
return reader.getRows().map((row) => Object.fromEntries(names.map((n, i) => [n, row[i]])));
|
|
7125
7479
|
}
|
|
7126
7480
|
async getCoverage(ticker) {
|
|
7127
7481
|
const tickerLit = ticker.replace(/'/g, "''");
|
|
@@ -7474,9 +7828,7 @@ var ParquetQuoteStore = class extends QuoteStore {
|
|
|
7474
7828
|
}
|
|
7475
7829
|
async readQuotes(occTickers, from, to) {
|
|
7476
7830
|
if (occTickers.length === 0) return /* @__PURE__ */ new Map();
|
|
7477
|
-
const firstUnderlying = this.ctx.tickers.resolve(
|
|
7478
|
-
extractRoot(occTickers[0])
|
|
7479
|
-
);
|
|
7831
|
+
const firstUnderlying = this.ctx.tickers.resolve(extractRoot(occTickers[0]));
|
|
7480
7832
|
for (const t of occTickers) {
|
|
7481
7833
|
const u = this.ctx.tickers.resolve(extractRoot(t));
|
|
7482
7834
|
if (u !== firstUnderlying) {
|
|
@@ -7791,9 +8143,7 @@ var DuckdbQuoteStore = class extends QuoteStore {
|
|
|
7791
8143
|
}
|
|
7792
8144
|
async readQuotes(occTickers, from, to) {
|
|
7793
8145
|
if (occTickers.length === 0) return /* @__PURE__ */ new Map();
|
|
7794
|
-
const firstUnderlying = this.ctx.tickers.resolve(
|
|
7795
|
-
extractRoot(occTickers[0])
|
|
7796
|
-
);
|
|
8146
|
+
const firstUnderlying = this.ctx.tickers.resolve(extractRoot(occTickers[0]));
|
|
7797
8147
|
for (const t of occTickers) {
|
|
7798
8148
|
const u = this.ctx.tickers.resolve(extractRoot(t));
|
|
7799
8149
|
if (u !== firstUnderlying) {
|
|
@@ -8071,31 +8421,27 @@ function createMarketStores(ctx) {
|
|
|
8071
8421
|
}
|
|
8072
8422
|
|
|
8073
8423
|
// src/market/tickers/schemas.ts
|
|
8074
|
-
import { z as
|
|
8424
|
+
import { z as z11 } from "zod";
|
|
8075
8425
|
var TICKER_RE = /^[A-Z][A-Z0-9^_-]*$/;
|
|
8076
|
-
var UnderlyingsFileSchema =
|
|
8077
|
-
version:
|
|
8078
|
-
underlyings:
|
|
8079
|
-
|
|
8080
|
-
underlying:
|
|
8081
|
-
roots:
|
|
8426
|
+
var UnderlyingsFileSchema = z11.object({
|
|
8427
|
+
version: z11.literal(1),
|
|
8428
|
+
underlyings: z11.array(
|
|
8429
|
+
z11.object({
|
|
8430
|
+
underlying: z11.string().min(1).max(16).regex(TICKER_RE),
|
|
8431
|
+
roots: z11.array(z11.string().min(1).max(16).regex(TICKER_RE)).min(1).max(32)
|
|
8082
8432
|
})
|
|
8083
8433
|
)
|
|
8084
8434
|
});
|
|
8085
|
-
var registerUnderlyingSchema =
|
|
8086
|
-
underlying:
|
|
8087
|
-
roots:
|
|
8088
|
-
"OCC roots that resolve to this underlying, e.g. ['SPX','SPXW','SPXQ']"
|
|
8089
|
-
)
|
|
8435
|
+
var registerUnderlyingSchema = z11.object({
|
|
8436
|
+
underlying: z11.string().min(1).max(16).regex(TICKER_RE).describe("Canonical underlying symbol, e.g. SPX"),
|
|
8437
|
+
roots: z11.array(z11.string().min(1).max(16).regex(TICKER_RE)).min(1).max(32).describe("OCC roots that resolve to this underlying, e.g. ['SPX','SPXW','SPXQ']")
|
|
8090
8438
|
});
|
|
8091
|
-
var unregisterUnderlyingSchema =
|
|
8092
|
-
underlying:
|
|
8439
|
+
var unregisterUnderlyingSchema = z11.object({
|
|
8440
|
+
underlying: z11.string().min(1).max(16).regex(TICKER_RE).describe("Underlying to remove. Bundled defaults cannot be removed.")
|
|
8093
8441
|
});
|
|
8094
|
-
var listUnderlyingsSchema =
|
|
8095
|
-
var resolveRootSchema =
|
|
8096
|
-
input:
|
|
8097
|
-
"Bare root ('SPXW') or full OCC ticker ('SPXW251219C05000000')"
|
|
8098
|
-
)
|
|
8442
|
+
var listUnderlyingsSchema = z11.object({});
|
|
8443
|
+
var resolveRootSchema = z11.object({
|
|
8444
|
+
input: z11.string().min(1).max(32).describe("Bare root ('SPXW') or full OCC ticker ('SPXW251219C05000000')")
|
|
8099
8445
|
});
|
|
8100
8446
|
|
|
8101
8447
|
// src/market/tickers/registry.ts
|
|
@@ -8107,9 +8453,7 @@ function validate(underlying, roots) {
|
|
|
8107
8453
|
}
|
|
8108
8454
|
for (const r of roots) {
|
|
8109
8455
|
if (!TICKER_RE.test(r)) {
|
|
8110
|
-
throw new Error(
|
|
8111
|
-
`TickerRegistry: invalid root "${r}" \u2014 must match ${TICKER_RE.source}`
|
|
8112
|
-
);
|
|
8456
|
+
throw new Error(`TickerRegistry: invalid root "${r}" \u2014 must match ${TICKER_RE.source}`);
|
|
8113
8457
|
}
|
|
8114
8458
|
}
|
|
8115
8459
|
}
|
|
@@ -8192,9 +8536,7 @@ var TickerRegistry = class {
|
|
|
8192
8536
|
unregister(underlying) {
|
|
8193
8537
|
const entry = this.entries.get(underlying);
|
|
8194
8538
|
if (!entry) {
|
|
8195
|
-
throw new Error(
|
|
8196
|
-
`TickerRegistry.unregister: unknown underlying "${underlying}"`
|
|
8197
|
-
);
|
|
8539
|
+
throw new Error(`TickerRegistry.unregister: unknown underlying "${underlying}"`);
|
|
8198
8540
|
}
|
|
8199
8541
|
if (entry.source === "default") {
|
|
8200
8542
|
throw new Error(
|
|
@@ -8482,7 +8824,13 @@ var MarketIngestor = class {
|
|
|
8482
8824
|
assetClass
|
|
8483
8825
|
});
|
|
8484
8826
|
} catch (error) {
|
|
8485
|
-
const mapped = this.mapProviderFailure(
|
|
8827
|
+
const mapped = this.mapProviderFailure(
|
|
8828
|
+
provider,
|
|
8829
|
+
"bars",
|
|
8830
|
+
normalizedTicker,
|
|
8831
|
+
error,
|
|
8832
|
+
assetClass
|
|
8833
|
+
);
|
|
8486
8834
|
if (mapped) return mapped;
|
|
8487
8835
|
throw error;
|
|
8488
8836
|
}
|
|
@@ -8735,11 +9083,7 @@ var MarketIngestor = class {
|
|
|
8735
9083
|
if (mapped) return mapped;
|
|
8736
9084
|
throw error;
|
|
8737
9085
|
}
|
|
8738
|
-
const written = await this.writeQuotesForTicker(
|
|
8739
|
-
provider,
|
|
8740
|
-
ticker,
|
|
8741
|
-
quotes
|
|
8742
|
-
);
|
|
9086
|
+
const written = await this.writeQuotesForTicker(provider, ticker, quotes);
|
|
8743
9087
|
totalRows += written.rowsWritten;
|
|
8744
9088
|
if (written.minDate && (!minDate || written.minDate < minDate)) minDate = written.minDate;
|
|
8745
9089
|
if (written.maxDate && (!maxDate || written.maxDate > maxDate)) maxDate = written.maxDate;
|
|
@@ -8775,12 +9119,7 @@ var MarketIngestor = class {
|
|
|
8775
9119
|
for (const underlying of underlyings) {
|
|
8776
9120
|
const upperUnderlying = underlying.toUpperCase();
|
|
8777
9121
|
for (const date of dates) {
|
|
8778
|
-
const drain = await this.drainBulkQuotes(
|
|
8779
|
-
provider,
|
|
8780
|
-
upperUnderlying,
|
|
8781
|
-
date,
|
|
8782
|
-
onProgress
|
|
8783
|
-
);
|
|
9122
|
+
const drain = await this.drainBulkQuotes(provider, upperUnderlying, date, onProgress);
|
|
8784
9123
|
if (drain.rowsWritten > 0) {
|
|
8785
9124
|
totalRows += drain.rowsWritten;
|
|
8786
9125
|
if (!minDate || date < minDate) minDate = date;
|
|
@@ -8840,7 +9179,11 @@ var MarketIngestor = class {
|
|
|
8840
9179
|
totalContracts: info.totalContracts
|
|
8841
9180
|
});
|
|
8842
9181
|
} : void 0;
|
|
8843
|
-
const stream = provider.fetchBulkQuotes({
|
|
9182
|
+
const stream = provider.fetchBulkQuotes({
|
|
9183
|
+
underlying: upperUnderlying,
|
|
9184
|
+
date,
|
|
9185
|
+
onGroupComplete
|
|
9186
|
+
});
|
|
8844
9187
|
for await (const chunk of stream) {
|
|
8845
9188
|
for (const row of chunk) {
|
|
8846
9189
|
const root = extractRoot(row.ticker);
|
|
@@ -8891,15 +9234,12 @@ var MarketIngestor = class {
|
|
|
8891
9234
|
);
|
|
8892
9235
|
} catch (error) {
|
|
8893
9236
|
const message = error instanceof Error ? error.message : String(error);
|
|
8894
|
-
console.warn(
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
error: message
|
|
8901
|
-
}
|
|
8902
|
-
);
|
|
9237
|
+
console.warn("[drainBulkQuotes] enrichQuoteRows failed; skipping batch", {
|
|
9238
|
+
underlying: resolvedUnderlying,
|
|
9239
|
+
date,
|
|
9240
|
+
rows: rows.length,
|
|
9241
|
+
error: message
|
|
9242
|
+
});
|
|
8903
9243
|
skipped.push({
|
|
8904
9244
|
underlying: resolvedUnderlying,
|
|
8905
9245
|
date,
|
|
@@ -8986,16 +9326,13 @@ var MarketIngestor = class {
|
|
|
8986
9326
|
);
|
|
8987
9327
|
} catch (error) {
|
|
8988
9328
|
const message = error instanceof Error ? error.message : String(error);
|
|
8989
|
-
console.warn(
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
error: message
|
|
8997
|
-
}
|
|
8998
|
-
);
|
|
9329
|
+
console.warn("[writeQuotesForTicker] enrichQuoteRows failed; skipping batch", {
|
|
9330
|
+
underlying,
|
|
9331
|
+
date,
|
|
9332
|
+
ticker,
|
|
9333
|
+
rows: rows.length,
|
|
9334
|
+
error: message
|
|
9335
|
+
});
|
|
8999
9336
|
skipped.push({
|
|
9000
9337
|
underlying,
|
|
9001
9338
|
date,
|
|
@@ -9062,7 +9399,12 @@ var MarketIngestor = class {
|
|
|
9062
9399
|
for (const underlying of opts.underlyings) {
|
|
9063
9400
|
const upperUnderlying = underlying.toUpperCase();
|
|
9064
9401
|
const assetClass = this.detectAssetClass(upperUnderlying);
|
|
9065
|
-
const unsupported = this.preflightProviderSupport(
|
|
9402
|
+
const unsupported = this.preflightProviderSupport(
|
|
9403
|
+
provider,
|
|
9404
|
+
"chain",
|
|
9405
|
+
upperUnderlying,
|
|
9406
|
+
assetClass
|
|
9407
|
+
);
|
|
9066
9408
|
if (unsupported) return unsupported;
|
|
9067
9409
|
const dates = this.enumerateDates(opts.from, opts.to);
|
|
9068
9410
|
for (const date of dates) {
|
|
@@ -9074,7 +9416,13 @@ var MarketIngestor = class {
|
|
|
9074
9416
|
expired: true
|
|
9075
9417
|
});
|
|
9076
9418
|
} catch (error) {
|
|
9077
|
-
const mapped = this.mapProviderFailure(
|
|
9419
|
+
const mapped = this.mapProviderFailure(
|
|
9420
|
+
provider,
|
|
9421
|
+
"chain",
|
|
9422
|
+
upperUnderlying,
|
|
9423
|
+
error,
|
|
9424
|
+
assetClass
|
|
9425
|
+
);
|
|
9078
9426
|
if (mapped) return mapped;
|
|
9079
9427
|
throw error;
|
|
9080
9428
|
}
|
|
@@ -9222,43 +9570,75 @@ var MarketIngestor = class {
|
|
|
9222
9570
|
case "spot_bars": {
|
|
9223
9571
|
const ticker = opts.partition.ticker;
|
|
9224
9572
|
if (!ticker) {
|
|
9225
|
-
return {
|
|
9573
|
+
return {
|
|
9574
|
+
status: "error",
|
|
9575
|
+
rowsWritten: 0,
|
|
9576
|
+
error: "partition.ticker is required for datasetType='spot_bars'"
|
|
9577
|
+
};
|
|
9226
9578
|
}
|
|
9227
9579
|
const { rowCount } = await this.deps.stores.spot.writeFromSelect(
|
|
9228
9580
|
{ ticker: ticker.toUpperCase(), date: partitionDate },
|
|
9229
9581
|
opts.selectSql
|
|
9230
9582
|
);
|
|
9231
|
-
return {
|
|
9583
|
+
return {
|
|
9584
|
+
status: "ok",
|
|
9585
|
+
rowsWritten: rowCount,
|
|
9586
|
+
dateRange: { from: partitionDate, to: partitionDate }
|
|
9587
|
+
};
|
|
9232
9588
|
}
|
|
9233
9589
|
case "option_quotes": {
|
|
9234
9590
|
const underlying = opts.partition.underlying;
|
|
9235
9591
|
if (!underlying) {
|
|
9236
|
-
return {
|
|
9592
|
+
return {
|
|
9593
|
+
status: "error",
|
|
9594
|
+
rowsWritten: 0,
|
|
9595
|
+
error: "partition.underlying is required for datasetType='option_quotes'"
|
|
9596
|
+
};
|
|
9237
9597
|
}
|
|
9238
9598
|
const { rowCount } = await this.deps.stores.quote.writeFromSelect(
|
|
9239
9599
|
{ underlying: underlying.toUpperCase(), date: partitionDate },
|
|
9240
9600
|
opts.selectSql
|
|
9241
9601
|
);
|
|
9242
|
-
return {
|
|
9602
|
+
return {
|
|
9603
|
+
status: "ok",
|
|
9604
|
+
rowsWritten: rowCount,
|
|
9605
|
+
dateRange: { from: partitionDate, to: partitionDate }
|
|
9606
|
+
};
|
|
9243
9607
|
}
|
|
9244
9608
|
case "option_chain": {
|
|
9245
9609
|
const underlying = opts.partition.underlying;
|
|
9246
9610
|
if (!underlying) {
|
|
9247
|
-
return {
|
|
9611
|
+
return {
|
|
9612
|
+
status: "error",
|
|
9613
|
+
rowsWritten: 0,
|
|
9614
|
+
error: "partition.underlying is required for datasetType='option_chain'"
|
|
9615
|
+
};
|
|
9248
9616
|
}
|
|
9249
9617
|
const { rowCount } = await this.deps.stores.chain.writeFromSelect(
|
|
9250
9618
|
{ underlying: underlying.toUpperCase(), date: partitionDate },
|
|
9251
9619
|
opts.selectSql
|
|
9252
9620
|
);
|
|
9253
|
-
return {
|
|
9621
|
+
return {
|
|
9622
|
+
status: "ok",
|
|
9623
|
+
rowsWritten: rowCount,
|
|
9624
|
+
dateRange: { from: partitionDate, to: partitionDate }
|
|
9625
|
+
};
|
|
9254
9626
|
}
|
|
9255
9627
|
default: {
|
|
9256
9628
|
const _exhaustive = opts.datasetType;
|
|
9257
|
-
return {
|
|
9629
|
+
return {
|
|
9630
|
+
status: "error",
|
|
9631
|
+
rowsWritten: 0,
|
|
9632
|
+
error: `Unknown datasetType: ${String(_exhaustive)}`
|
|
9633
|
+
};
|
|
9258
9634
|
}
|
|
9259
9635
|
}
|
|
9260
9636
|
} catch (err) {
|
|
9261
|
-
return {
|
|
9637
|
+
return {
|
|
9638
|
+
status: "error",
|
|
9639
|
+
rowsWritten: 0,
|
|
9640
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9641
|
+
};
|
|
9262
9642
|
}
|
|
9263
9643
|
}
|
|
9264
9644
|
async computeVixContext(opts) {
|
|
@@ -9348,7 +9728,11 @@ var MarketIngestor = class {
|
|
|
9348
9728
|
const coverage = {};
|
|
9349
9729
|
for (const ticker of opts.spotTickers) {
|
|
9350
9730
|
try {
|
|
9351
|
-
const cov = await this.deps.stores.spot.getCoverage(
|
|
9731
|
+
const cov = await this.deps.stores.spot.getCoverage(
|
|
9732
|
+
ticker.toUpperCase(),
|
|
9733
|
+
opts.asOf,
|
|
9734
|
+
opts.asOf
|
|
9735
|
+
);
|
|
9352
9736
|
coverage[ticker] = {
|
|
9353
9737
|
totalDates: cov.totalDates,
|
|
9354
9738
|
dateRange: cov.earliest && cov.latest ? { from: cov.earliest, to: cov.latest } : void 0
|
|
@@ -9372,7 +9756,13 @@ var MarketIngestor = class {
|
|
|
9372
9756
|
else status = "ok";
|
|
9373
9757
|
return {
|
|
9374
9758
|
status,
|
|
9375
|
-
perOperation: {
|
|
9759
|
+
perOperation: {
|
|
9760
|
+
spot: spotResults,
|
|
9761
|
+
chain: chainResults,
|
|
9762
|
+
quotes: quoteResults,
|
|
9763
|
+
openInterest: openInterestResults,
|
|
9764
|
+
vixContext
|
|
9765
|
+
},
|
|
9376
9766
|
coverage,
|
|
9377
9767
|
errors,
|
|
9378
9768
|
...aggregateSkipped.length > 0 ? { skipped: aggregateSkipped } : {}
|
|
@@ -9455,9 +9845,7 @@ function selectVerificationSampleDates(fromDate = "2022-01-01", toDate = (/* @__
|
|
|
9455
9845
|
...PHASE_5_KNOWN_EVENTS.map((s) => s.date),
|
|
9456
9846
|
...PHASE_5_STRUCTURAL_DATES.map((s) => s.date)
|
|
9457
9847
|
]);
|
|
9458
|
-
const candidates = enumerateWeekdays(fromDate, toDate).filter(
|
|
9459
|
-
(d) => !selectedSet.has(d)
|
|
9460
|
-
);
|
|
9848
|
+
const candidates = enumerateWeekdays(fromDate, toDate).filter((d) => !selectedSet.has(d));
|
|
9461
9849
|
const random = [];
|
|
9462
9850
|
const pool = [...candidates];
|
|
9463
9851
|
for (let i = 0; i < randomCount && pool.length > 0; i++) {
|
|
@@ -9582,11 +9970,10 @@ function compareRow(oldRow, newRow, kind, ticker, date) {
|
|
|
9582
9970
|
}
|
|
9583
9971
|
|
|
9584
9972
|
// src/utils/calibration-probe.ts
|
|
9585
|
-
import * as path11 from "path";
|
|
9586
9973
|
import { DuckDBInstance } from "@duckdb/node-api";
|
|
9587
9974
|
async function calibrateProviderFetch(ticker, probeDates, dataRoot) {
|
|
9588
9975
|
const provider = getProvider();
|
|
9589
|
-
const dbPath =
|
|
9976
|
+
const dbPath = resolveDbPath(dataRoot, "market");
|
|
9590
9977
|
const instance = await DuckDBInstance.create(dbPath);
|
|
9591
9978
|
const conn = await instance.connect();
|
|
9592
9979
|
const deltas = [];
|
|
@@ -9701,6 +10088,7 @@ export {
|
|
|
9701
10088
|
appendBackfillManifestLineDurable,
|
|
9702
10089
|
applyQuoteGreeks,
|
|
9703
10090
|
applyQuoteGreeksParallel,
|
|
10091
|
+
armHoldingPeriods,
|
|
9704
10092
|
assessPrecision,
|
|
9705
10093
|
bachelierDelta,
|
|
9706
10094
|
bachelierGamma,
|
|
@@ -9717,6 +10105,8 @@ export {
|
|
|
9717
10105
|
bsPrice,
|
|
9718
10106
|
bsTheta,
|
|
9719
10107
|
bsVega,
|
|
10108
|
+
buildArmDaySeries,
|
|
10109
|
+
buildBlockTradingDayIndex,
|
|
9720
10110
|
buildEnrichmentPlan,
|
|
9721
10111
|
buildFilterPredicate,
|
|
9722
10112
|
buildLookaheadFreeQuery,
|
|
@@ -9884,6 +10274,7 @@ export {
|
|
|
9884
10274
|
optionHistoryQuote,
|
|
9885
10275
|
optionHistoryQuoteBand,
|
|
9886
10276
|
optionListContracts,
|
|
10277
|
+
pairedComparisonInputSchema,
|
|
9887
10278
|
parseBackfillOccTicker,
|
|
9888
10279
|
parseCsvToBars,
|
|
9889
10280
|
parseDatabaseRowsToBars,
|
|
@@ -9898,6 +10289,7 @@ export {
|
|
|
9898
10289
|
readJsonFile,
|
|
9899
10290
|
readParquetFilesSql,
|
|
9900
10291
|
regimeAllocationAdvisorSchema,
|
|
10292
|
+
registerPairedComparisonTool,
|
|
9901
10293
|
registerTickerTools,
|
|
9902
10294
|
registerUnderlyingSchema,
|
|
9903
10295
|
replayTradeSchema,
|
|
@@ -9915,6 +10307,7 @@ export {
|
|
|
9915
10307
|
rthDailyAggregateSubquery,
|
|
9916
10308
|
runContextEnrichment,
|
|
9917
10309
|
runEnrichment,
|
|
10310
|
+
runPairedBootstrapComparison,
|
|
9918
10311
|
saveUserOverride,
|
|
9919
10312
|
scoreDataQuality,
|
|
9920
10313
|
selectVerificationSampleDates,
|