tradeblocks-mcp 3.0.2 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -68
- package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
- package/dist/chunk-27S67XW3.js.map +1 -0
- package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
- package/dist/chunk-2E63THNI.js.map +1 -0
- package/dist/{chunk-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
- package/dist/chunk-6S37CXUA.js.map +1 -0
- package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
- package/dist/chunk-AP7IHUUR.js.map +1 -0
- package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
- package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
- package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
- package/dist/chunk-SEUZYQGQ.js.map +1 -0
- package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
- package/dist/chunk-UBUC5A66.js.map +1 -0
- package/dist/{chunk-XXYOUIZY.js → chunk-VDU25Z6X.js} +63 -80
- package/dist/chunk-VDU25Z6X.js.map +1 -0
- package/dist/daily-log-processor-BY3ISY6K.js +9 -0
- package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
- package/dist/iv-solver-worker.js +9 -1
- package/dist/market-provider-H3ARIVZ4.js +16 -0
- package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
- package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
- package/dist/test-exports.js +358 -254
- package/dist/test-exports.js.map +1 -1
- package/dist/trade-processor-EYA3I3XB.js +9 -0
- package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
- package/manifest.json +3 -11
- package/package.json +1 -1
- package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
- package/server/chunk-4P7D7YZP.js.map +1 -0
- package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
- package/server/chunk-BOPHW5M6.js.map +1 -0
- package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
- package/server/chunk-GH2552SE.js.map +1 -0
- package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
- package/server/chunk-OBYKFW2B.js.map +1 -0
- package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
- package/server/chunk-T66KH2XH.js.map +1 -0
- package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
- package/server/chunk-W5E7FHC4.js.map +1 -0
- package/server/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
- package/server/chunk-YUCOAJ4Z.js.map +1 -0
- package/server/{chunk-PNKG7RY7.js → chunk-ZBJCF4ZG.js} +63 -80
- package/server/chunk-ZBJCF4ZG.js.map +1 -0
- package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
- package/server/config-DK7KOMNL.js.map +1 -0
- package/server/daily-log-processor-ENEUT22A.js +10 -0
- package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
- package/server/http-server.js +2 -5
- package/server/index.js +1771 -1538
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +9 -1
- package/server/market-provider-B437HKLW.js +17 -0
- package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
- package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
- package/server/trade-processor-L3PIQ5TG.js +10 -0
- package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
- package/src/auth/clients-store.ts +4 -4
- package/src/auth/code-store.ts +1 -1
- package/src/auth/config.ts +11 -12
- package/src/auth/login-page.ts +8 -10
- package/src/auth/provider.ts +35 -30
- package/src/auth/token.ts +17 -15
- package/src/db/backtest-schemas.ts +12 -4
- package/src/db/connection.ts +107 -40
- package/src/db/index.ts +37 -5
- package/src/db/json-adapters.ts +22 -31
- package/src/db/json-migration.ts +11 -15
- package/src/db/json-store.ts +5 -3
- package/src/db/market-datasets.ts +48 -15
- package/src/db/market-schemas.ts +11 -5
- package/src/db/market-views.ts +119 -32
- package/src/db/parquet-writer.ts +7 -7
- package/src/db/profile-schemas.ts +39 -22
- package/src/db/schemas.ts +2 -2
- package/src/http-server.ts +7 -12
- package/src/index.ts +8 -9
- package/src/market/ingestor/index.ts +1 -4
- package/src/market/ingestor/market-ingestor.ts +140 -67
- package/src/market/ingestor/types.ts +1 -3
- package/src/market/stores/chain-sql.ts +2 -8
- package/src/market/stores/chain-store.ts +1 -4
- package/src/market/stores/duckdb-chain-store.ts +3 -14
- package/src/market/stores/duckdb-enriched-store.ts +3 -13
- package/src/market/stores/duckdb-quote-store.ts +10 -27
- package/src/market/stores/duckdb-spot-store.ts +4 -20
- package/src/market/stores/enriched-sql.ts +3 -9
- package/src/market/stores/enriched-store.ts +2 -2
- package/src/market/stores/parquet-chain-store.ts +5 -22
- package/src/market/stores/parquet-enriched-store.ts +4 -16
- package/src/market/stores/parquet-oi-daily-store.ts +3 -14
- package/src/market/stores/parquet-quote-store.ts +9 -29
- package/src/market/stores/parquet-spot-store.ts +14 -33
- package/src/market/stores/quote-store.ts +2 -10
- package/src/market/stores/spot-sql.ts +3 -15
- package/src/market/stores/spot-store.ts +1 -1
- package/src/market/tickers/defaults.json +6 -6
- package/src/market/tickers/loader.ts +2 -7
- package/src/market/tickers/registry.ts +5 -15
- package/src/market/tickers/schemas.ts +2 -6
- package/src/models/strategy-profile.ts +39 -39
- package/src/sync/block-sync.ts +44 -65
- package/src/sync/index.ts +1 -4
- package/src/sync/metadata.ts +14 -20
- package/src/test-exports.ts +127 -123
- package/src/tools/analysis.ts +180 -159
- package/src/tools/batch-exit-analysis.ts +100 -59
- package/src/tools/blocks/analysis.ts +116 -101
- package/src/tools/blocks/comparison.ts +281 -364
- package/src/tools/blocks/core.ts +208 -253
- package/src/tools/blocks/health.ts +571 -563
- package/src/tools/blocks/similarity.ts +316 -283
- package/src/tools/edge-decay.ts +124 -197
- package/src/tools/exit-analysis.ts +130 -77
- package/src/tools/greeks-attribution.ts +84 -35
- package/src/tools/guides.ts +4 -6
- package/src/tools/imports.ts +9 -12
- package/src/tools/market-data.ts +1067 -757
- package/src/tools/market-enrichment.ts +3 -3
- package/src/tools/market-fetch.ts +148 -67
- package/src/tools/market-imports.ts +12 -12
- package/src/tools/middleware/sync-middleware.ts +5 -6
- package/src/tools/performance.ts +185 -302
- package/src/tools/profile-analysis.ts +52 -66
- package/src/tools/profiles.ts +106 -69
- package/src/tools/regime-advisor.ts +20 -45
- package/src/tools/replay.ts +81 -77
- package/src/tools/reports/discrepancies.ts +298 -328
- package/src/tools/reports/fields.ts +7 -25
- package/src/tools/reports/helpers.ts +18 -49
- package/src/tools/reports/predictive.ts +27 -70
- package/src/tools/reports/slippage-trends.ts +315 -345
- package/src/tools/reports/slippage.ts +1 -4
- package/src/tools/reports/strategy-matches.ts +399 -441
- package/src/tools/schema.ts +43 -40
- package/src/tools/shared/filters.ts +3 -9
- package/src/tools/snapshot.ts +9 -30
- package/src/tools/sql.ts +15 -14
- package/src/tools/tickers.ts +1 -4
- package/src/utils/batch-exit-analysis.ts +31 -42
- package/src/utils/black-scholes.ts +39 -29
- package/src/utils/block-loader.ts +68 -82
- package/src/utils/calibration-probe.ts +1 -2
- package/src/utils/chain-loader.ts +3 -3
- package/src/utils/csv-discovery.ts +16 -22
- package/src/utils/data-quality.ts +24 -36
- package/src/utils/exit-triggers.ts +91 -96
- package/src/utils/field-timing.ts +94 -79
- package/src/utils/filter-predicates.ts +13 -9
- package/src/utils/flatfile-importer.ts +94 -64
- package/src/utils/greeks-decomposition.ts +152 -100
- package/src/utils/iv-solver-pool.ts +55 -25
- package/src/utils/iv-solver-worker.ts +5 -5
- package/src/utils/market-enricher.ts +528 -497
- package/src/utils/market-importer.ts +31 -12
- package/src/utils/market-provider.ts +21 -23
- package/src/utils/massive-tier.ts +5 -7
- package/src/utils/migrate-option-data-helpers.ts +2 -8
- package/src/utils/option-quote-greeks.ts +25 -31
- package/src/utils/option-time.ts +4 -8
- package/src/utils/output-formatter.ts +1 -4
- package/src/utils/provider-capabilities.ts +1 -4
- package/src/utils/providers/massive.ts +59 -93
- package/src/utils/providers/thetadata/backfill.ts +14 -23
- package/src/utils/providers/thetadata/client.ts +12 -8
- package/src/utils/providers/thetadata/decode.ts +2 -20
- package/src/utils/providers/thetadata/endpoints.ts +32 -40
- package/src/utils/providers/thetadata/join.ts +11 -10
- package/src/utils/providers/thetadata/proto.ts +12 -10
- package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
- package/src/utils/providers/thetadata.ts +11 -10
- package/src/utils/quote-enricher.ts +4 -4
- package/src/utils/quote-parquet-projection.ts +3 -11
- package/src/utils/sample-date-selector.ts +3 -5
- package/src/utils/schema-metadata.ts +102 -70
- package/src/utils/ticker.ts +5 -9
- package/src/utils/trade-replay.ts +77 -68
- package/dist/chunk-4BLCXNQ6.js.map +0 -1
- package/dist/chunk-BKQ4PM4Y.js.map +0 -1
- package/dist/chunk-FGZH632F.js.map +0 -1
- package/dist/chunk-LDKTV7GW.js.map +0 -1
- package/dist/chunk-PRAYH3RT.js.map +0 -1
- package/dist/chunk-W2PP3LEH.js.map +0 -1
- package/dist/chunk-XXYOUIZY.js.map +0 -1
- package/dist/daily-log-processor-6MWJ23JK.js +0 -9
- package/dist/market-provider-VDRJUEF2.js +0 -16
- package/dist/trade-processor-NHU4VWRX.js +0 -9
- package/server/chunk-5EBXHT6C.js.map +0 -1
- package/server/chunk-72GKJE2U.js.map +0 -1
- package/server/chunk-CSDVJPBB.js.map +0 -1
- package/server/chunk-FBNDMCT5.js.map +0 -1
- package/server/chunk-NRFXAJF7.js.map +0 -1
- package/server/chunk-NZO6PT64.js.map +0 -1
- package/server/chunk-PNKG7RY7.js.map +0 -1
- package/server/chunk-WA5AAPCH.js.map +0 -1
- package/server/config-6IZXEFEX.js.map +0 -1
- package/server/daily-log-processor-Y3PVSVBM.js +0 -10
- package/server/market-provider-VOYYVYWT.js +0 -17
- package/server/trade-processor-JWVS37KM.js +0 -10
- /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-BY3ISY6K.js.map} +0 -0
- /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
- /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-H3ARIVZ4.js.map} +0 -0
- /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
- /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
- /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.js.map} +0 -0
- /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
- /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-ENEUT22A.js.map} +0 -0
- /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
- /package/server/{market-provider-VOYYVYWT.js.map → market-provider-B437HKLW.js.map} +0 -0
- /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
- /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
- /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.js.map} +0 -0
- /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
package/dist/test-exports.js
CHANGED
|
@@ -80,7 +80,7 @@ import {
|
|
|
80
80
|
writeParquetPartition,
|
|
81
81
|
writeQuoteMinutesPartition,
|
|
82
82
|
writeSpotPartition
|
|
83
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-UBUC5A66.js";
|
|
84
84
|
import {
|
|
85
85
|
BACHELIER_DTE_THRESHOLD,
|
|
86
86
|
IvSolverPool,
|
|
@@ -156,17 +156,17 @@ import {
|
|
|
156
156
|
stockHistoryOhlc,
|
|
157
157
|
thetaTimestampToEtMinute,
|
|
158
158
|
toMassiveTicker
|
|
159
|
-
} from "./chunk-
|
|
159
|
+
} from "./chunk-VDU25Z6X.js";
|
|
160
160
|
import {
|
|
161
161
|
PortfolioStatsCalculator,
|
|
162
162
|
calculateCorrelationMatrix,
|
|
163
163
|
performTailRiskAnalysis
|
|
164
|
-
} from "./chunk-
|
|
165
|
-
import "./chunk-
|
|
166
|
-
import "./chunk-
|
|
167
|
-
import "./chunk-
|
|
168
|
-
import "./chunk-
|
|
169
|
-
import "./chunk-
|
|
164
|
+
} from "./chunk-C6LL746C.js";
|
|
165
|
+
import "./chunk-AP7IHUUR.js";
|
|
166
|
+
import "./chunk-SEUZYQGQ.js";
|
|
167
|
+
import "./chunk-6S37CXUA.js";
|
|
168
|
+
import "./chunk-27S67XW3.js";
|
|
169
|
+
import "./chunk-2E63THNI.js";
|
|
170
170
|
import "./chunk-5WRI5ZAA.js";
|
|
171
171
|
|
|
172
172
|
// src/utils/trading-dates.ts
|
|
@@ -189,9 +189,7 @@ function yesterdayET(now = /* @__PURE__ */ new Date()) {
|
|
|
189
189
|
// src/tools/shared/filters.ts
|
|
190
190
|
function filterByStrategy(trades, strategy) {
|
|
191
191
|
if (!strategy) return trades;
|
|
192
|
-
return trades.filter(
|
|
193
|
-
(t) => t.strategy.toLowerCase() === strategy.toLowerCase()
|
|
194
|
-
);
|
|
192
|
+
return trades.filter((t) => t.strategy.toLowerCase() === strategy.toLowerCase());
|
|
195
193
|
}
|
|
196
194
|
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
197
195
|
function validateDateParam(date) {
|
|
@@ -798,10 +796,22 @@ var VIX_OHLCV_MAPPINGS = [
|
|
|
798
796
|
{ alias: "VIX_Low", tableAlias: "vix", sourceCol: "low", ticker: "VIX", timing: "close" },
|
|
799
797
|
// VIX9D
|
|
800
798
|
{ alias: "VIX9D_Open", tableAlias: "vix9d", sourceCol: "open", ticker: "VIX9D", timing: "open" },
|
|
801
|
-
{
|
|
799
|
+
{
|
|
800
|
+
alias: "VIX9D_Close",
|
|
801
|
+
tableAlias: "vix9d",
|
|
802
|
+
sourceCol: "close",
|
|
803
|
+
ticker: "VIX9D",
|
|
804
|
+
timing: "close"
|
|
805
|
+
},
|
|
802
806
|
// VIX3M
|
|
803
807
|
{ alias: "VIX3M_Open", tableAlias: "vix3m", sourceCol: "open", ticker: "VIX3M", timing: "open" },
|
|
804
|
-
{
|
|
808
|
+
{
|
|
809
|
+
alias: "VIX3M_Close",
|
|
810
|
+
tableAlias: "vix3m",
|
|
811
|
+
sourceCol: "close",
|
|
812
|
+
ticker: "VIX3M",
|
|
813
|
+
timing: "close"
|
|
814
|
+
}
|
|
805
815
|
];
|
|
806
816
|
var VIX_ENRICHED_MAPPINGS = [
|
|
807
817
|
{ alias: "VIX_IVR", tableAlias: "evix", sourceCol: "ivr", ticker: "VIX", timing: "close" },
|
|
@@ -811,10 +821,7 @@ var VIX_ENRICHED_MAPPINGS = [
|
|
|
811
821
|
{ alias: "VIX3M_IVR", tableAlias: "evix3m", sourceCol: "ivr", ticker: "VIX3M", timing: "close" },
|
|
812
822
|
{ alias: "VIX3M_IVP", tableAlias: "evix3m", sourceCol: "ivp", ticker: "VIX3M", timing: "close" }
|
|
813
823
|
];
|
|
814
|
-
var VIX_ALL_MAPPINGS = [
|
|
815
|
-
...VIX_OHLCV_MAPPINGS,
|
|
816
|
-
...VIX_ENRICHED_MAPPINGS
|
|
817
|
-
];
|
|
824
|
+
var VIX_ALL_MAPPINGS = [...VIX_OHLCV_MAPPINGS, ...VIX_ENRICHED_MAPPINGS];
|
|
818
825
|
var VIX_TICKER_ALIASES = [...new Set(VIX_OHLCV_MAPPINGS.map((m) => m.tableAlias))];
|
|
819
826
|
var VIX_TICKER_FOR_ALIAS = Object.fromEntries([
|
|
820
827
|
...VIX_OHLCV_MAPPINGS.map((m) => [m.tableAlias, m.ticker]),
|
|
@@ -851,9 +858,7 @@ var CLOSE_KNOWN_FIELDS = /* @__PURE__ */ new Set([
|
|
|
851
858
|
...DAILY_CLOSE_FIELDS,
|
|
852
859
|
...CONTEXT_CLOSE_FIELDS
|
|
853
860
|
]);
|
|
854
|
-
var STATIC_FIELDS = /* @__PURE__ */ new Set([
|
|
855
|
-
...DAILY_STATIC_FIELDS
|
|
856
|
-
]);
|
|
861
|
+
var STATIC_FIELDS = /* @__PURE__ */ new Set([...DAILY_STATIC_FIELDS]);
|
|
857
862
|
function buildVixJoinClause(tickerAliases, baseAlias = "d") {
|
|
858
863
|
return tickerAliases.flatMap((alias) => {
|
|
859
864
|
const ticker = VIX_TICKER_FOR_ALIAS[alias];
|
|
@@ -978,7 +983,11 @@ function buildOutcomeQuery(tradeDatesOrKeys) {
|
|
|
978
983
|
if (typeof tradeDatesOrKeys[0] === "string") {
|
|
979
984
|
return buildOutcomeQueryForDates(tradeDatesOrKeys, vixCloseCols, derivedCloseCols);
|
|
980
985
|
}
|
|
981
|
-
return buildOutcomeQueryForKeys(
|
|
986
|
+
return buildOutcomeQueryForKeys(
|
|
987
|
+
tradeDatesOrKeys,
|
|
988
|
+
vixCloseCols,
|
|
989
|
+
derivedCloseCols
|
|
990
|
+
);
|
|
982
991
|
}
|
|
983
992
|
function buildTargetCloseCols(eAlias, sAlias) {
|
|
984
993
|
return [...DAILY_CLOSE_FIELDS].map((f) => `${OHLCV_COLS.has(f) ? sAlias : eAlias}."${f}"`).join(", ");
|
|
@@ -1098,11 +1107,11 @@ function scoreDataQuality(input) {
|
|
|
1098
1107
|
async function queryCoverage(stores, underlying, fromDate, toDate) {
|
|
1099
1108
|
const spotCov = await stores.spot.getCoverage(underlying, fromDate, toDate);
|
|
1100
1109
|
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(
|
|
1110
|
+
const dates = enumerateCoveredDates(spotCov.earliest, spotCov.latest).concat(
|
|
1104
1111
|
enumerateCoveredDates(quoteCov.earliest, quoteCov.latest)
|
|
1105
1112
|
);
|
|
1113
|
+
const uniqueDates = [...new Set(dates)].sort();
|
|
1114
|
+
const quoteDateSet = new Set(enumerateCoveredDates(quoteCov.earliest, quoteCov.latest));
|
|
1106
1115
|
if (uniqueDates.length === 0) {
|
|
1107
1116
|
return {
|
|
1108
1117
|
totalBars: 0,
|
|
@@ -1168,9 +1177,7 @@ function formatCoverageReport(tickerPattern, coverage) {
|
|
|
1168
1177
|
}
|
|
1169
1178
|
if (current) groups.push(current);
|
|
1170
1179
|
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;
|
|
1180
|
+
const avgBars = group.barCounts.length > 0 ? Math.round(group.barCounts.reduce((s, v) => s + v, 0) / group.barCounts.length) : 0;
|
|
1174
1181
|
const rangeStr = group.fromDate === group.toDate ? group.fromDate : `${group.fromDate} to ${group.toDate}`;
|
|
1175
1182
|
let detail;
|
|
1176
1183
|
if (group.density === "dense") {
|
|
@@ -1445,7 +1452,9 @@ async function setupParquetWorkingTables(conn, dataDir) {
|
|
|
1445
1452
|
const enrichedTickerGlob = path.join(enrichedDir, "ticker=*", "data.parquet");
|
|
1446
1453
|
const enrichedContextPath = path.join(enrichedDir, "context", "data.parquet");
|
|
1447
1454
|
if (existsSync(dailyPath)) {
|
|
1448
|
-
await conn.run(
|
|
1455
|
+
await conn.run(
|
|
1456
|
+
`CREATE TEMP TABLE "${dailyTable}" AS SELECT * FROM read_parquet('${dailyPath}')`
|
|
1457
|
+
);
|
|
1449
1458
|
await alignDailyWorkingTableColumns(conn, dailyTable);
|
|
1450
1459
|
} else if (hasEnrichedTickerFiles(enrichedDir)) {
|
|
1451
1460
|
await conn.run(
|
|
@@ -1460,9 +1469,7 @@ async function setupParquetWorkingTables(conn, dataDir) {
|
|
|
1460
1469
|
}
|
|
1461
1470
|
await alignDailyWorkingTableColumns(conn, dailyTable);
|
|
1462
1471
|
} else {
|
|
1463
|
-
await conn.run(
|
|
1464
|
-
`CREATE TEMP TABLE "${dailyTable}" AS SELECT * FROM market.enriched WHERE 1=0`
|
|
1465
|
-
);
|
|
1472
|
+
await conn.run(`CREATE TEMP TABLE "${dailyTable}" AS SELECT * FROM market.enriched WHERE 1=0`);
|
|
1466
1473
|
for (const ohlcv of ["open", "high", "low", "close"]) {
|
|
1467
1474
|
try {
|
|
1468
1475
|
await conn.run(`ALTER TABLE "${dailyTable}" ADD COLUMN "${ohlcv}" DOUBLE`);
|
|
@@ -1485,7 +1492,9 @@ async function setupParquetWorkingTables(conn, dataDir) {
|
|
|
1485
1492
|
} catch {
|
|
1486
1493
|
}
|
|
1487
1494
|
if (existsSync(dateContextPath)) {
|
|
1488
|
-
await conn.run(
|
|
1495
|
+
await conn.run(
|
|
1496
|
+
`CREATE TEMP TABLE "${dateContextTable}" AS SELECT * FROM read_parquet('${dateContextPath}')`
|
|
1497
|
+
);
|
|
1489
1498
|
} else if (existsSync(enrichedContextPath)) {
|
|
1490
1499
|
await conn.run(
|
|
1491
1500
|
`CREATE TEMP TABLE "${dateContextTable}" AS SELECT * FROM read_parquet('${enrichedContextPath}')`
|
|
@@ -1496,7 +1505,9 @@ async function setupParquetWorkingTables(conn, dataDir) {
|
|
|
1496
1505
|
Trend_Direction VARCHAR, VIX_Spike_Pct DOUBLE, VIX_Gap_Pct DOUBLE
|
|
1497
1506
|
)`);
|
|
1498
1507
|
}
|
|
1499
|
-
await conn.run(
|
|
1508
|
+
await conn.run(
|
|
1509
|
+
`CREATE UNIQUE INDEX "idx_${dateContextTable}_date" ON "${dateContextTable}"(date)`
|
|
1510
|
+
);
|
|
1500
1511
|
await conn.run(`CREATE UNIQUE INDEX "idx_${dailyTable}_pk" ON "${dailyTable}"(ticker, date)`);
|
|
1501
1512
|
return { dailyTable, dateContextTable };
|
|
1502
1513
|
}
|
|
@@ -1523,12 +1534,7 @@ async function flushEnrichedToParquet(conn, dataDir, ticker, tables) {
|
|
|
1523
1534
|
targetPath: tickerFile,
|
|
1524
1535
|
selectQuery: `SELECT ticker, date, ${enrichedColList} FROM "${tables.dailyTable}" WHERE ticker = '${ticker}' ORDER BY date`
|
|
1525
1536
|
});
|
|
1526
|
-
const contextFile = path.join(
|
|
1527
|
-
resolveMarketDir(dataDir),
|
|
1528
|
-
"enriched",
|
|
1529
|
-
"context",
|
|
1530
|
-
"data.parquet"
|
|
1531
|
-
);
|
|
1537
|
+
const contextFile = path.join(resolveMarketDir(dataDir), "enriched", "context", "data.parquet");
|
|
1532
1538
|
await writeParquetAtomic(conn, {
|
|
1533
1539
|
targetPath: contextFile,
|
|
1534
1540
|
selectQuery: `SELECT * FROM "${tables.dateContextTable}" ORDER BY date`
|
|
@@ -1564,7 +1570,9 @@ async function runTier2(conn, targets, spotStore) {
|
|
|
1564
1570
|
const BATCH_SIZE = 500;
|
|
1565
1571
|
for (let start = 0; start < bars.length; start += BATCH_SIZE) {
|
|
1566
1572
|
const batch = bars.slice(start, start + BATCH_SIZE);
|
|
1567
|
-
const placeholders = batch.map(
|
|
1573
|
+
const placeholders = batch.map(
|
|
1574
|
+
(_, i) => `($${i * 6 + 1},$${i * 6 + 2},$${i * 6 + 3},$${i * 6 + 4},$${i * 6 + 5},$${i * 6 + 6})`
|
|
1575
|
+
).join(",");
|
|
1568
1576
|
const params = batch.flatMap((b) => [b.ticker, b.date, b.open, b.high, b.low, b.close]);
|
|
1569
1577
|
await conn.run(
|
|
1570
1578
|
`INSERT INTO "${vixTempTable}" VALUES ${placeholders}`,
|
|
@@ -1696,12 +1704,21 @@ async function runTier2(conn, targets, spotStore) {
|
|
|
1696
1704
|
};
|
|
1697
1705
|
});
|
|
1698
1706
|
const enrichedContext = computeVIXDerivedFields(contextRows);
|
|
1699
|
-
const derivedCols = [
|
|
1707
|
+
const derivedCols = [
|
|
1708
|
+
"date",
|
|
1709
|
+
"Vol_Regime",
|
|
1710
|
+
"Term_Structure_State",
|
|
1711
|
+
"Trend_Direction",
|
|
1712
|
+
"VIX_Spike_Pct",
|
|
1713
|
+
"VIX_Gap_Pct"
|
|
1714
|
+
];
|
|
1700
1715
|
const BATCH_SIZE = 500;
|
|
1701
1716
|
for (let start = 0; start < enrichedContext.length; start += BATCH_SIZE) {
|
|
1702
1717
|
const batch = enrichedContext.slice(start, start + BATCH_SIZE);
|
|
1703
1718
|
const placeholders = batch.map((_, rowIdx) => {
|
|
1704
|
-
const params2 = derivedCols.map(
|
|
1719
|
+
const params2 = derivedCols.map(
|
|
1720
|
+
(__, colIdx) => `$${rowIdx * derivedCols.length + colIdx + 1}`
|
|
1721
|
+
);
|
|
1705
1722
|
return `(${params2.join(", ")})`;
|
|
1706
1723
|
}).join(", ");
|
|
1707
1724
|
const sql = `INSERT OR REPLACE INTO ${dateContextTarget} (${derivedCols.join(", ")}) VALUES ${placeholders}`;
|
|
@@ -1735,10 +1752,9 @@ async function hasTier3Data(conn, ticker, spotStore) {
|
|
|
1735
1752
|
const cov = await spotStore.getCoverage(ticker, "1970-01-01", "9999-12-31");
|
|
1736
1753
|
return cov.totalDates > 0;
|
|
1737
1754
|
}
|
|
1738
|
-
const r = await conn.runAndReadAll(
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
);
|
|
1755
|
+
const r = await conn.runAndReadAll(`SELECT COUNT(*) FROM market.spot WHERE ticker = $1 LIMIT 1`, [
|
|
1756
|
+
ticker
|
|
1757
|
+
]);
|
|
1742
1758
|
return Number(r.getRows()[0]?.[0] ?? 0) > 0;
|
|
1743
1759
|
}
|
|
1744
1760
|
async function runContextEnrichment(conn, targets) {
|
|
@@ -1778,7 +1794,10 @@ async function runEnrichment(conn, ticker, opts = {}, io) {
|
|
|
1778
1794
|
fetchParams.push(lookbackStart);
|
|
1779
1795
|
}
|
|
1780
1796
|
fetchSql += ` ORDER BY date ASC`;
|
|
1781
|
-
const rawReader = await conn.runAndReadAll(
|
|
1797
|
+
const rawReader = await conn.runAndReadAll(
|
|
1798
|
+
fetchSql,
|
|
1799
|
+
fetchParams
|
|
1800
|
+
);
|
|
1782
1801
|
rawRows = rawReader.getRows();
|
|
1783
1802
|
}
|
|
1784
1803
|
if (rawRows.length === 0) {
|
|
@@ -1995,7 +2014,8 @@ function computeIntradayTimingFields(bars) {
|
|
|
1995
2014
|
const highInAfternoon = highTime >= 12;
|
|
1996
2015
|
const lowInAfternoon = lowTime >= 12;
|
|
1997
2016
|
let reversalType = 0;
|
|
1998
|
-
if (highInMorning && lowInAfternoon)
|
|
2017
|
+
if (highInMorning && lowInAfternoon)
|
|
2018
|
+
reversalType = 1;
|
|
1999
2019
|
else if (lowInMorning && highInAfternoon) reversalType = -1;
|
|
2000
2020
|
const openingBars = bars.filter((b) => hhmmToDecimalHours(b.time) < 10);
|
|
2001
2021
|
let openingDriveStrength = 0;
|
|
@@ -2020,7 +2040,14 @@ function computeIntradayTimingFields(bars) {
|
|
|
2020
2040
|
intradayRealizedVol = barStdDev * Math.sqrt(bars.length * 252);
|
|
2021
2041
|
}
|
|
2022
2042
|
}
|
|
2023
|
-
return {
|
|
2043
|
+
return {
|
|
2044
|
+
highTime,
|
|
2045
|
+
lowTime,
|
|
2046
|
+
highBeforeLow,
|
|
2047
|
+
reversalType,
|
|
2048
|
+
openingDriveStrength,
|
|
2049
|
+
intradayRealizedVol
|
|
2050
|
+
};
|
|
2024
2051
|
}
|
|
2025
2052
|
async function runTier3(conn, ticker, dates, dailyTarget = "market.enriched", spotStore) {
|
|
2026
2053
|
const hasData = await hasTier3Data(conn, ticker, spotStore);
|
|
@@ -2086,7 +2113,14 @@ async function runTier3(conn, ticker, dates, dailyTarget = "market.enriched", sp
|
|
|
2086
2113
|
reason: "intraday data exists but no bars overlap with enrichment date range"
|
|
2087
2114
|
};
|
|
2088
2115
|
}
|
|
2089
|
-
const tier3Cols = [
|
|
2116
|
+
const tier3Cols = [
|
|
2117
|
+
"High_Time",
|
|
2118
|
+
"Low_Time",
|
|
2119
|
+
"High_Before_Low",
|
|
2120
|
+
"Reversal_Type",
|
|
2121
|
+
"Opening_Drive_Strength",
|
|
2122
|
+
"Intraday_Realized_Vol"
|
|
2123
|
+
];
|
|
2090
2124
|
const enrichedRows = [];
|
|
2091
2125
|
for (const [dateStr, bars] of barsByDate) {
|
|
2092
2126
|
const timing = computeIntradayTimingFields(bars);
|
|
@@ -2205,7 +2239,9 @@ function applyColumnMapping(rows, columnMapping, ticker) {
|
|
|
2205
2239
|
if (hasNullDate) continue;
|
|
2206
2240
|
if (!("date" in mapped)) continue;
|
|
2207
2241
|
if (!("time" in mapped)) {
|
|
2208
|
-
const dateSourceCol = Object.entries(columnMapping).find(
|
|
2242
|
+
const dateSourceCol = Object.entries(columnMapping).find(
|
|
2243
|
+
([, schema]) => schema === "date"
|
|
2244
|
+
)?.[0];
|
|
2209
2245
|
if (dateSourceCol) {
|
|
2210
2246
|
const rawDateValue = row[dateSourceCol] ?? "";
|
|
2211
2247
|
const numericDate = Number(rawDateValue);
|
|
@@ -2599,9 +2635,13 @@ var profileStrategySchema = z.object({
|
|
|
2599
2635
|
operator: z.string().describe("Comparison operator: >, <, >=, <=, ==, between, in"),
|
|
2600
2636
|
value: z.union([z.string(), z.number(), z.array(z.union([z.string(), z.number()]))]).describe("Filter value or array for between/in operators"),
|
|
2601
2637
|
description: z.string().optional().describe("Human-readable description of this filter"),
|
|
2602
|
-
source: z.enum(["market", "execution"]).optional().describe(
|
|
2638
|
+
source: z.enum(["market", "execution"]).optional().describe(
|
|
2639
|
+
"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."
|
|
2640
|
+
)
|
|
2603
2641
|
})
|
|
2604
|
-
).default([]).describe(
|
|
2642
|
+
).default([]).describe(
|
|
2643
|
+
"Entry condition filters. Tag each with source: 'market' (testable in analysis) or 'execution' (OO/platform-level, skipped in analysis)."
|
|
2644
|
+
),
|
|
2605
2645
|
exitRules: z.array(
|
|
2606
2646
|
z.object({
|
|
2607
2647
|
type: z.string().describe("Rule type: stop_loss, profit_target, time_exit, conditional"),
|
|
@@ -2616,7 +2656,9 @@ var profileStrategySchema = z.object({
|
|
|
2616
2656
|
slippage: z.number().optional().describe("Per-rule slippage override")
|
|
2617
2657
|
})
|
|
2618
2658
|
).default([]).describe("Exit rules and triggers"),
|
|
2619
|
-
expectedRegimes: z.array(z.enum(["very_low", "low", "below_avg", "above_avg", "high", "extreme"])).default([]).describe(
|
|
2659
|
+
expectedRegimes: z.array(z.enum(["very_low", "low", "below_avg", "above_avg", "high", "extreme"])).default([]).describe(
|
|
2660
|
+
"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+"
|
|
2661
|
+
),
|
|
2620
2662
|
keyMetrics: z.object({
|
|
2621
2663
|
expectedWinRate: z.number().optional().describe("Expected win rate (0-1)"),
|
|
2622
2664
|
targetPremium: z.number().optional().describe("Target premium collected ($)"),
|
|
@@ -2633,7 +2675,9 @@ var profileStrategySchema = z.object({
|
|
|
2633
2675
|
backtestAllocationPct: z.number().optional().describe("Allocation % used in backtest"),
|
|
2634
2676
|
liveAllocationPct: z.number().optional().describe("Allocation % used in live portfolio"),
|
|
2635
2677
|
maxContractsPerTrade: z.number().optional().describe("Per-entry contract cap (distinct from maxContracts hard cap)")
|
|
2636
|
-
}).optional().describe(
|
|
2678
|
+
}).optional().describe(
|
|
2679
|
+
"Position sizing rules. Per-block \u2014 same strategy in backtest vs portfolio may have different sizing."
|
|
2680
|
+
),
|
|
2637
2681
|
underlying: z.string().optional().describe("Underlying symbol: SPX, QQQ, etc."),
|
|
2638
2682
|
reEntry: z.boolean().optional().describe("Strategy supports re-entry on same day"),
|
|
2639
2683
|
capProfits: z.boolean().optional().describe("Profits are capped by structure"),
|
|
@@ -2657,30 +2701,33 @@ async function handleProfileStrategy(input, baseDir) {
|
|
|
2657
2701
|
await upgradeToReadWrite(baseDir);
|
|
2658
2702
|
try {
|
|
2659
2703
|
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
|
-
|
|
2704
|
+
const stored = await upsertProfile(
|
|
2705
|
+
conn,
|
|
2706
|
+
{
|
|
2707
|
+
blockId: input.blockId,
|
|
2708
|
+
strategyName: input.strategyName,
|
|
2709
|
+
structureType: input.structureType,
|
|
2710
|
+
greeksBias: input.greeksBias,
|
|
2711
|
+
thesis: input.thesis,
|
|
2712
|
+
legs: input.legs,
|
|
2713
|
+
entryFilters: input.entryFilters,
|
|
2714
|
+
exitRules: input.exitRules,
|
|
2715
|
+
expectedRegimes: input.expectedRegimes,
|
|
2716
|
+
keyMetrics: input.keyMetrics,
|
|
2717
|
+
positionSizing: input.positionSizing,
|
|
2718
|
+
underlying: input.underlying,
|
|
2719
|
+
reEntry: input.reEntry,
|
|
2720
|
+
capProfits: input.capProfits,
|
|
2721
|
+
capLosses: input.capLosses,
|
|
2722
|
+
requireTwoPricesPT: input.requireTwoPricesPT,
|
|
2723
|
+
closeOnCompletion: input.closeOnCompletion,
|
|
2724
|
+
ignoreMarginReq: input.ignoreMarginReq
|
|
2725
|
+
},
|
|
2726
|
+
baseDir
|
|
2683
2727
|
);
|
|
2728
|
+
return createToolOutput(`Profile saved: ${input.strategyName} for block ${input.blockId}`, {
|
|
2729
|
+
profile: stored
|
|
2730
|
+
});
|
|
2684
2731
|
} finally {
|
|
2685
2732
|
await downgradeToReadOnly(baseDir);
|
|
2686
2733
|
}
|
|
@@ -2694,10 +2741,7 @@ async function handleGetStrategyProfile(input, baseDir) {
|
|
|
2694
2741
|
{ profile: null }
|
|
2695
2742
|
);
|
|
2696
2743
|
}
|
|
2697
|
-
return createToolOutput(
|
|
2698
|
-
`Profile: ${input.strategyName} in block ${input.blockId}`,
|
|
2699
|
-
{ profile }
|
|
2700
|
-
);
|
|
2744
|
+
return createToolOutput(`Profile: ${input.strategyName} in block ${input.blockId}`, { profile });
|
|
2701
2745
|
}
|
|
2702
2746
|
async function handleListProfiles(input, baseDir) {
|
|
2703
2747
|
const conn = await getConnection(baseDir);
|
|
@@ -3286,7 +3330,9 @@ async function handleValidateEntryFilters(input, baseDir) {
|
|
|
3286
3330
|
single: ablationSingle,
|
|
3287
3331
|
pairs: ablationPairs
|
|
3288
3332
|
},
|
|
3289
|
-
execution_filters_skipped: executionFilters.map(
|
|
3333
|
+
execution_filters_skipped: executionFilters.map(
|
|
3334
|
+
(f) => f.description || `${f.field} ${f.operator} ${f.value}`
|
|
3335
|
+
),
|
|
3290
3336
|
profile_update_hints: profileUpdateHints,
|
|
3291
3337
|
warnings
|
|
3292
3338
|
});
|
|
@@ -3317,7 +3363,9 @@ async function handlePortfolioStructureMap(input, baseDir) {
|
|
|
3317
3363
|
try {
|
|
3318
3364
|
block = await loadBlock(baseDir, profile.blockId);
|
|
3319
3365
|
} catch {
|
|
3320
|
-
warnings.push(
|
|
3366
|
+
warnings.push(
|
|
3367
|
+
`Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'`
|
|
3368
|
+
);
|
|
3321
3369
|
continue;
|
|
3322
3370
|
}
|
|
3323
3371
|
let trades = filterByStrategy(block.trades, profile.strategyName);
|
|
@@ -3328,7 +3376,9 @@ async function handlePortfolioStructureMap(input, baseDir) {
|
|
|
3328
3376
|
}
|
|
3329
3377
|
}
|
|
3330
3378
|
if (trades.length === 0) {
|
|
3331
|
-
warnings.push(
|
|
3379
|
+
warnings.push(
|
|
3380
|
+
`No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'`
|
|
3381
|
+
);
|
|
3332
3382
|
continue;
|
|
3333
3383
|
}
|
|
3334
3384
|
const tradeKeys = uniqueTradeLookupKeys(trades);
|
|
@@ -3446,10 +3496,7 @@ async function handlePortfolioStructureMap(input, baseDir) {
|
|
|
3446
3496
|
);
|
|
3447
3497
|
}
|
|
3448
3498
|
const unknownTrendStats = unknownTrendPls.size > 0 ? Object.fromEntries(
|
|
3449
|
-
[...unknownTrendPls.entries()].map(([name, pls]) => [
|
|
3450
|
-
name,
|
|
3451
|
-
computeSliceStats(pls)
|
|
3452
|
-
])
|
|
3499
|
+
[...unknownTrendPls.entries()].map(([name, pls]) => [name, computeSliceStats(pls)])
|
|
3453
3500
|
) : void 0;
|
|
3454
3501
|
const coverageSummary = {
|
|
3455
3502
|
totalCells: 18,
|
|
@@ -3551,12 +3598,8 @@ var VOL_REGIME_LABELS2 = {
|
|
|
3551
3598
|
6: "extreme"
|
|
3552
3599
|
};
|
|
3553
3600
|
var regimeAllocationAdvisorSchema = z3.object({
|
|
3554
|
-
blockId: z3.string().optional().describe(
|
|
3555
|
-
|
|
3556
|
-
),
|
|
3557
|
-
minTrades: z3.number().optional().default(5).describe(
|
|
3558
|
-
"Minimum trades per regime cell for reliable stats (default: 5)"
|
|
3559
|
-
)
|
|
3601
|
+
blockId: z3.string().optional().describe("Block ID to analyze. When omitted, aggregate across all profiled strategies."),
|
|
3602
|
+
minTrades: z3.number().optional().default(5).describe("Minimum trades per regime cell for reliable stats (default: 5)")
|
|
3560
3603
|
});
|
|
3561
3604
|
async function handleRegimeAllocationAdvisor(input, baseDir) {
|
|
3562
3605
|
const minTrades = input.minTrades ?? 5;
|
|
@@ -3648,9 +3691,7 @@ async function handleRegimeAllocationAdvisor(input, baseDir) {
|
|
|
3648
3691
|
if (!regimeAggPls[label]) regimeAggPls[label] = [];
|
|
3649
3692
|
regimeAggPls[label].push(trade.pl);
|
|
3650
3693
|
}
|
|
3651
|
-
const expectedSet = new Set(
|
|
3652
|
-
profile.expectedRegimes.map((r) => r.toLowerCase())
|
|
3653
|
-
);
|
|
3694
|
+
const expectedSet = new Set(profile.expectedRegimes.map((r) => r.toLowerCase()));
|
|
3654
3695
|
const regimePerformance = {};
|
|
3655
3696
|
for (const [label, pls] of Object.entries(regimePls)) {
|
|
3656
3697
|
const stats = computeSliceStats(pls);
|
|
@@ -3787,9 +3828,7 @@ var replayTradeSchema = z4.object({
|
|
|
3787
3828
|
).optional().describe("Explicit leg definitions for hypothetical replay"),
|
|
3788
3829
|
// Mode B: Tradelog replay
|
|
3789
3830
|
block_id: z4.string().optional().describe("Block ID to load trade from"),
|
|
3790
|
-
trade_index: z4.number().optional().describe(
|
|
3791
|
-
"0-based index of trade in block's tradelog (ordered by date_opened)"
|
|
3792
|
-
),
|
|
3831
|
+
trade_index: z4.number().optional().describe("0-based index of trade in block's tradelog (ordered by date_opened)"),
|
|
3793
3832
|
// Common fields
|
|
3794
3833
|
open_date: z4.string().optional().describe(
|
|
3795
3834
|
"Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)"
|
|
@@ -3836,22 +3875,13 @@ function resolveOODateRange(parsedLegs, tradeYear, tradeOpenDate) {
|
|
|
3836
3875
|
return { from: tradeOpenDate, to: maxDate };
|
|
3837
3876
|
}
|
|
3838
3877
|
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;
|
|
3878
|
+
const { legs: inputLegs, block_id, trade_index, multiplier, close_at, skip_quotes } = params;
|
|
3847
3879
|
let { open_date, close_date } = params;
|
|
3848
3880
|
let tradeCloseTimestamp;
|
|
3849
3881
|
let replayLegs;
|
|
3850
3882
|
if (inputLegs && inputLegs.length > 0) {
|
|
3851
3883
|
if (!open_date || !close_date) {
|
|
3852
|
-
throw new Error(
|
|
3853
|
-
"open_date and close_date are required for hypothetical replay mode"
|
|
3854
|
-
);
|
|
3884
|
+
throw new Error("open_date and close_date are required for hypothetical replay mode");
|
|
3855
3885
|
}
|
|
3856
3886
|
replayLegs = inputLegs.map((leg) => ({
|
|
3857
3887
|
occTicker: buildOccTicker(leg.ticker, leg.expiry, leg.type, leg.strike),
|
|
@@ -3870,9 +3900,7 @@ async function handleReplayTrade(params, baseDir, stores, injectedConn) {
|
|
|
3870
3900
|
);
|
|
3871
3901
|
const rows = result.getRows();
|
|
3872
3902
|
if (rows.length === 0) {
|
|
3873
|
-
throw new Error(
|
|
3874
|
-
`No trade found at index ${trade_index} in block "${block_id}"`
|
|
3875
|
-
);
|
|
3903
|
+
throw new Error(`No trade found at index ${trade_index} in block "${block_id}"`);
|
|
3876
3904
|
}
|
|
3877
3905
|
const row = rows[0];
|
|
3878
3906
|
const legsStr = String(row[0] ?? "");
|
|
@@ -3981,11 +4009,7 @@ async function handleReplayTrade(params, baseDir, stores, injectedConn) {
|
|
|
3981
4009
|
);
|
|
3982
4010
|
if (underlyingBars.length === 0) {
|
|
3983
4011
|
try {
|
|
3984
|
-
underlyingBars = await stores.spot.readDailyBars(
|
|
3985
|
-
underlyingTicker,
|
|
3986
|
-
open_date,
|
|
3987
|
-
close_date
|
|
3988
|
-
);
|
|
4012
|
+
underlyingBars = await stores.spot.readDailyBars(underlyingTicker, open_date, close_date);
|
|
3989
4013
|
} catch {
|
|
3990
4014
|
}
|
|
3991
4015
|
}
|
|
@@ -4112,9 +4136,7 @@ var getOptionSnapshotSchema = z5.object({
|
|
|
4112
4136
|
expiration_date_gte: z5.string().optional().describe("Earliest expiration date (YYYY-MM-DD)"),
|
|
4113
4137
|
expiration_date_lte: z5.string().optional().describe("Latest expiration date (YYYY-MM-DD)"),
|
|
4114
4138
|
contract_type: z5.enum(["call", "put"]).optional().describe("Filter by call or put"),
|
|
4115
|
-
limit: z5.number().optional().default(50).describe(
|
|
4116
|
-
"Max contracts to return (default 50, use higher for full chain)"
|
|
4117
|
-
)
|
|
4139
|
+
limit: z5.number().optional().default(50).describe("Max contracts to return (default 50, use higher for full chain)")
|
|
4118
4140
|
});
|
|
4119
4141
|
async function handleGetOptionSnapshot(params) {
|
|
4120
4142
|
try {
|
|
@@ -4231,7 +4253,9 @@ function numericalDecomposition(config, totalPnlChange, stepCount) {
|
|
|
4231
4253
|
...f,
|
|
4232
4254
|
pctOfTotal: totalAbsSum > 0 ? Math.abs(f.totalPnl) / totalAbsSum * 100 : 0
|
|
4233
4255
|
}));
|
|
4234
|
-
const summaryParts = factors.map(
|
|
4256
|
+
const summaryParts = factors.map(
|
|
4257
|
+
(f) => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`
|
|
4258
|
+
);
|
|
4235
4259
|
const summary = `P&L of ${totalPnlChange.toFixed(2)} (numerical): ${summaryParts.join(", ")}`;
|
|
4236
4260
|
return {
|
|
4237
4261
|
factors,
|
|
@@ -4266,7 +4290,15 @@ function priceOption(type, S, K, dte, r, q, iv) {
|
|
|
4266
4290
|
return bsPrice(bsType, S, K, T, r, q, iv);
|
|
4267
4291
|
}
|
|
4268
4292
|
function decomposeGreeks(config) {
|
|
4269
|
-
const {
|
|
4293
|
+
const {
|
|
4294
|
+
pnlPath,
|
|
4295
|
+
legs,
|
|
4296
|
+
underlyingPrices,
|
|
4297
|
+
legGroups,
|
|
4298
|
+
legPricingInputs,
|
|
4299
|
+
riskFreeRate,
|
|
4300
|
+
dividendYield
|
|
4301
|
+
} = config;
|
|
4270
4302
|
if (pnlPath.length <= 1) {
|
|
4271
4303
|
const emptyFactors = [
|
|
4272
4304
|
{ factor: "delta", totalPnl: 0, pctOfTotal: 0, steps: [] },
|
|
@@ -4426,7 +4458,9 @@ function decomposeGreeks(config) {
|
|
|
4426
4458
|
const summaryParts = factors.filter((f) => f.factor !== "residual").map((f) => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);
|
|
4427
4459
|
const residualFactor = factors.find((f) => f.factor === "residual");
|
|
4428
4460
|
if (residualFactor && Math.abs(residualFactor.totalPnl) > 0.01) {
|
|
4429
|
-
summaryParts.push(
|
|
4461
|
+
summaryParts.push(
|
|
4462
|
+
`residual ${residualFactor.totalPnl.toFixed(2)} (${residualFactor.pctOfTotal.toFixed(0)}%)`
|
|
4463
|
+
);
|
|
4430
4464
|
}
|
|
4431
4465
|
const summary = `P&L of ${totalPnlChange.toFixed(2)} (${methodLabel}): ${summaryParts.join(", ")}`;
|
|
4432
4466
|
const warning = residualPct > 0.5 ? `Residual ${(residualPct * 100).toFixed(0)}% \u2014 attribution limited for some legs.` : null;
|
|
@@ -4558,7 +4592,7 @@ function evaluateTrigger(trigger, pnlPath, legs) {
|
|
|
4558
4592
|
switch (type) {
|
|
4559
4593
|
case "profitTarget": {
|
|
4560
4594
|
if (trigger.unit === "percent" && trigger.entryCost == null) break;
|
|
4561
|
-
const requiredHits = trigger.requiredHits ??
|
|
4595
|
+
const requiredHits = trigger.requiredHits ?? 2;
|
|
4562
4596
|
const dollarThresholdPT = trigger.unit === "percent" ? threshold * Math.abs(trigger.entryCost) : threshold;
|
|
4563
4597
|
if (pnl >= dollarThresholdPT) {
|
|
4564
4598
|
if (point.allLegsSync !== false) profitTargetHits++;
|
|
@@ -4940,11 +4974,13 @@ var triggerConfigSchema = z6.object({
|
|
|
4940
4974
|
openDate: z6.string().optional(),
|
|
4941
4975
|
clockTime: z6.string().optional(),
|
|
4942
4976
|
trailAmount: z6.number().optional(),
|
|
4943
|
-
steps: z6.array(
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4977
|
+
steps: z6.array(
|
|
4978
|
+
z6.object({
|
|
4979
|
+
armAt: z6.number(),
|
|
4980
|
+
stopAt: z6.number(),
|
|
4981
|
+
closeAllocationPct: z6.number().min(0).max(1).optional().describe("Fraction of REMAINING position to close at this milestone (0-1)")
|
|
4982
|
+
})
|
|
4983
|
+
).optional(),
|
|
4948
4984
|
spreadWidth: z6.number().optional(),
|
|
4949
4985
|
contracts: z6.number().optional(),
|
|
4950
4986
|
legIndex: z6.number().optional().describe("0-based leg index for perLegDelta \u2014 targets specific leg"),
|
|
@@ -4969,11 +5005,13 @@ var analyzeExitTriggersSchema = z6.object({
|
|
|
4969
5005
|
multiplier: z6.number().default(100),
|
|
4970
5006
|
triggers: z6.array(triggerConfigSchema).describe("Exit triggers to evaluate against the P&L path"),
|
|
4971
5007
|
actual_exit_timestamp: z6.string().optional().describe("Actual exit time for comparison (format: YYYY-MM-DD HH:MM)"),
|
|
4972
|
-
leg_groups: z6.array(
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
5008
|
+
leg_groups: z6.array(
|
|
5009
|
+
z6.object({
|
|
5010
|
+
label: z6.string(),
|
|
5011
|
+
leg_indices: z6.array(z6.number()),
|
|
5012
|
+
triggers: z6.array(triggerConfigSchema)
|
|
5013
|
+
})
|
|
5014
|
+
).optional().describe("Per-leg-group exit triggers for multi-structure strategies"),
|
|
4977
5015
|
format: z6.enum(["summary", "full"]).default("summary").describe("'summary' omits per-step trigger states, 'full' includes all fire events")
|
|
4978
5016
|
});
|
|
4979
5017
|
var decomposeGreeksSchema = z6.object({
|
|
@@ -4984,10 +5022,12 @@ var decomposeGreeksSchema = z6.object({
|
|
|
4984
5022
|
open_date: z6.string().optional(),
|
|
4985
5023
|
close_date: z6.string().optional(),
|
|
4986
5024
|
multiplier: z6.number().default(100),
|
|
4987
|
-
leg_groups: z6.array(
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
5025
|
+
leg_groups: z6.array(
|
|
5026
|
+
z6.object({
|
|
5027
|
+
label: z6.string(),
|
|
5028
|
+
leg_indices: z6.array(z6.number())
|
|
5029
|
+
})
|
|
5030
|
+
).optional().describe("Leg grouping for per-group vega attribution (e.g., front_month vs back_month)"),
|
|
4991
5031
|
format: z6.enum(["summary", "full"]).default("summary").describe("'summary' shows ranked factors, 'full' includes per-step contributions"),
|
|
4992
5032
|
skip_quotes: z6.boolean().default(false).describe("Skip NBBO quote enrichment for option bars. Faster, but lower precision.")
|
|
4993
5033
|
});
|
|
@@ -5012,7 +5052,8 @@ async function fetchPriceMap(stores, ticker, from, to) {
|
|
|
5012
5052
|
}
|
|
5013
5053
|
}
|
|
5014
5054
|
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)
|
|
5055
|
+
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)
|
|
5056
|
+
continue;
|
|
5016
5057
|
const ts = `${b.date} ${b.time ?? ""}`.trim();
|
|
5017
5058
|
map.set(ts, markPrice(b));
|
|
5018
5059
|
}
|
|
@@ -5083,12 +5124,7 @@ async function handleAnalyzeExitTriggers(params, baseDir, stores, injectedConn)
|
|
|
5083
5124
|
vix9dPrices = await fetchPriceMap(stores, "VIX9D", firstDate, lastDate);
|
|
5084
5125
|
}
|
|
5085
5126
|
if (needsUnderlying) {
|
|
5086
|
-
underlyingPrices = await fetchPriceMap(
|
|
5087
|
-
stores,
|
|
5088
|
-
underlyingTicker,
|
|
5089
|
-
firstDate,
|
|
5090
|
-
lastDate
|
|
5091
|
-
);
|
|
5127
|
+
underlyingPrices = await fetchPriceMap(stores, underlyingTicker, firstDate, lastDate);
|
|
5092
5128
|
}
|
|
5093
5129
|
const exitTriggers = triggers.map((t) => ({
|
|
5094
5130
|
type: t.type,
|
|
@@ -5458,11 +5494,13 @@ var triggerConfigSchema2 = z7.object({
|
|
|
5458
5494
|
openDate: z7.string().optional(),
|
|
5459
5495
|
clockTime: z7.string().optional(),
|
|
5460
5496
|
trailAmount: z7.number().optional(),
|
|
5461
|
-
steps: z7.array(
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5497
|
+
steps: z7.array(
|
|
5498
|
+
z7.object({
|
|
5499
|
+
armAt: z7.number(),
|
|
5500
|
+
stopAt: z7.number(),
|
|
5501
|
+
closeAllocationPct: z7.number().min(0).max(1).optional().describe("Fraction of REMAINING position to close at this milestone (0-1)")
|
|
5502
|
+
})
|
|
5503
|
+
).optional(),
|
|
5466
5504
|
spreadWidth: z7.number().optional(),
|
|
5467
5505
|
contracts: z7.number().optional(),
|
|
5468
5506
|
legIndex: z7.number().optional().describe("0-based leg index for perLegDelta \u2014 targets specific leg"),
|
|
@@ -5477,17 +5515,23 @@ var batchExitAnalysisSchema = z7.object({
|
|
|
5477
5515
|
to: z7.string().optional().describe("End date YYYY-MM-DD")
|
|
5478
5516
|
}).optional().describe("Filter trades by date range"),
|
|
5479
5517
|
candidate_policy: z7.array(triggerConfigSchema2).describe("Candidate exit policy triggers to evaluate -- same schema as analyze_exit_triggers"),
|
|
5480
|
-
leg_groups: z7.array(
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5518
|
+
leg_groups: z7.array(
|
|
5519
|
+
z7.object({
|
|
5520
|
+
label: z7.string(),
|
|
5521
|
+
leg_indices: z7.array(z7.number()),
|
|
5522
|
+
triggers: z7.array(triggerConfigSchema2)
|
|
5523
|
+
})
|
|
5524
|
+
).optional().describe("Per-leg-group exit triggers for multi-structure strategies"),
|
|
5525
|
+
baseline_mode: z7.enum(["actual", "holdToEnd"]).default("actual").describe(
|
|
5526
|
+
"'actual' compares candidate vs trade's actual P&L; 'holdToEnd' compares vs last replay timestamp"
|
|
5527
|
+
),
|
|
5486
5528
|
limit: z7.number().min(1).max(200).default(50).describe("Max trades to analyze. Most recent trades selected"),
|
|
5487
5529
|
min_pl: z7.number().optional().describe("Only include trades with actual P&L >= this value"),
|
|
5488
5530
|
max_pl: z7.number().optional().describe("Only include trades with actual P&L <= this value"),
|
|
5489
5531
|
multiplier: z7.number().default(100).describe("Contract multiplier (default 100)"),
|
|
5490
|
-
format: z7.enum(["summary", "full"]).default("summary").describe(
|
|
5532
|
+
format: z7.enum(["summary", "full"]).default("summary").describe(
|
|
5533
|
+
"'summary' returns aggregate stats + trigger attribution; 'full' adds per-trade breakdown"
|
|
5534
|
+
)
|
|
5491
5535
|
});
|
|
5492
5536
|
async function handleBatchExitAnalysis(params, baseDir, stores, injectedConn) {
|
|
5493
5537
|
const {
|
|
@@ -5650,9 +5694,7 @@ async function handleBatchExitAnalysis(params, baseDir, stores, injectedConn) {
|
|
|
5650
5694
|
if (profile) {
|
|
5651
5695
|
result.profileContext = {
|
|
5652
5696
|
structureType: profile.structureType,
|
|
5653
|
-
exitRules: profile.exitRules.map(
|
|
5654
|
-
(r) => r.description ?? `${r.type} ${r.trigger}`
|
|
5655
|
-
)
|
|
5697
|
+
exitRules: profile.exitRules.map((r) => r.description ?? `${r.type} ${r.trigger}`)
|
|
5656
5698
|
};
|
|
5657
5699
|
}
|
|
5658
5700
|
} catch {
|
|
@@ -5813,13 +5855,7 @@ function filterChain(contracts, filter) {
|
|
|
5813
5855
|
import { open, mkdir } from "fs/promises";
|
|
5814
5856
|
import { dirname, join as join2 } from "path";
|
|
5815
5857
|
function backfillManifestPath(dataRoot, runId) {
|
|
5816
|
-
return join2(
|
|
5817
|
-
dataRoot,
|
|
5818
|
-
"market",
|
|
5819
|
-
"_manifests",
|
|
5820
|
-
"thetadata-mdds-backfill",
|
|
5821
|
-
`${runId}.ndjson`
|
|
5822
|
-
);
|
|
5858
|
+
return join2(dataRoot, "market", "_manifests", "thetadata-mdds-backfill", `${runId}.ndjson`);
|
|
5823
5859
|
}
|
|
5824
5860
|
function backfillPartitionPath(dataRoot, underlying, date) {
|
|
5825
5861
|
return join2(
|
|
@@ -6129,8 +6165,12 @@ function tradingDays(from, to) {
|
|
|
6129
6165
|
var getGreeksAttributionSchema = z9.object({
|
|
6130
6166
|
block_id: z9.string().describe("Block ID to analyze"),
|
|
6131
6167
|
mode: z9.enum(["summary", "instance"]).default("summary").describe("summary: block-level attribution. instance: single trade time-series."),
|
|
6132
|
-
trade_index: z9.number().int().min(0).optional().describe(
|
|
6133
|
-
|
|
6168
|
+
trade_index: z9.number().int().min(0).optional().describe(
|
|
6169
|
+
"Trade index (required for instance mode). Use get_block_info to find trade indices."
|
|
6170
|
+
),
|
|
6171
|
+
skip_quotes: z9.boolean().default(true).describe(
|
|
6172
|
+
"Use cached bar data only (fast). Set false to fetch NBBO quotes for higher precision."
|
|
6173
|
+
),
|
|
6134
6174
|
detailed: z9.boolean().default(false).describe("false: 5 factors (delta, gamma, theta, vega, residual). true: adds charm, vanna."),
|
|
6135
6175
|
strategy: z9.string().optional().describe("Filter to trades matching this strategy name (case-insensitive).")
|
|
6136
6176
|
});
|
|
@@ -6138,7 +6178,16 @@ var COLLAPSE_MAP = {
|
|
|
6138
6178
|
charm: "delta",
|
|
6139
6179
|
vanna: "vega"
|
|
6140
6180
|
};
|
|
6141
|
-
var FACTOR_ORDER = [
|
|
6181
|
+
var FACTOR_ORDER = [
|
|
6182
|
+
"theta",
|
|
6183
|
+
"vega",
|
|
6184
|
+
"delta",
|
|
6185
|
+
"gamma",
|
|
6186
|
+
"residual",
|
|
6187
|
+
"time_and_vol",
|
|
6188
|
+
"charm",
|
|
6189
|
+
"vanna"
|
|
6190
|
+
];
|
|
6142
6191
|
function collapseFactors(factors, detailed) {
|
|
6143
6192
|
const totals = /* @__PURE__ */ new Map();
|
|
6144
6193
|
for (const f of factors) {
|
|
@@ -6188,9 +6237,25 @@ async function handleGetGreeksAttribution(params, baseDir, stores, injectedConn)
|
|
|
6188
6237
|
if (trade_index == null) {
|
|
6189
6238
|
throw new Error("trade_index is required for instance mode");
|
|
6190
6239
|
}
|
|
6191
|
-
return handleInstanceMode(
|
|
6240
|
+
return handleInstanceMode(
|
|
6241
|
+
block_id,
|
|
6242
|
+
trade_index,
|
|
6243
|
+
skip_quotes,
|
|
6244
|
+
detailed,
|
|
6245
|
+
baseDir,
|
|
6246
|
+
stores,
|
|
6247
|
+
injectedConn
|
|
6248
|
+
);
|
|
6192
6249
|
}
|
|
6193
|
-
return handleSummaryMode(
|
|
6250
|
+
return handleSummaryMode(
|
|
6251
|
+
block_id,
|
|
6252
|
+
skip_quotes,
|
|
6253
|
+
detailed,
|
|
6254
|
+
strategy,
|
|
6255
|
+
baseDir,
|
|
6256
|
+
stores,
|
|
6257
|
+
injectedConn
|
|
6258
|
+
);
|
|
6194
6259
|
}
|
|
6195
6260
|
async function handleSummaryMode(block_id, skip_quotes, detailed, strategy, baseDir, stores, injectedConn) {
|
|
6196
6261
|
const conn = injectedConn ?? await getConnection(baseDir);
|
|
@@ -6242,7 +6307,10 @@ async function handleSummaryMode(block_id, skip_quotes, detailed, strategy, base
|
|
|
6242
6307
|
injectedConn
|
|
6243
6308
|
).then((result) => {
|
|
6244
6309
|
for (const factor of result.factors) {
|
|
6245
|
-
accumulated.set(
|
|
6310
|
+
accumulated.set(
|
|
6311
|
+
factor.factor,
|
|
6312
|
+
(accumulated.get(factor.factor) ?? 0) + factor.totalPnl
|
|
6313
|
+
);
|
|
6246
6314
|
}
|
|
6247
6315
|
actualTotalPnl += trade.actualPl;
|
|
6248
6316
|
markTotalPnl += result.totalPnlChange;
|
|
@@ -6345,10 +6413,20 @@ async function handleInstanceMode(block_id, trade_index, skip_quotes, detailed,
|
|
|
6345
6413
|
for (let i = 0; i <= stepCount; i++) {
|
|
6346
6414
|
const entry = {
|
|
6347
6415
|
date: getStepDate(i),
|
|
6348
|
-
delta: getStepValue(
|
|
6416
|
+
delta: getStepValue(
|
|
6417
|
+
factorSteps,
|
|
6418
|
+
"delta",
|
|
6419
|
+
i,
|
|
6420
|
+
detailed ? 0 : factorSteps.get("charm")?.[i] ?? 0
|
|
6421
|
+
),
|
|
6349
6422
|
gamma: getStepValue(factorSteps, "gamma", i, 0),
|
|
6350
6423
|
theta: getStepValue(factorSteps, "theta", i, 0),
|
|
6351
|
-
vega: getStepValue(
|
|
6424
|
+
vega: getStepValue(
|
|
6425
|
+
factorSteps,
|
|
6426
|
+
"vega",
|
|
6427
|
+
i,
|
|
6428
|
+
detailed ? 0 : factorSteps.get("vanna")?.[i] ?? 0
|
|
6429
|
+
),
|
|
6352
6430
|
residual: getStepValue(factorSteps, "residual", i, 0)
|
|
6353
6431
|
};
|
|
6354
6432
|
if (factorSteps.has("time_and_vol")) {
|
|
@@ -6815,11 +6893,7 @@ var ParquetSpotStore = class extends SpotStore {
|
|
|
6815
6893
|
}));
|
|
6816
6894
|
}
|
|
6817
6895
|
async getCoverage(ticker, from, to) {
|
|
6818
|
-
const tickerDir = path5.join(
|
|
6819
|
-
resolveMarketDir(this.ctx.dataDir),
|
|
6820
|
-
"spot",
|
|
6821
|
-
`ticker=${ticker}`
|
|
6822
|
-
);
|
|
6896
|
+
const tickerDir = path5.join(resolveMarketDir(this.ctx.dataDir), "spot", `ticker=${ticker}`);
|
|
6823
6897
|
if (!existsSync4(tickerDir)) {
|
|
6824
6898
|
return { earliest: null, latest: null, missingDates: [], totalDates: 0 };
|
|
6825
6899
|
}
|
|
@@ -7044,9 +7118,7 @@ var ParquetEnrichedStore = class extends EnrichedStore {
|
|
|
7044
7118
|
});
|
|
7045
7119
|
const reader = await this.ctx.conn.runAndReadAll(sql);
|
|
7046
7120
|
const names = reader.columnNames();
|
|
7047
|
-
return reader.getRows().map(
|
|
7048
|
-
(row) => Object.fromEntries(names.map((n, i) => [n, row[i]]))
|
|
7049
|
-
);
|
|
7121
|
+
return reader.getRows().map((row) => Object.fromEntries(names.map((n, i) => [n, row[i]])));
|
|
7050
7122
|
}
|
|
7051
7123
|
async getCoverage(ticker) {
|
|
7052
7124
|
const filePath = path6.join(
|
|
@@ -7119,9 +7191,7 @@ var DuckdbEnrichedStore = class extends EnrichedStore {
|
|
|
7119
7191
|
});
|
|
7120
7192
|
const reader = await this.ctx.conn.runAndReadAll(sql);
|
|
7121
7193
|
const names = reader.columnNames();
|
|
7122
|
-
return reader.getRows().map(
|
|
7123
|
-
(row) => Object.fromEntries(names.map((n, i) => [n, row[i]]))
|
|
7124
|
-
);
|
|
7194
|
+
return reader.getRows().map((row) => Object.fromEntries(names.map((n, i) => [n, row[i]])));
|
|
7125
7195
|
}
|
|
7126
7196
|
async getCoverage(ticker) {
|
|
7127
7197
|
const tickerLit = ticker.replace(/'/g, "''");
|
|
@@ -7474,9 +7544,7 @@ var ParquetQuoteStore = class extends QuoteStore {
|
|
|
7474
7544
|
}
|
|
7475
7545
|
async readQuotes(occTickers, from, to) {
|
|
7476
7546
|
if (occTickers.length === 0) return /* @__PURE__ */ new Map();
|
|
7477
|
-
const firstUnderlying = this.ctx.tickers.resolve(
|
|
7478
|
-
extractRoot(occTickers[0])
|
|
7479
|
-
);
|
|
7547
|
+
const firstUnderlying = this.ctx.tickers.resolve(extractRoot(occTickers[0]));
|
|
7480
7548
|
for (const t of occTickers) {
|
|
7481
7549
|
const u = this.ctx.tickers.resolve(extractRoot(t));
|
|
7482
7550
|
if (u !== firstUnderlying) {
|
|
@@ -7791,9 +7859,7 @@ var DuckdbQuoteStore = class extends QuoteStore {
|
|
|
7791
7859
|
}
|
|
7792
7860
|
async readQuotes(occTickers, from, to) {
|
|
7793
7861
|
if (occTickers.length === 0) return /* @__PURE__ */ new Map();
|
|
7794
|
-
const firstUnderlying = this.ctx.tickers.resolve(
|
|
7795
|
-
extractRoot(occTickers[0])
|
|
7796
|
-
);
|
|
7862
|
+
const firstUnderlying = this.ctx.tickers.resolve(extractRoot(occTickers[0]));
|
|
7797
7863
|
for (const t of occTickers) {
|
|
7798
7864
|
const u = this.ctx.tickers.resolve(extractRoot(t));
|
|
7799
7865
|
if (u !== firstUnderlying) {
|
|
@@ -8084,18 +8150,14 @@ var UnderlyingsFileSchema = z10.object({
|
|
|
8084
8150
|
});
|
|
8085
8151
|
var registerUnderlyingSchema = z10.object({
|
|
8086
8152
|
underlying: z10.string().min(1).max(16).regex(TICKER_RE).describe("Canonical underlying symbol, e.g. SPX"),
|
|
8087
|
-
roots: z10.array(z10.string().min(1).max(16).regex(TICKER_RE)).min(1).max(32).describe(
|
|
8088
|
-
"OCC roots that resolve to this underlying, e.g. ['SPX','SPXW','SPXQ']"
|
|
8089
|
-
)
|
|
8153
|
+
roots: z10.array(z10.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
8154
|
});
|
|
8091
8155
|
var unregisterUnderlyingSchema = z10.object({
|
|
8092
8156
|
underlying: z10.string().min(1).max(16).regex(TICKER_RE).describe("Underlying to remove. Bundled defaults cannot be removed.")
|
|
8093
8157
|
});
|
|
8094
8158
|
var listUnderlyingsSchema = z10.object({});
|
|
8095
8159
|
var resolveRootSchema = z10.object({
|
|
8096
|
-
input: z10.string().min(1).max(32).describe(
|
|
8097
|
-
"Bare root ('SPXW') or full OCC ticker ('SPXW251219C05000000')"
|
|
8098
|
-
)
|
|
8160
|
+
input: z10.string().min(1).max(32).describe("Bare root ('SPXW') or full OCC ticker ('SPXW251219C05000000')")
|
|
8099
8161
|
});
|
|
8100
8162
|
|
|
8101
8163
|
// src/market/tickers/registry.ts
|
|
@@ -8107,9 +8169,7 @@ function validate(underlying, roots) {
|
|
|
8107
8169
|
}
|
|
8108
8170
|
for (const r of roots) {
|
|
8109
8171
|
if (!TICKER_RE.test(r)) {
|
|
8110
|
-
throw new Error(
|
|
8111
|
-
`TickerRegistry: invalid root "${r}" \u2014 must match ${TICKER_RE.source}`
|
|
8112
|
-
);
|
|
8172
|
+
throw new Error(`TickerRegistry: invalid root "${r}" \u2014 must match ${TICKER_RE.source}`);
|
|
8113
8173
|
}
|
|
8114
8174
|
}
|
|
8115
8175
|
}
|
|
@@ -8192,9 +8252,7 @@ var TickerRegistry = class {
|
|
|
8192
8252
|
unregister(underlying) {
|
|
8193
8253
|
const entry = this.entries.get(underlying);
|
|
8194
8254
|
if (!entry) {
|
|
8195
|
-
throw new Error(
|
|
8196
|
-
`TickerRegistry.unregister: unknown underlying "${underlying}"`
|
|
8197
|
-
);
|
|
8255
|
+
throw new Error(`TickerRegistry.unregister: unknown underlying "${underlying}"`);
|
|
8198
8256
|
}
|
|
8199
8257
|
if (entry.source === "default") {
|
|
8200
8258
|
throw new Error(
|
|
@@ -8482,7 +8540,13 @@ var MarketIngestor = class {
|
|
|
8482
8540
|
assetClass
|
|
8483
8541
|
});
|
|
8484
8542
|
} catch (error) {
|
|
8485
|
-
const mapped = this.mapProviderFailure(
|
|
8543
|
+
const mapped = this.mapProviderFailure(
|
|
8544
|
+
provider,
|
|
8545
|
+
"bars",
|
|
8546
|
+
normalizedTicker,
|
|
8547
|
+
error,
|
|
8548
|
+
assetClass
|
|
8549
|
+
);
|
|
8486
8550
|
if (mapped) return mapped;
|
|
8487
8551
|
throw error;
|
|
8488
8552
|
}
|
|
@@ -8735,11 +8799,7 @@ var MarketIngestor = class {
|
|
|
8735
8799
|
if (mapped) return mapped;
|
|
8736
8800
|
throw error;
|
|
8737
8801
|
}
|
|
8738
|
-
const written = await this.writeQuotesForTicker(
|
|
8739
|
-
provider,
|
|
8740
|
-
ticker,
|
|
8741
|
-
quotes
|
|
8742
|
-
);
|
|
8802
|
+
const written = await this.writeQuotesForTicker(provider, ticker, quotes);
|
|
8743
8803
|
totalRows += written.rowsWritten;
|
|
8744
8804
|
if (written.minDate && (!minDate || written.minDate < minDate)) minDate = written.minDate;
|
|
8745
8805
|
if (written.maxDate && (!maxDate || written.maxDate > maxDate)) maxDate = written.maxDate;
|
|
@@ -8775,12 +8835,7 @@ var MarketIngestor = class {
|
|
|
8775
8835
|
for (const underlying of underlyings) {
|
|
8776
8836
|
const upperUnderlying = underlying.toUpperCase();
|
|
8777
8837
|
for (const date of dates) {
|
|
8778
|
-
const drain = await this.drainBulkQuotes(
|
|
8779
|
-
provider,
|
|
8780
|
-
upperUnderlying,
|
|
8781
|
-
date,
|
|
8782
|
-
onProgress
|
|
8783
|
-
);
|
|
8838
|
+
const drain = await this.drainBulkQuotes(provider, upperUnderlying, date, onProgress);
|
|
8784
8839
|
if (drain.rowsWritten > 0) {
|
|
8785
8840
|
totalRows += drain.rowsWritten;
|
|
8786
8841
|
if (!minDate || date < minDate) minDate = date;
|
|
@@ -8840,7 +8895,11 @@ var MarketIngestor = class {
|
|
|
8840
8895
|
totalContracts: info.totalContracts
|
|
8841
8896
|
});
|
|
8842
8897
|
} : void 0;
|
|
8843
|
-
const stream = provider.fetchBulkQuotes({
|
|
8898
|
+
const stream = provider.fetchBulkQuotes({
|
|
8899
|
+
underlying: upperUnderlying,
|
|
8900
|
+
date,
|
|
8901
|
+
onGroupComplete
|
|
8902
|
+
});
|
|
8844
8903
|
for await (const chunk of stream) {
|
|
8845
8904
|
for (const row of chunk) {
|
|
8846
8905
|
const root = extractRoot(row.ticker);
|
|
@@ -8891,15 +8950,12 @@ var MarketIngestor = class {
|
|
|
8891
8950
|
);
|
|
8892
8951
|
} catch (error) {
|
|
8893
8952
|
const message = error instanceof Error ? error.message : String(error);
|
|
8894
|
-
console.warn(
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
error: message
|
|
8901
|
-
}
|
|
8902
|
-
);
|
|
8953
|
+
console.warn("[drainBulkQuotes] enrichQuoteRows failed; skipping batch", {
|
|
8954
|
+
underlying: resolvedUnderlying,
|
|
8955
|
+
date,
|
|
8956
|
+
rows: rows.length,
|
|
8957
|
+
error: message
|
|
8958
|
+
});
|
|
8903
8959
|
skipped.push({
|
|
8904
8960
|
underlying: resolvedUnderlying,
|
|
8905
8961
|
date,
|
|
@@ -8986,16 +9042,13 @@ var MarketIngestor = class {
|
|
|
8986
9042
|
);
|
|
8987
9043
|
} catch (error) {
|
|
8988
9044
|
const message = error instanceof Error ? error.message : String(error);
|
|
8989
|
-
console.warn(
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
error: message
|
|
8997
|
-
}
|
|
8998
|
-
);
|
|
9045
|
+
console.warn("[writeQuotesForTicker] enrichQuoteRows failed; skipping batch", {
|
|
9046
|
+
underlying,
|
|
9047
|
+
date,
|
|
9048
|
+
ticker,
|
|
9049
|
+
rows: rows.length,
|
|
9050
|
+
error: message
|
|
9051
|
+
});
|
|
8999
9052
|
skipped.push({
|
|
9000
9053
|
underlying,
|
|
9001
9054
|
date,
|
|
@@ -9062,7 +9115,12 @@ var MarketIngestor = class {
|
|
|
9062
9115
|
for (const underlying of opts.underlyings) {
|
|
9063
9116
|
const upperUnderlying = underlying.toUpperCase();
|
|
9064
9117
|
const assetClass = this.detectAssetClass(upperUnderlying);
|
|
9065
|
-
const unsupported = this.preflightProviderSupport(
|
|
9118
|
+
const unsupported = this.preflightProviderSupport(
|
|
9119
|
+
provider,
|
|
9120
|
+
"chain",
|
|
9121
|
+
upperUnderlying,
|
|
9122
|
+
assetClass
|
|
9123
|
+
);
|
|
9066
9124
|
if (unsupported) return unsupported;
|
|
9067
9125
|
const dates = this.enumerateDates(opts.from, opts.to);
|
|
9068
9126
|
for (const date of dates) {
|
|
@@ -9074,7 +9132,13 @@ var MarketIngestor = class {
|
|
|
9074
9132
|
expired: true
|
|
9075
9133
|
});
|
|
9076
9134
|
} catch (error) {
|
|
9077
|
-
const mapped = this.mapProviderFailure(
|
|
9135
|
+
const mapped = this.mapProviderFailure(
|
|
9136
|
+
provider,
|
|
9137
|
+
"chain",
|
|
9138
|
+
upperUnderlying,
|
|
9139
|
+
error,
|
|
9140
|
+
assetClass
|
|
9141
|
+
);
|
|
9078
9142
|
if (mapped) return mapped;
|
|
9079
9143
|
throw error;
|
|
9080
9144
|
}
|
|
@@ -9222,43 +9286,75 @@ var MarketIngestor = class {
|
|
|
9222
9286
|
case "spot_bars": {
|
|
9223
9287
|
const ticker = opts.partition.ticker;
|
|
9224
9288
|
if (!ticker) {
|
|
9225
|
-
return {
|
|
9289
|
+
return {
|
|
9290
|
+
status: "error",
|
|
9291
|
+
rowsWritten: 0,
|
|
9292
|
+
error: "partition.ticker is required for datasetType='spot_bars'"
|
|
9293
|
+
};
|
|
9226
9294
|
}
|
|
9227
9295
|
const { rowCount } = await this.deps.stores.spot.writeFromSelect(
|
|
9228
9296
|
{ ticker: ticker.toUpperCase(), date: partitionDate },
|
|
9229
9297
|
opts.selectSql
|
|
9230
9298
|
);
|
|
9231
|
-
return {
|
|
9299
|
+
return {
|
|
9300
|
+
status: "ok",
|
|
9301
|
+
rowsWritten: rowCount,
|
|
9302
|
+
dateRange: { from: partitionDate, to: partitionDate }
|
|
9303
|
+
};
|
|
9232
9304
|
}
|
|
9233
9305
|
case "option_quotes": {
|
|
9234
9306
|
const underlying = opts.partition.underlying;
|
|
9235
9307
|
if (!underlying) {
|
|
9236
|
-
return {
|
|
9308
|
+
return {
|
|
9309
|
+
status: "error",
|
|
9310
|
+
rowsWritten: 0,
|
|
9311
|
+
error: "partition.underlying is required for datasetType='option_quotes'"
|
|
9312
|
+
};
|
|
9237
9313
|
}
|
|
9238
9314
|
const { rowCount } = await this.deps.stores.quote.writeFromSelect(
|
|
9239
9315
|
{ underlying: underlying.toUpperCase(), date: partitionDate },
|
|
9240
9316
|
opts.selectSql
|
|
9241
9317
|
);
|
|
9242
|
-
return {
|
|
9318
|
+
return {
|
|
9319
|
+
status: "ok",
|
|
9320
|
+
rowsWritten: rowCount,
|
|
9321
|
+
dateRange: { from: partitionDate, to: partitionDate }
|
|
9322
|
+
};
|
|
9243
9323
|
}
|
|
9244
9324
|
case "option_chain": {
|
|
9245
9325
|
const underlying = opts.partition.underlying;
|
|
9246
9326
|
if (!underlying) {
|
|
9247
|
-
return {
|
|
9327
|
+
return {
|
|
9328
|
+
status: "error",
|
|
9329
|
+
rowsWritten: 0,
|
|
9330
|
+
error: "partition.underlying is required for datasetType='option_chain'"
|
|
9331
|
+
};
|
|
9248
9332
|
}
|
|
9249
9333
|
const { rowCount } = await this.deps.stores.chain.writeFromSelect(
|
|
9250
9334
|
{ underlying: underlying.toUpperCase(), date: partitionDate },
|
|
9251
9335
|
opts.selectSql
|
|
9252
9336
|
);
|
|
9253
|
-
return {
|
|
9337
|
+
return {
|
|
9338
|
+
status: "ok",
|
|
9339
|
+
rowsWritten: rowCount,
|
|
9340
|
+
dateRange: { from: partitionDate, to: partitionDate }
|
|
9341
|
+
};
|
|
9254
9342
|
}
|
|
9255
9343
|
default: {
|
|
9256
9344
|
const _exhaustive = opts.datasetType;
|
|
9257
|
-
return {
|
|
9345
|
+
return {
|
|
9346
|
+
status: "error",
|
|
9347
|
+
rowsWritten: 0,
|
|
9348
|
+
error: `Unknown datasetType: ${String(_exhaustive)}`
|
|
9349
|
+
};
|
|
9258
9350
|
}
|
|
9259
9351
|
}
|
|
9260
9352
|
} catch (err) {
|
|
9261
|
-
return {
|
|
9353
|
+
return {
|
|
9354
|
+
status: "error",
|
|
9355
|
+
rowsWritten: 0,
|
|
9356
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9357
|
+
};
|
|
9262
9358
|
}
|
|
9263
9359
|
}
|
|
9264
9360
|
async computeVixContext(opts) {
|
|
@@ -9348,7 +9444,11 @@ var MarketIngestor = class {
|
|
|
9348
9444
|
const coverage = {};
|
|
9349
9445
|
for (const ticker of opts.spotTickers) {
|
|
9350
9446
|
try {
|
|
9351
|
-
const cov = await this.deps.stores.spot.getCoverage(
|
|
9447
|
+
const cov = await this.deps.stores.spot.getCoverage(
|
|
9448
|
+
ticker.toUpperCase(),
|
|
9449
|
+
opts.asOf,
|
|
9450
|
+
opts.asOf
|
|
9451
|
+
);
|
|
9352
9452
|
coverage[ticker] = {
|
|
9353
9453
|
totalDates: cov.totalDates,
|
|
9354
9454
|
dateRange: cov.earliest && cov.latest ? { from: cov.earliest, to: cov.latest } : void 0
|
|
@@ -9372,7 +9472,13 @@ var MarketIngestor = class {
|
|
|
9372
9472
|
else status = "ok";
|
|
9373
9473
|
return {
|
|
9374
9474
|
status,
|
|
9375
|
-
perOperation: {
|
|
9475
|
+
perOperation: {
|
|
9476
|
+
spot: spotResults,
|
|
9477
|
+
chain: chainResults,
|
|
9478
|
+
quotes: quoteResults,
|
|
9479
|
+
openInterest: openInterestResults,
|
|
9480
|
+
vixContext
|
|
9481
|
+
},
|
|
9376
9482
|
coverage,
|
|
9377
9483
|
errors,
|
|
9378
9484
|
...aggregateSkipped.length > 0 ? { skipped: aggregateSkipped } : {}
|
|
@@ -9455,9 +9561,7 @@ function selectVerificationSampleDates(fromDate = "2022-01-01", toDate = (/* @__
|
|
|
9455
9561
|
...PHASE_5_KNOWN_EVENTS.map((s) => s.date),
|
|
9456
9562
|
...PHASE_5_STRUCTURAL_DATES.map((s) => s.date)
|
|
9457
9563
|
]);
|
|
9458
|
-
const candidates = enumerateWeekdays(fromDate, toDate).filter(
|
|
9459
|
-
(d) => !selectedSet.has(d)
|
|
9460
|
-
);
|
|
9564
|
+
const candidates = enumerateWeekdays(fromDate, toDate).filter((d) => !selectedSet.has(d));
|
|
9461
9565
|
const random = [];
|
|
9462
9566
|
const pool = [...candidates];
|
|
9463
9567
|
for (let i = 0; i < randomCount && pool.length > 0; i++) {
|