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
|
@@ -4,10 +4,7 @@ import { getDataRoot } from "./data-root.ts";
|
|
|
4
4
|
import { writeParquetAtomic, writeParquetPartition } from "./parquet-writer.ts";
|
|
5
5
|
|
|
6
6
|
export type CanonicalSingleFileDataset = "daily" | "date_context";
|
|
7
|
-
export type CanonicalPartitionedDataset =
|
|
8
|
-
| "intraday"
|
|
9
|
-
| "option_chain"
|
|
10
|
-
| "option_quote_minutes";
|
|
7
|
+
export type CanonicalPartitionedDataset = "intraday" | "option_chain" | "option_quote_minutes";
|
|
11
8
|
export type CanonicalMarketDataset = CanonicalSingleFileDataset | CanonicalPartitionedDataset;
|
|
12
9
|
|
|
13
10
|
const SINGLE_FILE_DATASETS: Record<CanonicalSingleFileDataset, string> = {
|
|
@@ -81,12 +78,24 @@ export interface DatasetDef {
|
|
|
81
78
|
}
|
|
82
79
|
|
|
83
80
|
export const DATASETS_V3: Record<string, DatasetDef> = {
|
|
84
|
-
spot:
|
|
85
|
-
enriched:
|
|
86
|
-
enriched_context:
|
|
87
|
-
option_chain:
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
spot: { subdir: "spot", partitionKeys: ["ticker", "date"], filename: "data.parquet" },
|
|
82
|
+
enriched: { subdir: "enriched", partitionKeys: ["ticker"], filename: "data.parquet" },
|
|
83
|
+
enriched_context: { subdir: "enriched/context", partitionKeys: [], filename: "data.parquet" },
|
|
84
|
+
option_chain: {
|
|
85
|
+
subdir: "option_chain",
|
|
86
|
+
partitionKeys: ["underlying", "date"],
|
|
87
|
+
filename: "data.parquet",
|
|
88
|
+
},
|
|
89
|
+
option_quote_minutes: {
|
|
90
|
+
subdir: "option_quote_minutes",
|
|
91
|
+
partitionKeys: ["underlying", "date"],
|
|
92
|
+
filename: "data.parquet",
|
|
93
|
+
},
|
|
94
|
+
option_oi_daily: {
|
|
95
|
+
subdir: "option_oi_daily",
|
|
96
|
+
partitionKeys: ["underlying", "date"],
|
|
97
|
+
filename: "data.parquet",
|
|
98
|
+
},
|
|
90
99
|
};
|
|
91
100
|
|
|
92
101
|
// ------ Per-dataset write helpers ------
|
|
@@ -102,12 +111,18 @@ export const DATASETS_V3: Record<string, DatasetDef> = {
|
|
|
102
111
|
|
|
103
112
|
export async function writeSpotPartition(
|
|
104
113
|
conn: DuckDBConnection,
|
|
105
|
-
args: {
|
|
114
|
+
args: {
|
|
115
|
+
dataDir: string;
|
|
116
|
+
ticker: string;
|
|
117
|
+
date: string;
|
|
118
|
+
selectQuery: string;
|
|
119
|
+
compression?: string;
|
|
120
|
+
},
|
|
106
121
|
): Promise<{ rowCount: number }> {
|
|
107
122
|
const def = DATASETS_V3.spot;
|
|
108
123
|
return writeParquetPartition(conn, {
|
|
109
124
|
baseDir: path.join(resolveMarketDir(args.dataDir), def.subdir),
|
|
110
|
-
partitions: { ticker: args.ticker, date: args.date },
|
|
125
|
+
partitions: { ticker: args.ticker, date: args.date }, // order matches def.partitionKeys
|
|
111
126
|
selectQuery: args.selectQuery,
|
|
112
127
|
compression: args.compression,
|
|
113
128
|
filename: def.filename,
|
|
@@ -116,7 +131,13 @@ export async function writeSpotPartition(
|
|
|
116
131
|
|
|
117
132
|
export async function writeChainPartition(
|
|
118
133
|
conn: DuckDBConnection,
|
|
119
|
-
args: {
|
|
134
|
+
args: {
|
|
135
|
+
dataDir: string;
|
|
136
|
+
underlying: string;
|
|
137
|
+
date: string;
|
|
138
|
+
selectQuery: string;
|
|
139
|
+
compression?: string;
|
|
140
|
+
},
|
|
120
141
|
): Promise<{ rowCount: number }> {
|
|
121
142
|
const def = DATASETS_V3.option_chain;
|
|
122
143
|
return writeParquetPartition(conn, {
|
|
@@ -130,7 +151,13 @@ export async function writeChainPartition(
|
|
|
130
151
|
|
|
131
152
|
export async function writeQuoteMinutesPartition(
|
|
132
153
|
conn: DuckDBConnection,
|
|
133
|
-
args: {
|
|
154
|
+
args: {
|
|
155
|
+
dataDir: string;
|
|
156
|
+
underlying: string;
|
|
157
|
+
date: string;
|
|
158
|
+
selectQuery: string;
|
|
159
|
+
compression?: string;
|
|
160
|
+
},
|
|
134
161
|
): Promise<{ rowCount: number }> {
|
|
135
162
|
const def = DATASETS_V3.option_quote_minutes;
|
|
136
163
|
// Sort rows by (ticker, time) before writing so DuckDB row groups in the
|
|
@@ -162,7 +189,13 @@ export async function writeQuoteMinutesPartition(
|
|
|
162
189
|
|
|
163
190
|
export async function writeOiDailyPartition(
|
|
164
191
|
conn: DuckDBConnection,
|
|
165
|
-
args: {
|
|
192
|
+
args: {
|
|
193
|
+
dataDir: string;
|
|
194
|
+
underlying: string;
|
|
195
|
+
date: string;
|
|
196
|
+
selectQuery: string;
|
|
197
|
+
compression?: string;
|
|
198
|
+
},
|
|
166
199
|
): Promise<{ rowCount: number }> {
|
|
167
200
|
const def = DATASETS_V3.option_oi_daily;
|
|
168
201
|
// Sort rows by ticker before writing so DuckDB row groups carry tight
|
package/src/db/market-schemas.ts
CHANGED
|
@@ -37,9 +37,7 @@ import type { DuckDBConnection } from "@duckdb/node-api";
|
|
|
37
37
|
*
|
|
38
38
|
* @param conn - Active DuckDB connection with market catalog attached
|
|
39
39
|
*/
|
|
40
|
-
export async function ensureMutableMarketTables(
|
|
41
|
-
conn: DuckDBConnection,
|
|
42
|
-
): Promise<void> {
|
|
40
|
+
export async function ensureMutableMarketTables(conn: DuckDBConnection): Promise<void> {
|
|
43
41
|
// Legacy coverage-tracking table CREATE removed — grep-verified zero readers/writers today.
|
|
44
42
|
// Coverage is now derived from store.getCoverage() (Parquet: readdirSync; DuckDB: SELECT DISTINCT).
|
|
45
43
|
|
|
@@ -97,8 +95,16 @@ export async function ensureMarketDataTables(conn: DuckDBConnection): Promise<vo
|
|
|
97
95
|
// createMarketParquetViews earlier in the connection sequence). Dropping them
|
|
98
96
|
// here would destroy legitimate v3.0 views.
|
|
99
97
|
for (const name of ["daily", "date_context", "intraday", "data_coverage"]) {
|
|
100
|
-
try {
|
|
101
|
-
|
|
98
|
+
try {
|
|
99
|
+
await conn.run(`DROP VIEW IF EXISTS market.${name}`);
|
|
100
|
+
} catch {
|
|
101
|
+
/* wrong type */
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
await conn.run(`DROP TABLE IF EXISTS market.${name}`);
|
|
105
|
+
} catch {
|
|
106
|
+
/* wrong type */
|
|
107
|
+
}
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
// ============================================================================
|
package/src/db/market-views.ts
CHANGED
|
@@ -27,10 +27,7 @@
|
|
|
27
27
|
import type { DuckDBConnection } from "@duckdb/node-api";
|
|
28
28
|
import { existsSync, readdirSync } from "fs";
|
|
29
29
|
import * as path from "path";
|
|
30
|
-
import {
|
|
31
|
-
resolveCanonicalMarketPartitionDir,
|
|
32
|
-
resolveMarketDir,
|
|
33
|
-
} from "./market-datasets.ts";
|
|
30
|
+
import { resolveCanonicalMarketPartitionDir, resolveMarketDir } from "./market-datasets.ts";
|
|
34
31
|
import {
|
|
35
32
|
describeReadParquetColumns,
|
|
36
33
|
quoteParquetCanonicalProjection,
|
|
@@ -51,10 +48,7 @@ export interface ViewCreationResult {
|
|
|
51
48
|
* option_chain, option_quote_minutes — all date-only) continue working unchanged.
|
|
52
49
|
* Pass "ticker" for ticker-first partitioning (the 3.0 spot directory).
|
|
53
50
|
*/
|
|
54
|
-
function hasParquetPartitions(
|
|
55
|
-
dir: string,
|
|
56
|
-
partitionKey: string = "date",
|
|
57
|
-
): boolean {
|
|
51
|
+
function hasParquetPartitions(dir: string, partitionKey: string = "date"): boolean {
|
|
58
52
|
if (!existsSync(dir)) return false;
|
|
59
53
|
try {
|
|
60
54
|
const prefix = `${partitionKey}=`;
|
|
@@ -136,7 +130,11 @@ export async function createMarketParquetViews(
|
|
|
136
130
|
// (`option_chain/underlying=X/date=Y/...`) — the current store writers
|
|
137
131
|
// produce the underlying-first layout.
|
|
138
132
|
const hiveViews: Array<{ name: string; subdir: string; partitionKey: string }> = [
|
|
139
|
-
{
|
|
133
|
+
{
|
|
134
|
+
name: "option_chain",
|
|
135
|
+
subdir: resolveCanonicalMarketPartitionDir(dataDir, "option_chain"),
|
|
136
|
+
partitionKey: "underlying",
|
|
137
|
+
},
|
|
140
138
|
];
|
|
141
139
|
|
|
142
140
|
for (const { name, subdir, partitionKey } of hiveViews) {
|
|
@@ -144,10 +142,19 @@ export async function createMarketParquetViews(
|
|
|
144
142
|
// Accept either the dataset's primary partition key (e.g. `underlying` for
|
|
145
143
|
// Market Data 3.0 option_chain) or the legacy `date` top-level partition.
|
|
146
144
|
const hasNewLayout = existsSync(dirPath) && hasParquetPartitions(dirPath, partitionKey);
|
|
147
|
-
const hasLegacyLayout =
|
|
145
|
+
const hasLegacyLayout =
|
|
146
|
+
partitionKey !== "date" && existsSync(dirPath) && hasParquetPartitions(dirPath, "date");
|
|
148
147
|
if (hasNewLayout || hasLegacyLayout) {
|
|
149
|
-
try {
|
|
150
|
-
|
|
148
|
+
try {
|
|
149
|
+
await conn.run(`DROP VIEW IF EXISTS market.${name}`);
|
|
150
|
+
} catch {
|
|
151
|
+
/* wrong type */
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
await conn.run(`DROP TABLE IF EXISTS market.${name}`);
|
|
155
|
+
} catch {
|
|
156
|
+
/* wrong type */
|
|
157
|
+
}
|
|
151
158
|
// Glob on data.parquet (not *.parquet) so DuckDB's in-flight
|
|
152
159
|
// tmp_data.parquet files — created by concurrent COPY ... TO writers
|
|
153
160
|
// during atomic parquet replacement — never match. Matching a mid-write
|
|
@@ -158,7 +165,11 @@ export async function createMarketParquetViews(
|
|
|
158
165
|
);
|
|
159
166
|
viewsCreated.push(name);
|
|
160
167
|
} else {
|
|
161
|
-
try {
|
|
168
|
+
try {
|
|
169
|
+
await conn.run(`DROP VIEW IF EXISTS market.${name}`);
|
|
170
|
+
} catch {
|
|
171
|
+
/* not a view */
|
|
172
|
+
}
|
|
162
173
|
tablesKept.push(name);
|
|
163
174
|
}
|
|
164
175
|
}
|
|
@@ -175,8 +186,16 @@ export async function createMarketParquetViews(
|
|
|
175
186
|
const optionQuoteHasNewLayout = hasParquetPartitions(optionMinuteQuoteDir, "underlying");
|
|
176
187
|
const optionQuoteHasLegacyLayout = hasParquetPartitions(optionMinuteQuoteDir, "date");
|
|
177
188
|
if (optionQuoteHasNewLayout || optionQuoteHasLegacyLayout) {
|
|
178
|
-
try {
|
|
179
|
-
|
|
189
|
+
try {
|
|
190
|
+
await conn.run("DROP VIEW IF EXISTS market.option_quote_minutes");
|
|
191
|
+
} catch {
|
|
192
|
+
/* wrong type */
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
await conn.run("DROP TABLE IF EXISTS market.option_quote_minutes");
|
|
196
|
+
} catch {
|
|
197
|
+
/* wrong type */
|
|
198
|
+
}
|
|
180
199
|
// See hive-views glob comment: scope to data.parquet so concurrent
|
|
181
200
|
// COPY ... TO writers' tmp_data.parquet never trips DESCRIBE.
|
|
182
201
|
const quoteSource = readParquetGlobSql(`${optionMinuteQuoteDir}/**/data.parquet`);
|
|
@@ -189,7 +208,11 @@ export async function createMarketParquetViews(
|
|
|
189
208
|
);
|
|
190
209
|
viewsCreated.push("option_quote_minutes");
|
|
191
210
|
} else {
|
|
192
|
-
try {
|
|
211
|
+
try {
|
|
212
|
+
await conn.run("DROP VIEW IF EXISTS market.option_quote_minutes");
|
|
213
|
+
} catch {
|
|
214
|
+
/* not a view */
|
|
215
|
+
}
|
|
193
216
|
tablesKept.push("option_quote_minutes");
|
|
194
217
|
}
|
|
195
218
|
|
|
@@ -197,14 +220,30 @@ export async function createMarketParquetViews(
|
|
|
197
220
|
// an older run. This keeps the public SQL surface aligned with the current
|
|
198
221
|
// architecture where quote greeks live inline on option_quote_minutes and
|
|
199
222
|
// missing values are computed in memory at query time.
|
|
200
|
-
try {
|
|
201
|
-
|
|
223
|
+
try {
|
|
224
|
+
await conn.run("DROP VIEW IF EXISTS market.option_greeks_minutes");
|
|
225
|
+
} catch {
|
|
226
|
+
/* wrong type */
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
await conn.run("DROP TABLE IF EXISTS market.option_greeks_minutes");
|
|
230
|
+
} catch {
|
|
231
|
+
/* wrong type */
|
|
232
|
+
}
|
|
202
233
|
|
|
203
234
|
// Remove the retired delta-index surface from the market schema if it exists
|
|
204
235
|
// from an older run. Delta selection now reads directly from
|
|
205
236
|
// market.option_quote_minutes greeks instead of a second persisted dataset.
|
|
206
|
-
try {
|
|
207
|
-
|
|
237
|
+
try {
|
|
238
|
+
await conn.run("DROP VIEW IF EXISTS market.option_delta_index");
|
|
239
|
+
} catch {
|
|
240
|
+
/* wrong type */
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
await conn.run("DROP TABLE IF EXISTS market.option_delta_index");
|
|
244
|
+
} catch {
|
|
245
|
+
/* wrong type */
|
|
246
|
+
}
|
|
208
247
|
|
|
209
248
|
// ============================================================================
|
|
210
249
|
// Canonical store views (spot, enriched, enriched_context, spot_daily).
|
|
@@ -215,8 +254,16 @@ export async function createMarketParquetViews(
|
|
|
215
254
|
// market.spot — ticker-first Hive partitioning: spot/ticker=X/date=Y/data.parquet
|
|
216
255
|
const spotDir = path.join(resolveMarketDir(dataDir), "spot");
|
|
217
256
|
if (hasParquetPartitions(spotDir, "ticker")) {
|
|
218
|
-
try {
|
|
219
|
-
|
|
257
|
+
try {
|
|
258
|
+
await conn.run("DROP VIEW IF EXISTS market.spot");
|
|
259
|
+
} catch {
|
|
260
|
+
/* wrong type */
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
await conn.run("DROP TABLE IF EXISTS market.spot");
|
|
264
|
+
} catch {
|
|
265
|
+
/* wrong type */
|
|
266
|
+
}
|
|
220
267
|
// Scope glob to data.parquet (see tmp_data.parquet race note above).
|
|
221
268
|
await conn.run(
|
|
222
269
|
`CREATE OR REPLACE VIEW market.spot AS
|
|
@@ -224,36 +271,64 @@ export async function createMarketParquetViews(
|
|
|
224
271
|
);
|
|
225
272
|
viewsCreated.push("spot");
|
|
226
273
|
} else {
|
|
227
|
-
try {
|
|
274
|
+
try {
|
|
275
|
+
await conn.run("DROP VIEW IF EXISTS market.spot");
|
|
276
|
+
} catch {
|
|
277
|
+
/* not a view */
|
|
278
|
+
}
|
|
228
279
|
tablesKept.push("spot");
|
|
229
280
|
}
|
|
230
281
|
|
|
231
282
|
// market.enriched — per-ticker single file: enriched/ticker=X/data.parquet (no date partition)
|
|
232
283
|
const enrichedDir = path.join(resolveMarketDir(dataDir), "enriched");
|
|
233
284
|
if (hasEnrichedTickerFiles(enrichedDir)) {
|
|
234
|
-
try {
|
|
235
|
-
|
|
285
|
+
try {
|
|
286
|
+
await conn.run("DROP VIEW IF EXISTS market.enriched");
|
|
287
|
+
} catch {
|
|
288
|
+
/* wrong type */
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
await conn.run("DROP TABLE IF EXISTS market.enriched");
|
|
292
|
+
} catch {
|
|
293
|
+
/* wrong type */
|
|
294
|
+
}
|
|
236
295
|
await conn.run(
|
|
237
296
|
`CREATE OR REPLACE VIEW market.enriched AS
|
|
238
297
|
SELECT * FROM read_parquet('${enrichedDir}/ticker=*/data.parquet', hive_partitioning=true)`,
|
|
239
298
|
);
|
|
240
299
|
viewsCreated.push("enriched");
|
|
241
300
|
} else {
|
|
242
|
-
try {
|
|
301
|
+
try {
|
|
302
|
+
await conn.run("DROP VIEW IF EXISTS market.enriched");
|
|
303
|
+
} catch {
|
|
304
|
+
/* not a view */
|
|
305
|
+
}
|
|
243
306
|
tablesKept.push("enriched");
|
|
244
307
|
}
|
|
245
308
|
|
|
246
309
|
// market.enriched_context — global single file: enriched/context/data.parquet (no partition)
|
|
247
310
|
if (hasEnrichedContextFile(enrichedDir)) {
|
|
248
|
-
try {
|
|
249
|
-
|
|
311
|
+
try {
|
|
312
|
+
await conn.run("DROP VIEW IF EXISTS market.enriched_context");
|
|
313
|
+
} catch {
|
|
314
|
+
/* wrong type */
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
await conn.run("DROP TABLE IF EXISTS market.enriched_context");
|
|
318
|
+
} catch {
|
|
319
|
+
/* wrong type */
|
|
320
|
+
}
|
|
250
321
|
await conn.run(
|
|
251
322
|
`CREATE OR REPLACE VIEW market.enriched_context AS
|
|
252
323
|
SELECT * FROM read_parquet('${path.join(enrichedDir, "context", "data.parquet")}')`,
|
|
253
324
|
);
|
|
254
325
|
viewsCreated.push("enriched_context");
|
|
255
326
|
} else {
|
|
256
|
-
try {
|
|
327
|
+
try {
|
|
328
|
+
await conn.run("DROP VIEW IF EXISTS market.enriched_context");
|
|
329
|
+
} catch {
|
|
330
|
+
/* not a view */
|
|
331
|
+
}
|
|
257
332
|
tablesKept.push("enriched_context");
|
|
258
333
|
}
|
|
259
334
|
|
|
@@ -279,8 +354,16 @@ export async function createMarketParquetViews(
|
|
|
279
354
|
}
|
|
280
355
|
})();
|
|
281
356
|
if (spotExists) {
|
|
282
|
-
try {
|
|
283
|
-
|
|
357
|
+
try {
|
|
358
|
+
await conn.run("DROP VIEW IF EXISTS market.spot_daily");
|
|
359
|
+
} catch {
|
|
360
|
+
/* wrong type */
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
await conn.run("DROP TABLE IF EXISTS market.spot_daily");
|
|
364
|
+
} catch {
|
|
365
|
+
/* wrong type */
|
|
366
|
+
}
|
|
284
367
|
await conn.run(`
|
|
285
368
|
CREATE OR REPLACE VIEW market.spot_daily AS
|
|
286
369
|
SELECT ticker, date,
|
|
@@ -305,7 +388,11 @@ export async function createMarketParquetViews(
|
|
|
305
388
|
`);
|
|
306
389
|
viewsCreated.push("spot_daily");
|
|
307
390
|
} else {
|
|
308
|
-
try {
|
|
391
|
+
try {
|
|
392
|
+
await conn.run("DROP VIEW IF EXISTS market.spot_daily");
|
|
393
|
+
} catch {
|
|
394
|
+
/* not a view */
|
|
395
|
+
}
|
|
309
396
|
tablesKept.push("spot_daily");
|
|
310
397
|
}
|
|
311
398
|
|
package/src/db/parquet-writer.ts
CHANGED
|
@@ -111,7 +111,11 @@ export async function writeParquetAtomic(
|
|
|
111
111
|
return { rowCount };
|
|
112
112
|
} catch (writeErr) {
|
|
113
113
|
// Clean up sidecar on failure so the next attempt starts fresh
|
|
114
|
-
try {
|
|
114
|
+
try {
|
|
115
|
+
await fs.unlink(tempPath);
|
|
116
|
+
} catch {
|
|
117
|
+
/* may not exist */
|
|
118
|
+
}
|
|
115
119
|
throw writeErr;
|
|
116
120
|
} finally {
|
|
117
121
|
// Always clean up staging table
|
|
@@ -222,9 +226,7 @@ export async function writeParquetPartition(
|
|
|
222
226
|
// composing any path. Reject unsafe input (separators, whitespace, nulls).
|
|
223
227
|
for (const [k, v] of Object.entries(partitions)) {
|
|
224
228
|
if (!PARTITION_KEY_RE.test(k)) {
|
|
225
|
-
throw new Error(
|
|
226
|
-
`writeParquetPartition: unsafe partition key: ${JSON.stringify(k)}`,
|
|
227
|
-
);
|
|
229
|
+
throw new Error(`writeParquetPartition: unsafe partition key: ${JSON.stringify(k)}`);
|
|
228
230
|
}
|
|
229
231
|
if (!PARTITION_VALUE_RE.test(v)) {
|
|
230
232
|
throw new Error(
|
|
@@ -234,9 +236,7 @@ export async function writeParquetPartition(
|
|
|
234
236
|
}
|
|
235
237
|
|
|
236
238
|
// Preserve insertion order (ES2015 guarantees for string keys).
|
|
237
|
-
const partitionSegments = Object.entries(partitions).map(
|
|
238
|
-
([k, v]) => `${k}=${v}`,
|
|
239
|
-
);
|
|
239
|
+
const partitionSegments = Object.entries(partitions).map(([k, v]) => `${k}=${v}`);
|
|
240
240
|
const targetPath = path.join(opts.baseDir, ...partitionSegments, filename);
|
|
241
241
|
|
|
242
242
|
// writeParquetAtomic (existing, unchanged) handles: staging table → mkdir -p
|
|
@@ -51,13 +51,27 @@ export async function ensureProfilesSchema(conn: DuckDBConnection): Promise<void
|
|
|
51
51
|
ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS position_sizing JSON
|
|
52
52
|
`);
|
|
53
53
|
// Migration: add schema v2 top-level columns (nullable for backward compat)
|
|
54
|
-
await conn.run(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
await conn.run(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
await conn.run(
|
|
54
|
+
await conn.run(
|
|
55
|
+
`ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS underlying VARCHAR`,
|
|
56
|
+
);
|
|
57
|
+
await conn.run(
|
|
58
|
+
`ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS re_entry BOOLEAN`,
|
|
59
|
+
);
|
|
60
|
+
await conn.run(
|
|
61
|
+
`ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS cap_profits BOOLEAN`,
|
|
62
|
+
);
|
|
63
|
+
await conn.run(
|
|
64
|
+
`ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS cap_losses BOOLEAN`,
|
|
65
|
+
);
|
|
66
|
+
await conn.run(
|
|
67
|
+
`ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS require_two_prices_pt BOOLEAN`,
|
|
68
|
+
);
|
|
69
|
+
await conn.run(
|
|
70
|
+
`ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS close_on_completion BOOLEAN`,
|
|
71
|
+
);
|
|
72
|
+
await conn.run(
|
|
73
|
+
`ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS ignore_margin_req BOOLEAN`,
|
|
74
|
+
);
|
|
61
75
|
|
|
62
76
|
// Migration: add strategy execution param columns.
|
|
63
77
|
// block_id stays NOT NULL in the PRIMARY KEY. Template profiles
|
|
@@ -76,7 +90,9 @@ export async function ensureProfilesSchema(conn: DuckDBConnection): Promise<void
|
|
|
76
90
|
{ name: "default_to_date", type: "VARCHAR" },
|
|
77
91
|
];
|
|
78
92
|
for (const col of strategyCols) {
|
|
79
|
-
await conn.run(
|
|
93
|
+
await conn.run(
|
|
94
|
+
`ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS ${col.name} ${col.type}`,
|
|
95
|
+
);
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
// Migration: normalize expected_regimes to canonical Vol_Regime labels
|
|
@@ -111,7 +127,7 @@ async function migrateExpectedRegimes(conn: DuckDBConnection): Promise<void> {
|
|
|
111
127
|
const result = await conn.runAndReadAll(
|
|
112
128
|
`SELECT block_id, strategy_name, expected_regimes
|
|
113
129
|
FROM profiles.strategy_profiles
|
|
114
|
-
WHERE expected_regimes IS NOT NULL AND expected_regimes != '[]'
|
|
130
|
+
WHERE expected_regimes IS NOT NULL AND expected_regimes != '[]'`,
|
|
115
131
|
);
|
|
116
132
|
|
|
117
133
|
for (const row of result.getRows()) {
|
|
@@ -145,7 +161,7 @@ async function migrateExpectedRegimes(conn: DuckDBConnection): Promise<void> {
|
|
|
145
161
|
await conn.run(
|
|
146
162
|
`UPDATE profiles.strategy_profiles
|
|
147
163
|
SET expected_regimes = '${escSql(newJson)}', updated_at = current_timestamp
|
|
148
|
-
WHERE block_id = '${escSql(blockId)}' AND strategy_name = '${escSql(strategyName)}'
|
|
164
|
+
WHERE block_id = '${escSql(blockId)}' AND strategy_name = '${escSql(strategyName)}'`,
|
|
149
165
|
);
|
|
150
166
|
}
|
|
151
167
|
}
|
|
@@ -220,7 +236,7 @@ function rowToProfile(row: unknown[]): StrategyProfile {
|
|
|
220
236
|
const ps = parseJsonObj(row[10]);
|
|
221
237
|
return ps && Object.keys(ps).length > 0 ? ps : undefined;
|
|
222
238
|
})(),
|
|
223
|
-
underlying: row[11] as string | undefined ?? undefined,
|
|
239
|
+
underlying: (row[11] as string | undefined) ?? undefined,
|
|
224
240
|
reEntry: toBoolOrUndef(row[12]),
|
|
225
241
|
capProfits: toBoolOrUndef(row[13]),
|
|
226
242
|
capLosses: toBoolOrUndef(row[14]),
|
|
@@ -252,7 +268,7 @@ const SELECT_COLUMNS = `
|
|
|
252
268
|
export async function upsertProfile(
|
|
253
269
|
conn: DuckDBConnection,
|
|
254
270
|
profile: Omit<StrategyProfile, "createdAt" | "updatedAt">,
|
|
255
|
-
baseDir?: string
|
|
271
|
+
baseDir?: string,
|
|
256
272
|
): Promise<StrategyProfile> {
|
|
257
273
|
if (isParquetMode() && baseDir) {
|
|
258
274
|
return upsertProfileJson(profile, getBlocksDir(baseDir));
|
|
@@ -272,9 +288,12 @@ export async function upsertProfile(
|
|
|
272
288
|
const reEntrySql = profile.reEntry === undefined ? "NULL" : String(profile.reEntry);
|
|
273
289
|
const capProfitsSql = profile.capProfits === undefined ? "NULL" : String(profile.capProfits);
|
|
274
290
|
const capLossesSql = profile.capLosses === undefined ? "NULL" : String(profile.capLosses);
|
|
275
|
-
const requireTwoPricesPTSql =
|
|
276
|
-
|
|
277
|
-
const
|
|
291
|
+
const requireTwoPricesPTSql =
|
|
292
|
+
profile.requireTwoPricesPT === undefined ? "NULL" : String(profile.requireTwoPricesPT);
|
|
293
|
+
const closeOnCompletionSql =
|
|
294
|
+
profile.closeOnCompletion === undefined ? "NULL" : String(profile.closeOnCompletion);
|
|
295
|
+
const ignoreMarginReqSql =
|
|
296
|
+
profile.ignoreMarginReq === undefined ? "NULL" : String(profile.ignoreMarginReq);
|
|
278
297
|
|
|
279
298
|
await conn.run(`
|
|
280
299
|
INSERT INTO profiles.strategy_profiles
|
|
@@ -328,7 +347,7 @@ export async function upsertProfile(
|
|
|
328
347
|
const stored = await getProfile(conn, profile.blockId, profile.strategyName, baseDir);
|
|
329
348
|
if (!stored) {
|
|
330
349
|
throw new Error(
|
|
331
|
-
`Failed to retrieve profile after upsert: ${profile.blockId}/${profile.strategyName}
|
|
350
|
+
`Failed to retrieve profile after upsert: ${profile.blockId}/${profile.strategyName}`,
|
|
332
351
|
);
|
|
333
352
|
}
|
|
334
353
|
return stored;
|
|
@@ -346,7 +365,7 @@ export async function getProfile(
|
|
|
346
365
|
conn: DuckDBConnection,
|
|
347
366
|
blockId: string,
|
|
348
367
|
strategyName: string,
|
|
349
|
-
baseDir?: string
|
|
368
|
+
baseDir?: string,
|
|
350
369
|
): Promise<StrategyProfile | null> {
|
|
351
370
|
if (isParquetMode() && baseDir) {
|
|
352
371
|
return getProfileJson(blockId, strategyName, getBlocksDir(baseDir));
|
|
@@ -372,14 +391,12 @@ export async function getProfile(
|
|
|
372
391
|
export async function listProfiles(
|
|
373
392
|
conn: DuckDBConnection,
|
|
374
393
|
blockId?: string,
|
|
375
|
-
baseDir?: string
|
|
394
|
+
baseDir?: string,
|
|
376
395
|
): Promise<StrategyProfile[]> {
|
|
377
396
|
if (isParquetMode() && baseDir) {
|
|
378
397
|
return listProfilesJson(getBlocksDir(baseDir), blockId);
|
|
379
398
|
}
|
|
380
|
-
const whereClause = blockId
|
|
381
|
-
? `WHERE block_id = '${escSql(blockId)}'`
|
|
382
|
-
: "";
|
|
399
|
+
const whereClause = blockId ? `WHERE block_id = '${escSql(blockId)}'` : "";
|
|
383
400
|
|
|
384
401
|
const result = await conn.runAndReadAll(`
|
|
385
402
|
SELECT ${SELECT_COLUMNS}
|
|
@@ -403,7 +420,7 @@ export async function deleteProfile(
|
|
|
403
420
|
conn: DuckDBConnection,
|
|
404
421
|
blockId: string,
|
|
405
422
|
strategyName: string,
|
|
406
|
-
baseDir?: string
|
|
423
|
+
baseDir?: string,
|
|
407
424
|
): Promise<boolean> {
|
|
408
425
|
if (isParquetMode() && baseDir) {
|
|
409
426
|
return deleteProfileJson(blockId, strategyName, getBlocksDir(baseDir));
|
package/src/db/schemas.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { DuckDBConnection } from "@duckdb/node-api";
|
|
|
13
13
|
export async function tableExists(
|
|
14
14
|
conn: DuckDBConnection,
|
|
15
15
|
schemaName: string,
|
|
16
|
-
tableName: string
|
|
16
|
+
tableName: string,
|
|
17
17
|
): Promise<boolean> {
|
|
18
18
|
const result = await conn.runAndReadAll(`
|
|
19
19
|
SELECT 1
|
|
@@ -27,7 +27,7 @@ async function hasColumn(
|
|
|
27
27
|
conn: DuckDBConnection,
|
|
28
28
|
schemaName: string,
|
|
29
29
|
tableName: string,
|
|
30
|
-
columnName: string
|
|
30
|
+
columnName: string,
|
|
31
31
|
): Promise<boolean> {
|
|
32
32
|
const result = await conn.runAndReadAll(`
|
|
33
33
|
SELECT 1
|
package/src/http-server.ts
CHANGED
|
@@ -34,7 +34,7 @@ export type ServerFactory = () => McpServer;
|
|
|
34
34
|
*/
|
|
35
35
|
export async function startHttpServer(
|
|
36
36
|
serverFactory: ServerFactory,
|
|
37
|
-
options: HttpServerOptions
|
|
37
|
+
options: HttpServerOptions,
|
|
38
38
|
): Promise<Server> {
|
|
39
39
|
const { port, host = "0.0.0.0", auth } = options;
|
|
40
40
|
|
|
@@ -47,18 +47,15 @@ export async function startHttpServer(
|
|
|
47
47
|
|
|
48
48
|
if (auth && !auth.noAuth) {
|
|
49
49
|
// SDK auth modules stay dynamic (externalized by esbuild, resolved from node_modules)
|
|
50
|
-
const { mcpAuthRouter } = await import(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const { requireBearerAuth } = await import(
|
|
54
|
-
"@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"
|
|
55
|
-
);
|
|
50
|
+
const { mcpAuthRouter } = await import("@modelcontextprotocol/sdk/server/auth/router.js");
|
|
51
|
+
const { requireBearerAuth } =
|
|
52
|
+
await import("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js");
|
|
56
53
|
|
|
57
54
|
const provider = new TradeBlocksAuthProvider(auth);
|
|
58
55
|
|
|
59
56
|
// Determine issuer URL (public URL for OAuth discovery metadata)
|
|
60
57
|
const issuerUrl = new URL(
|
|
61
|
-
auth.issuerUrl || `http://${host === "0.0.0.0" ? "localhost" : host}:${port}
|
|
58
|
+
auth.issuerUrl || `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`,
|
|
62
59
|
);
|
|
63
60
|
|
|
64
61
|
// Mount OAuth routes: /.well-known, /authorize, /token, /register
|
|
@@ -103,7 +100,7 @@ export async function startHttpServer(
|
|
|
103
100
|
console.error(`Authentication enabled. Login at ${issuerUrl}/authorize`);
|
|
104
101
|
} else if (auth?.noAuth) {
|
|
105
102
|
console.error(
|
|
106
|
-
"WARNING: Authentication disabled (--no-auth). Only use behind an authenticating reverse proxy."
|
|
103
|
+
"WARNING: Authentication disabled (--no-auth). Only use behind an authenticating reverse proxy.",
|
|
107
104
|
);
|
|
108
105
|
}
|
|
109
106
|
|
|
@@ -140,9 +137,7 @@ export async function startHttpServer(
|
|
|
140
137
|
return new Promise((resolve, reject) => {
|
|
141
138
|
httpServer.on("error", reject);
|
|
142
139
|
httpServer.listen(port, host, () => {
|
|
143
|
-
console.error(
|
|
144
|
-
`TradeBlocks MCP HTTP server listening on http://${host}:${port}/mcp`
|
|
145
|
-
);
|
|
140
|
+
console.error(`TradeBlocks MCP HTTP server listening on http://${host}:${port}/mcp`);
|
|
146
141
|
console.error(`Health check available at http://${host}:${port}/`);
|
|
147
142
|
resolve(httpServer);
|
|
148
143
|
});
|