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
|
@@ -27,7 +27,7 @@ export interface GreeksResult {
|
|
|
27
27
|
theta: number | null; // per day
|
|
28
28
|
vega: number | null; // per 1% IV move
|
|
29
29
|
iv: number | null; // annualized implied volatility (0-N, not percentage)
|
|
30
|
-
model?:
|
|
30
|
+
model?: "bs" | "bachelier"; // which pricing model was used; undefined if IV solve failed
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// --- Internal helpers (exported for Bachelier model and testing) ---
|
|
@@ -50,7 +50,7 @@ export function cdf(x: number): number {
|
|
|
50
50
|
const absX = Math.abs(x);
|
|
51
51
|
|
|
52
52
|
const p = 0.2316419;
|
|
53
|
-
const b1 = 0.
|
|
53
|
+
const b1 = 0.31938153;
|
|
54
54
|
const b2 = -0.356563782;
|
|
55
55
|
const b3 = 1.781477937;
|
|
56
56
|
const b4 = -1.821255978;
|
|
@@ -98,7 +98,7 @@ function d1d2(
|
|
|
98
98
|
* @returns Option price
|
|
99
99
|
*/
|
|
100
100
|
export function bsPrice(
|
|
101
|
-
type:
|
|
101
|
+
type: "call" | "put",
|
|
102
102
|
S: number,
|
|
103
103
|
K: number,
|
|
104
104
|
T: number,
|
|
@@ -108,13 +108,13 @@ export function bsPrice(
|
|
|
108
108
|
): number {
|
|
109
109
|
// Edge case: at or past expiry
|
|
110
110
|
if (T <= 0) {
|
|
111
|
-
return type ===
|
|
111
|
+
return type === "call" ? Math.max(S - K, 0) : Math.max(K - S, 0);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
// Edge case: zero volatility — return discounted intrinsic
|
|
115
115
|
if (sigma <= 0) {
|
|
116
116
|
const forward = S * Math.exp((r - q) * T);
|
|
117
|
-
if (type ===
|
|
117
|
+
if (type === "call") {
|
|
118
118
|
return Math.max(forward - K, 0) * Math.exp(-r * T);
|
|
119
119
|
} else {
|
|
120
120
|
return Math.max(K - forward, 0) * Math.exp(-r * T);
|
|
@@ -123,7 +123,7 @@ export function bsPrice(
|
|
|
123
123
|
|
|
124
124
|
const { d1, d2 } = d1d2(S, K, T, r, q, sigma);
|
|
125
125
|
|
|
126
|
-
if (type ===
|
|
126
|
+
if (type === "call") {
|
|
127
127
|
return S * Math.exp(-q * T) * cdf(d1) - K * Math.exp(-r * T) * cdf(d2);
|
|
128
128
|
} else {
|
|
129
129
|
return K * Math.exp(-r * T) * cdf(-d2) - S * Math.exp(-q * T) * cdf(-d1);
|
|
@@ -136,7 +136,7 @@ export function bsPrice(
|
|
|
136
136
|
* Put: (N(d1) - 1) * e^(-qT)
|
|
137
137
|
*/
|
|
138
138
|
export function bsDelta(
|
|
139
|
-
type:
|
|
139
|
+
type: "call" | "put",
|
|
140
140
|
S: number,
|
|
141
141
|
K: number,
|
|
142
142
|
T: number,
|
|
@@ -145,14 +145,14 @@ export function bsDelta(
|
|
|
145
145
|
sigma: number,
|
|
146
146
|
): number {
|
|
147
147
|
if (T <= 0 || sigma <= 0) {
|
|
148
|
-
if (type ===
|
|
148
|
+
if (type === "call") return S > K ? 1 : 0;
|
|
149
149
|
return S < K ? -1 : 0;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
const { d1 } = d1d2(S, K, T, r, q, sigma);
|
|
153
153
|
const eqT = Math.exp(-q * T);
|
|
154
154
|
|
|
155
|
-
if (type ===
|
|
155
|
+
if (type === "call") {
|
|
156
156
|
return cdf(d1) * eqT;
|
|
157
157
|
} else {
|
|
158
158
|
return (cdf(d1) - 1) * eqT;
|
|
@@ -182,7 +182,7 @@ export function bsGamma(
|
|
|
182
182
|
* Returns the daily time decay (negative for long options).
|
|
183
183
|
*/
|
|
184
184
|
export function bsTheta(
|
|
185
|
-
type:
|
|
185
|
+
type: "call" | "put",
|
|
186
186
|
S: number,
|
|
187
187
|
K: number,
|
|
188
188
|
T: number,
|
|
@@ -200,7 +200,7 @@ export function bsTheta(
|
|
|
200
200
|
// First term: -(S * e^(-qT) * N'(d1) * sigma) / (2 * sqrt(T))
|
|
201
201
|
const term1 = -(S * eqT * pdf(d1) * sigma) / (2 * sqrtT);
|
|
202
202
|
|
|
203
|
-
if (type ===
|
|
203
|
+
if (type === "call") {
|
|
204
204
|
const term2 = q * S * eqT * cdf(d1);
|
|
205
205
|
const term3 = -r * K * erT * cdf(d2);
|
|
206
206
|
return (term1 - term2 - term3) / 365;
|
|
@@ -244,7 +244,7 @@ export function bsVega(
|
|
|
244
244
|
* @returns Implied volatility or null if convergence fails
|
|
245
245
|
*/
|
|
246
246
|
export function solveIV(
|
|
247
|
-
type:
|
|
247
|
+
type: "call" | "put",
|
|
248
248
|
marketPrice: number,
|
|
249
249
|
S: number,
|
|
250
250
|
K: number,
|
|
@@ -323,7 +323,7 @@ export function solveIV(
|
|
|
323
323
|
* @returns Option price
|
|
324
324
|
*/
|
|
325
325
|
export function bachelierPrice(
|
|
326
|
-
type:
|
|
326
|
+
type: "call" | "put",
|
|
327
327
|
S: number,
|
|
328
328
|
K: number,
|
|
329
329
|
T: number,
|
|
@@ -331,16 +331,18 @@ export function bachelierPrice(
|
|
|
331
331
|
q: number,
|
|
332
332
|
sigma_n: number,
|
|
333
333
|
): number {
|
|
334
|
-
if (T <= 0) return type ===
|
|
334
|
+
if (T <= 0) return type === "call" ? Math.max(S - K, 0) : Math.max(K - S, 0);
|
|
335
335
|
if (sigma_n <= 0) {
|
|
336
336
|
const forward = S * Math.exp((r - q) * T);
|
|
337
|
-
return
|
|
337
|
+
return (
|
|
338
|
+
Math.exp(-r * T) * (type === "call" ? Math.max(forward - K, 0) : Math.max(K - forward, 0))
|
|
339
|
+
);
|
|
338
340
|
}
|
|
339
341
|
const forward = S * Math.exp((r - q) * T);
|
|
340
342
|
const sqrtT = Math.sqrt(T);
|
|
341
343
|
const d = (forward - K) / (sigma_n * sqrtT);
|
|
342
344
|
const discount = Math.exp(-r * T);
|
|
343
|
-
if (type ===
|
|
345
|
+
if (type === "call") {
|
|
344
346
|
return discount * ((forward - K) * cdf(d) + sigma_n * sqrtT * pdf(d));
|
|
345
347
|
} else {
|
|
346
348
|
return discount * ((K - forward) * cdf(-d) + sigma_n * sqrtT * pdf(d));
|
|
@@ -353,7 +355,7 @@ export function bachelierPrice(
|
|
|
353
355
|
* Put: -e^(-rT) * N(-d)
|
|
354
356
|
*/
|
|
355
357
|
export function bachelierDelta(
|
|
356
|
-
type:
|
|
358
|
+
type: "call" | "put",
|
|
357
359
|
S: number,
|
|
358
360
|
K: number,
|
|
359
361
|
T: number,
|
|
@@ -362,13 +364,13 @@ export function bachelierDelta(
|
|
|
362
364
|
sigma_n: number,
|
|
363
365
|
): number {
|
|
364
366
|
if (T <= 0 || sigma_n <= 0) {
|
|
365
|
-
if (type ===
|
|
367
|
+
if (type === "call") return S > K ? 1 : 0;
|
|
366
368
|
return S < K ? -1 : 0;
|
|
367
369
|
}
|
|
368
370
|
const forward = S * Math.exp((r - q) * T);
|
|
369
371
|
const d = (forward - K) / (sigma_n * Math.sqrt(T));
|
|
370
372
|
const discount = Math.exp(-r * T);
|
|
371
|
-
return type ===
|
|
373
|
+
return type === "call" ? discount * cdf(d) : -discount * cdf(-d);
|
|
372
374
|
}
|
|
373
375
|
|
|
374
376
|
/**
|
|
@@ -387,7 +389,7 @@ export function bachelierGamma(
|
|
|
387
389
|
const forward = S * Math.exp((r - q) * T);
|
|
388
390
|
const sqrtT = Math.sqrt(T);
|
|
389
391
|
const d = (forward - K) / (sigma_n * sqrtT);
|
|
390
|
-
return Math.exp(-r * T) * pdf(d) / (sigma_n * sqrtT);
|
|
392
|
+
return (Math.exp(-r * T) * pdf(d)) / (sigma_n * sqrtT);
|
|
391
393
|
}
|
|
392
394
|
|
|
393
395
|
/**
|
|
@@ -396,7 +398,7 @@ export function bachelierGamma(
|
|
|
396
398
|
* Formula: -(e^(-rT) * sigma_n * n(d) / (2 * sqrt(T)) - r * price) / 365
|
|
397
399
|
*/
|
|
398
400
|
export function bachelierTheta(
|
|
399
|
-
type:
|
|
401
|
+
type: "call" | "put",
|
|
400
402
|
S: number,
|
|
401
403
|
K: number,
|
|
402
404
|
T: number,
|
|
@@ -413,7 +415,7 @@ export function bachelierTheta(
|
|
|
413
415
|
// dV/dT = -e^(-rT) * sigma_n * n(d) / (2*sqrt(T)) + r * price (positive = more time = more value)
|
|
414
416
|
// theta = -dV/dT per year, then / 365 for per-calendar-day
|
|
415
417
|
// annualTheta is negative for long options (time decay)
|
|
416
|
-
const annualTheta = -discount * sigma_n * pdf(d) / (2 * sqrtT) + r * price;
|
|
418
|
+
const annualTheta = (-discount * sigma_n * pdf(d)) / (2 * sqrtT) + r * price;
|
|
417
419
|
return annualTheta / 365;
|
|
418
420
|
}
|
|
419
421
|
|
|
@@ -432,7 +434,7 @@ export function bachelierVega(
|
|
|
432
434
|
if (T <= 0 || sigma_n <= 0) return 0;
|
|
433
435
|
const forward = S * Math.exp((r - q) * T);
|
|
434
436
|
const d = (forward - K) / (sigma_n * Math.sqrt(T));
|
|
435
|
-
return Math.exp(-r * T) * Math.sqrt(T) * pdf(d) / 100;
|
|
437
|
+
return (Math.exp(-r * T) * Math.sqrt(T) * pdf(d)) / 100;
|
|
436
438
|
}
|
|
437
439
|
|
|
438
440
|
/**
|
|
@@ -450,7 +452,7 @@ export function bachelierVega(
|
|
|
450
452
|
* @returns Normal implied volatility (dollar vol, e.g., ~800 for SPX) or null
|
|
451
453
|
*/
|
|
452
454
|
export function solveNormalIV(
|
|
453
|
-
type:
|
|
455
|
+
type: "call" | "put",
|
|
454
456
|
marketPrice: number,
|
|
455
457
|
S: number,
|
|
456
458
|
K: number,
|
|
@@ -527,18 +529,26 @@ export function computeLegGreeks(
|
|
|
527
529
|
underlyingPrice: number,
|
|
528
530
|
strike: number,
|
|
529
531
|
dte: number,
|
|
530
|
-
type:
|
|
532
|
+
type: "C" | "P",
|
|
531
533
|
riskFreeRate: number,
|
|
532
534
|
dividendYield: number,
|
|
533
535
|
): GreeksResult {
|
|
534
536
|
const T = dte / 365;
|
|
535
|
-
const bsType = type ===
|
|
537
|
+
const bsType = type === "C" ? "call" : "put";
|
|
536
538
|
const nullResult: GreeksResult = { delta: null, gamma: null, theta: null, vega: null, iv: null };
|
|
537
539
|
|
|
538
540
|
if (dte < BACHELIER_DTE_THRESHOLD) {
|
|
539
541
|
// Bachelier normal model for short-dated options (dte < 0.1 days / ~2.4 hours).
|
|
540
542
|
// iv field stores normal (dollar) volatility, not log-normal vol.
|
|
541
|
-
const iv = solveNormalIV(
|
|
543
|
+
const iv = solveNormalIV(
|
|
544
|
+
bsType,
|
|
545
|
+
optionPrice,
|
|
546
|
+
underlyingPrice,
|
|
547
|
+
strike,
|
|
548
|
+
T,
|
|
549
|
+
riskFreeRate,
|
|
550
|
+
dividendYield,
|
|
551
|
+
);
|
|
542
552
|
if (iv === null) return nullResult;
|
|
543
553
|
return {
|
|
544
554
|
delta: bachelierDelta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),
|
|
@@ -546,7 +556,7 @@ export function computeLegGreeks(
|
|
|
546
556
|
theta: bachelierTheta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),
|
|
547
557
|
vega: bachelierVega(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),
|
|
548
558
|
iv,
|
|
549
|
-
model:
|
|
559
|
+
model: "bachelier",
|
|
550
560
|
};
|
|
551
561
|
}
|
|
552
562
|
|
|
@@ -560,6 +570,6 @@ export function computeLegGreeks(
|
|
|
560
570
|
theta: bsTheta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),
|
|
561
571
|
vega: bsVega(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),
|
|
562
572
|
iv,
|
|
563
|
-
model:
|
|
573
|
+
model: "bs",
|
|
564
574
|
};
|
|
565
575
|
}
|
|
@@ -23,7 +23,13 @@ import { getSyncMetadataJson } from "../db/json-adapters.ts";
|
|
|
23
23
|
import { getBlocksDir } from "../sync/index.ts";
|
|
24
24
|
|
|
25
25
|
// Re-export CSV discovery types and functions from shared module
|
|
26
|
-
export {
|
|
26
|
+
export {
|
|
27
|
+
type CsvMappings,
|
|
28
|
+
type CsvType,
|
|
29
|
+
detectCsvType,
|
|
30
|
+
discoverCsvFiles,
|
|
31
|
+
logCsvDiscoveryWarning,
|
|
32
|
+
} from "./csv-discovery.ts";
|
|
27
33
|
import { type CsvType, discoverCsvFiles } from "./csv-discovery.ts";
|
|
28
34
|
|
|
29
35
|
function resolveBlocksBaseDir(baseDir: string): string {
|
|
@@ -81,17 +87,14 @@ function parseDatePreservingCalendarDay(dateStr: string): Date {
|
|
|
81
87
|
/**
|
|
82
88
|
* Parse numeric value from CSV string
|
|
83
89
|
*/
|
|
84
|
-
function parseNumber(
|
|
85
|
-
value: string | undefined,
|
|
86
|
-
defaultValue?: number
|
|
87
|
-
): number {
|
|
90
|
+
function parseNumber(value: string | undefined, defaultValue?: number): number {
|
|
88
91
|
if (!value || value.trim() === "" || value.toLowerCase() === "nan") {
|
|
89
92
|
if (defaultValue !== undefined) return defaultValue;
|
|
90
93
|
return 0;
|
|
91
94
|
}
|
|
92
95
|
const cleaned = value.replace(/[$,%]/g, "").trim();
|
|
93
96
|
const parsed = parseFloat(cleaned);
|
|
94
|
-
return isNaN(parsed) ? defaultValue ?? 0 : parsed;
|
|
97
|
+
return isNaN(parsed) ? (defaultValue ?? 0) : parsed;
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
const KNOWN_TRADE_COLUMNS = new Set([
|
|
@@ -129,7 +132,10 @@ const KNOWN_TRADE_COLUMNS = new Set([
|
|
|
129
132
|
*/
|
|
130
133
|
function parseCSV(content: string): Record<string, string>[] {
|
|
131
134
|
// Strip UTF-8 BOM if present (common in Windows/Excel CSV exports)
|
|
132
|
-
const lines = content
|
|
135
|
+
const lines = content
|
|
136
|
+
.replace(/^\uFEFF/, "")
|
|
137
|
+
.trim()
|
|
138
|
+
.split("\n");
|
|
133
139
|
if (lines.length < 2) return [];
|
|
134
140
|
|
|
135
141
|
const headers = parseCSVLine(lines[0]);
|
|
@@ -205,14 +211,10 @@ function convertToTrade(raw: Record<string, string>, blockId?: string): Trade |
|
|
|
205
211
|
legs,
|
|
206
212
|
premium: parseNumber(raw["Premium"]),
|
|
207
213
|
premiumPrecision,
|
|
208
|
-
closingPrice: raw["Closing Price"]
|
|
209
|
-
? parseNumber(raw["Closing Price"])
|
|
210
|
-
: undefined,
|
|
214
|
+
closingPrice: raw["Closing Price"] ? parseNumber(raw["Closing Price"]) : undefined,
|
|
211
215
|
dateClosed,
|
|
212
216
|
timeClosed: raw["Time Closed"] || undefined,
|
|
213
|
-
avgClosingCost: raw["Avg. Closing Cost"]
|
|
214
|
-
? parseNumber(raw["Avg. Closing Cost"])
|
|
215
|
-
: undefined,
|
|
217
|
+
avgClosingCost: raw["Avg. Closing Cost"] ? parseNumber(raw["Avg. Closing Cost"]) : undefined,
|
|
216
218
|
reasonForClose: raw["Reason For Close"] || undefined,
|
|
217
219
|
pl: parseNumber(raw["P/L"]),
|
|
218
220
|
numContracts: Math.round(parseNumber(raw["No. of Contracts"], 1)),
|
|
@@ -221,27 +223,21 @@ function convertToTrade(raw: Record<string, string>, blockId?: string): Trade |
|
|
|
221
223
|
strategy,
|
|
222
224
|
openingCommissionsFees: parseNumber(
|
|
223
225
|
raw["Opening Commissions + Fees"] || raw["Opening comms & fees"],
|
|
224
|
-
0
|
|
226
|
+
0,
|
|
225
227
|
),
|
|
226
228
|
closingCommissionsFees: parseNumber(
|
|
227
229
|
raw["Closing Commissions + Fees"] || raw["Closing comms & fees"],
|
|
228
|
-
0
|
|
230
|
+
0,
|
|
229
231
|
),
|
|
230
232
|
openingShortLongRatio: parseNumber(raw["Opening Short/Long Ratio"], 0),
|
|
231
233
|
closingShortLongRatio: raw["Closing Short/Long Ratio"]
|
|
232
234
|
? parseNumber(raw["Closing Short/Long Ratio"])
|
|
233
235
|
: undefined,
|
|
234
|
-
openingVix: raw["Opening VIX"]
|
|
235
|
-
|
|
236
|
-
: undefined,
|
|
237
|
-
closingVix: raw["Closing VIX"]
|
|
238
|
-
? parseNumber(raw["Closing VIX"])
|
|
239
|
-
: undefined,
|
|
236
|
+
openingVix: raw["Opening VIX"] ? parseNumber(raw["Opening VIX"]) : undefined,
|
|
237
|
+
closingVix: raw["Closing VIX"] ? parseNumber(raw["Closing VIX"]) : undefined,
|
|
240
238
|
gap: raw["Gap"] ? parseNumber(raw["Gap"]) : undefined,
|
|
241
239
|
movement: raw["Movement"] ? parseNumber(raw["Movement"]) : undefined,
|
|
242
|
-
maxProfit: raw["Max Profit"]
|
|
243
|
-
? parseNumber(raw["Max Profit"])
|
|
244
|
-
: undefined,
|
|
240
|
+
maxProfit: raw["Max Profit"] ? parseNumber(raw["Max Profit"]) : undefined,
|
|
245
241
|
maxLoss: raw["Max Loss"] ? parseNumber(raw["Max Loss"]) : undefined,
|
|
246
242
|
};
|
|
247
243
|
|
|
@@ -268,7 +264,7 @@ function convertToTrade(raw: Record<string, string>, blockId?: string): Trade |
|
|
|
268
264
|
*/
|
|
269
265
|
function convertToDailyLogEntry(
|
|
270
266
|
raw: Record<string, string>,
|
|
271
|
-
blockId?: string
|
|
267
|
+
blockId?: string,
|
|
272
268
|
): DailyLogEntry | null {
|
|
273
269
|
try {
|
|
274
270
|
const date = parseDatePreservingCalendarDay(raw["Date"]);
|
|
@@ -298,7 +294,7 @@ function convertToDailyLogEntry(
|
|
|
298
294
|
async function loadTrades(
|
|
299
295
|
blockPath: string,
|
|
300
296
|
filename: string = "tradelog.csv",
|
|
301
|
-
blockId?: string
|
|
297
|
+
blockId?: string,
|
|
302
298
|
): Promise<Trade[]> {
|
|
303
299
|
const tradelogPath = path.join(blockPath, filename);
|
|
304
300
|
const content = await fs.readFile(tradelogPath, "utf-8");
|
|
@@ -314,8 +310,7 @@ async function loadTrades(
|
|
|
314
310
|
|
|
315
311
|
// Sort by date and time
|
|
316
312
|
trades.sort((a, b) => {
|
|
317
|
-
const dateCompare =
|
|
318
|
-
new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime();
|
|
313
|
+
const dateCompare = new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime();
|
|
319
314
|
if (dateCompare !== 0) return dateCompare;
|
|
320
315
|
return a.timeOpened.localeCompare(b.timeOpened);
|
|
321
316
|
});
|
|
@@ -332,7 +327,7 @@ async function loadTrades(
|
|
|
332
327
|
async function loadDailyLogs(
|
|
333
328
|
blockPath: string,
|
|
334
329
|
blockId: string,
|
|
335
|
-
filename: string = "dailylog.csv"
|
|
330
|
+
filename: string = "dailylog.csv",
|
|
336
331
|
): Promise<DailyLogEntry[] | undefined> {
|
|
337
332
|
const dailylogPath = path.join(blockPath, filename);
|
|
338
333
|
|
|
@@ -350,9 +345,7 @@ async function loadDailyLogs(
|
|
|
350
345
|
}
|
|
351
346
|
|
|
352
347
|
// Sort by date
|
|
353
|
-
entries.sort(
|
|
354
|
-
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
|
|
355
|
-
);
|
|
348
|
+
entries.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
|
356
349
|
|
|
357
350
|
return entries.length > 0 ? entries : undefined;
|
|
358
351
|
} catch {
|
|
@@ -365,10 +358,7 @@ async function loadDailyLogs(
|
|
|
365
358
|
* Load a complete block (trades + optional daily logs).
|
|
366
359
|
* Uses csv-discovery header sniffing for file resolution.
|
|
367
360
|
*/
|
|
368
|
-
export async function loadBlock(
|
|
369
|
-
baseDir: string,
|
|
370
|
-
blockId: string
|
|
371
|
-
): Promise<LoadedBlock> {
|
|
361
|
+
export async function loadBlock(baseDir: string, blockId: string): Promise<LoadedBlock> {
|
|
372
362
|
const blocksDir = resolveBlocksBaseDir(baseDir);
|
|
373
363
|
const blockPath = path.join(blocksDir, blockId);
|
|
374
364
|
|
|
@@ -469,14 +459,17 @@ export async function listBlocks(baseDir: string): Promise<BlockInfo[]> {
|
|
|
469
459
|
}
|
|
470
460
|
|
|
471
461
|
// Build a map of block_id -> trade stats
|
|
472
|
-
const tradeStats = new Map<
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
462
|
+
const tradeStats = new Map<
|
|
463
|
+
string,
|
|
464
|
+
{
|
|
465
|
+
tradeCount: number;
|
|
466
|
+
strategies: string[];
|
|
467
|
+
minDate: Date | null;
|
|
468
|
+
maxDate: Date | null;
|
|
469
|
+
totalPl: number;
|
|
470
|
+
netPl: number;
|
|
471
|
+
}
|
|
472
|
+
>();
|
|
480
473
|
|
|
481
474
|
for (const row of tradeStatsReader.getRows()) {
|
|
482
475
|
const blockId = row[0] as string;
|
|
@@ -510,13 +503,16 @@ export async function listBlocks(baseDir: string): Promise<BlockInfo[]> {
|
|
|
510
503
|
GROUP BY r.block_id
|
|
511
504
|
`);
|
|
512
505
|
|
|
513
|
-
const reportingStats = new Map<
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
506
|
+
const reportingStats = new Map<
|
|
507
|
+
string,
|
|
508
|
+
{
|
|
509
|
+
tradeCount: number;
|
|
510
|
+
strategyCount: number;
|
|
511
|
+
totalPL: number;
|
|
512
|
+
minDate: string | null;
|
|
513
|
+
maxDate: string | null;
|
|
514
|
+
}
|
|
515
|
+
>();
|
|
520
516
|
|
|
521
517
|
for (const row of reportingReader.getRows()) {
|
|
522
518
|
const blockId = row[0] as string;
|
|
@@ -530,10 +526,13 @@ export async function listBlocks(baseDir: string): Promise<BlockInfo[]> {
|
|
|
530
526
|
}
|
|
531
527
|
|
|
532
528
|
// Query 3: Sync metadata to determine hasDailyLog/hasReportingLog
|
|
533
|
-
const syncMeta = new Map<
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
529
|
+
const syncMeta = new Map<
|
|
530
|
+
string,
|
|
531
|
+
{
|
|
532
|
+
hasDailyLog: boolean;
|
|
533
|
+
hasReportingLog: boolean;
|
|
534
|
+
}
|
|
535
|
+
>();
|
|
537
536
|
|
|
538
537
|
if (isParquetMode()) {
|
|
539
538
|
// In Parquet mode, read .sync-meta.json files from blocksDir
|
|
@@ -654,27 +653,21 @@ export async function listBlocks(baseDir: string): Promise<BlockInfo[]> {
|
|
|
654
653
|
/**
|
|
655
654
|
* Normalize header names using column aliases
|
|
656
655
|
*/
|
|
657
|
-
function normalizeRecordHeaders(
|
|
658
|
-
raw: Record<string, string>
|
|
659
|
-
): Record<string, string> {
|
|
656
|
+
function normalizeRecordHeaders(raw: Record<string, string>): Record<string, string> {
|
|
660
657
|
const normalized: Record<string, string> = { ...raw };
|
|
661
|
-
Object.entries(REPORTING_TRADE_COLUMN_ALIASES).forEach(
|
|
662
|
-
([alias
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
delete normalized[alias];
|
|
666
|
-
}
|
|
658
|
+
Object.entries(REPORTING_TRADE_COLUMN_ALIASES).forEach(([alias, canonical]) => {
|
|
659
|
+
if (normalized[alias] !== undefined) {
|
|
660
|
+
normalized[canonical] = normalized[alias];
|
|
661
|
+
delete normalized[alias];
|
|
667
662
|
}
|
|
668
|
-
);
|
|
663
|
+
});
|
|
669
664
|
return normalized;
|
|
670
665
|
}
|
|
671
666
|
|
|
672
667
|
/**
|
|
673
668
|
* Convert raw CSV record to ReportingTrade object
|
|
674
669
|
*/
|
|
675
|
-
export function convertToReportingTrade(
|
|
676
|
-
raw: Record<string, string>
|
|
677
|
-
): ReportingTrade | null {
|
|
670
|
+
export function convertToReportingTrade(raw: Record<string, string>): ReportingTrade | null {
|
|
678
671
|
// Check if this is a TAT format row
|
|
679
672
|
const keys = Object.keys(raw);
|
|
680
673
|
if (isTatFormat(keys)) {
|
|
@@ -725,7 +718,7 @@ export function convertToReportingTrade(
|
|
|
725
718
|
*/
|
|
726
719
|
export async function loadReportingLog(
|
|
727
720
|
baseDir: string,
|
|
728
|
-
blockId: string
|
|
721
|
+
blockId: string,
|
|
729
722
|
): Promise<ReportingTrade[]> {
|
|
730
723
|
const blocksDir = resolveBlocksBaseDir(baseDir);
|
|
731
724
|
const blockPath = path.join(blocksDir, blockId);
|
|
@@ -754,10 +747,7 @@ export async function loadReportingLog(
|
|
|
754
747
|
}
|
|
755
748
|
|
|
756
749
|
// Sort by date
|
|
757
|
-
trades.sort(
|
|
758
|
-
(a, b) =>
|
|
759
|
-
new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime()
|
|
760
|
-
);
|
|
750
|
+
trades.sort((a, b) => new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime());
|
|
761
751
|
|
|
762
752
|
return trades;
|
|
763
753
|
}
|
|
@@ -808,7 +798,7 @@ function toKebabCase(str: string): string {
|
|
|
808
798
|
*/
|
|
809
799
|
function validateCsvColumns(
|
|
810
800
|
records: Record<string, string>[],
|
|
811
|
-
csvType: "tradelog" | "dailylog" | "reportinglog"
|
|
801
|
+
csvType: "tradelog" | "dailylog" | "reportinglog",
|
|
812
802
|
): { valid: boolean; error?: string } {
|
|
813
803
|
if (records.length === 0) {
|
|
814
804
|
return { valid: false, error: "CSV file is empty or has no data rows" };
|
|
@@ -849,9 +839,7 @@ function validateCsvColumns(
|
|
|
849
839
|
// Required columns for OO reporting log (with aliases)
|
|
850
840
|
const dateOpenedAliases = ["Date Opened", "date_opened"];
|
|
851
841
|
const plAliases = ["P/L", "pl"];
|
|
852
|
-
const hasDateOpened = dateOpenedAliases.some((col) =>
|
|
853
|
-
headers.includes(col)
|
|
854
|
-
);
|
|
842
|
+
const hasDateOpened = dateOpenedAliases.some((col) => headers.includes(col));
|
|
855
843
|
const hasPl = plAliases.some((col) => headers.includes(col));
|
|
856
844
|
const missing: string[] = [];
|
|
857
845
|
if (!hasDateOpened) missing.push("Date Opened");
|
|
@@ -881,7 +869,7 @@ function validateCsvColumns(
|
|
|
881
869
|
*/
|
|
882
870
|
export async function importCsv(
|
|
883
871
|
baseDir: string,
|
|
884
|
-
options: ImportCsvOptions
|
|
872
|
+
options: ImportCsvOptions,
|
|
885
873
|
): Promise<ImportCsvResult> {
|
|
886
874
|
const { csvPath, blockName } = options;
|
|
887
875
|
let { csvType = "tradelog" } = options;
|
|
@@ -918,9 +906,7 @@ export async function importCsv(
|
|
|
918
906
|
const blockId = toKebabCase(name);
|
|
919
907
|
|
|
920
908
|
if (!blockId) {
|
|
921
|
-
throw new Error(
|
|
922
|
-
"Could not derive a valid block ID from the filename or provided name"
|
|
923
|
-
);
|
|
909
|
+
throw new Error("Could not derive a valid block ID from the filename or provided name");
|
|
924
910
|
}
|
|
925
911
|
|
|
926
912
|
// Check if block already exists
|
|
@@ -928,7 +914,7 @@ export async function importCsv(
|
|
|
928
914
|
try {
|
|
929
915
|
await fs.access(blockPath);
|
|
930
916
|
throw new Error(
|
|
931
|
-
`Block "${blockId}" already exists. Use a different blockName or delete the existing block first
|
|
917
|
+
`Block "${blockId}" already exists. Use a different blockName or delete the existing block first.`,
|
|
932
918
|
);
|
|
933
919
|
} catch (error) {
|
|
934
920
|
// Directory doesn't exist - good, we can create it
|
|
@@ -117,8 +117,7 @@ export async function calibrateProviderFetch(
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const avgCloseDelta =
|
|
121
|
-
deltas.length > 0 ? deltas.reduce((a, b) => a + b, 0) / deltas.length : 0;
|
|
120
|
+
const avgCloseDelta = deltas.length > 0 ? deltas.reduce((a, b) => a + b, 0) / deltas.length : 0;
|
|
122
121
|
const maxCloseDelta = deltas.length > 0 ? Math.max(...deltas) : 0;
|
|
123
122
|
return { avgCloseDelta, maxCloseDelta, dateResults };
|
|
124
123
|
}
|
|
@@ -83,7 +83,7 @@ export function deduplicateContracts(contracts: ContractRow[]): ContractRow[] {
|
|
|
83
83
|
const existing = map.get(key);
|
|
84
84
|
if (!existing) {
|
|
85
85
|
map.set(key, c);
|
|
86
|
-
} else if (c.ticker.startsWith(
|
|
86
|
+
} else if (c.ticker.startsWith("SPXW") && !existing.ticker.startsWith("SPXW")) {
|
|
87
87
|
map.set(key, c);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -132,7 +132,7 @@ export async function loadChain(
|
|
|
132
132
|
): Promise<ChainLoadResult> {
|
|
133
133
|
throw new Error(
|
|
134
134
|
"chain-loader.loadChain has been removed. " +
|
|
135
|
-
|
|
135
|
+
"Use stores.chain.readChain(underlying, date) instead — empty array is the new skip signal.",
|
|
136
136
|
);
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -150,6 +150,6 @@ export async function loadChainsBulk(
|
|
|
150
150
|
): Promise<Map<string, ChainLoadResult>> {
|
|
151
151
|
throw new Error(
|
|
152
152
|
"chain-loader.loadChainsBulk has been removed. " +
|
|
153
|
-
|
|
153
|
+
"Use a per-date loop with stores.chain.readChain(underlying, date) instead.",
|
|
154
154
|
);
|
|
155
155
|
}
|