tradeblocks-mcp 3.0.1 → 3.0.2

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.
@@ -1,9 +1,10 @@
1
+ #!/usr/bin/env node
1
2
  import {
2
3
  getSofrRateByKey
3
- } from "./chunk-QTTR7AAW.js";
4
+ } from "./chunk-72GKJE2U.js";
4
5
  import {
5
6
  loadMddsProtoRoot
6
- } from "./chunk-FGZH632F.js";
7
+ } from "./chunk-FBNDMCT5.js";
7
8
 
8
9
  // src/utils/providers/massive.ts
9
10
  import { z } from "zod";
@@ -751,18 +752,18 @@ var MassiveProvider = class {
751
752
  const s3Path = `s3massive:flatfiles/${assetPath}/${year}/${month}/${date}.csv.gz`;
752
753
  const tmpDir = assetClass === "index" ? "/tmp/massive-flat-index" : "/tmp/massive-flat";
753
754
  const localPath = `${tmpDir}/${date}.csv.gz`;
754
- const { existsSync, mkdirSync } = await import("fs");
755
+ const { existsSync: existsSync2, mkdirSync } = await import("fs");
755
756
  const { execFile } = await import("child_process");
756
757
  const { promisify } = await import("util");
757
758
  const execFileAsync = promisify(execFile);
758
759
  mkdirSync(tmpDir, { recursive: true });
759
- if (existsSync(localPath)) return localPath;
760
+ if (existsSync2(localPath)) return localPath;
760
761
  try {
761
762
  await execFileAsync("rclone", ["copy", s3Path, `${tmpDir}/`], { timeout: 12e4 });
762
763
  } catch {
763
764
  return null;
764
765
  }
765
- return existsSync(localPath) ? localPath : null;
766
+ return existsSync2(localPath) ? localPath : null;
766
767
  }
767
768
  async downloadBulkData(options) {
768
769
  const { date, dataset, assetClass, tickers, outputPath } = options;
@@ -1385,7 +1386,7 @@ var ThetaMddsClient = class {
1385
1386
  fetchImpl;
1386
1387
  loadGrpcPackage;
1387
1388
  constructor(env = process.env, fetchImpl = fetch, loadGrpcPackage = async () => {
1388
- const { loadMddsGrpcPackage } = await import("./proto-3HZTCWK4.js");
1389
+ const { loadMddsGrpcPackage } = await import("./proto-DWRZJO4E.js");
1389
1390
  return loadMddsGrpcPackage();
1390
1391
  }) {
1391
1392
  this.env = env;
@@ -1527,22 +1528,23 @@ var PRICE_TYPE_FACTORS = [
1527
1528
  1e9
1528
1529
  ];
1529
1530
  var ET_MINUTE_PATTERN = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/;
1531
+ var ET_MINUTE_FORMAT = new Intl.DateTimeFormat("en-CA", {
1532
+ timeZone: "America/New_York",
1533
+ year: "numeric",
1534
+ month: "2-digit",
1535
+ day: "2-digit",
1536
+ hour: "2-digit",
1537
+ minute: "2-digit",
1538
+ hour12: false,
1539
+ hourCycle: "h23"
1540
+ });
1530
1541
  function thetaPriceToNumber(price) {
1531
1542
  return price.type === 0 ? Number.NaN : price.value * PRICE_TYPE_FACTORS[price.type];
1532
1543
  }
1533
1544
  function thetaTimestampToEtMinute(value) {
1534
1545
  if (typeof value === "string" && ET_MINUTE_PATTERN.test(value)) return value;
1535
1546
  const date = value instanceof Date ? value : new Date(value);
1536
- const parts = new Intl.DateTimeFormat("en-CA", {
1537
- timeZone: "America/New_York",
1538
- year: "numeric",
1539
- month: "2-digit",
1540
- day: "2-digit",
1541
- hour: "2-digit",
1542
- minute: "2-digit",
1543
- hour12: false,
1544
- hourCycle: "h23"
1545
- }).formatToParts(date);
1547
+ const parts = ET_MINUTE_FORMAT.formatToParts(date);
1546
1548
  const get = (type) => parts.find((part) => part.type === type)?.value ?? "";
1547
1549
  return `${get("year")}-${get("month")}-${get("day")} ${get("hour")}:${get("minute")}`;
1548
1550
  }
@@ -1668,17 +1670,6 @@ function normalizeThetaFirstOrderGreekRow(row) {
1668
1670
  underlyingPrice: asNumber(row.underlying_price)
1669
1671
  };
1670
1672
  }
1671
- function normalizeThetaImpliedVolatilityRow(row) {
1672
- return {
1673
- ...normalizeThetaQuoteRow(row),
1674
- bidIv: asNumber(row.bid_implied_vol),
1675
- midIv: asNumber(row.implied_vol ?? row.midpoint_implied_vol),
1676
- askIv: asNumber(row.ask_implied_vol),
1677
- ivError: asNumber(row.iv_error),
1678
- underlyingTimestamp: row.underlying_timestamp == null ? null : thetaTimestampToEtMinute(asText(row.underlying_timestamp)),
1679
- underlyingPrice: asNumber(row.underlying_price)
1680
- };
1681
- }
1682
1673
  function normalizeThetaContractListRow(row) {
1683
1674
  return {
1684
1675
  symbol: requiredText(row.symbol, "contract-list row", "symbol").toUpperCase(),
@@ -1898,30 +1889,6 @@ async function indexHistoryEod(client, params) {
1898
1889
  );
1899
1890
  return decodeThetaRows(chunks).map(normalizeThetaIndexEodRow);
1900
1891
  }
1901
- async function optionAtTimeQuote(client, params) {
1902
- const symbol = validateSymbol(params.symbol);
1903
- const expiration = validateHyphenDate(params.expiration, "expiration");
1904
- const strike = validateStrike(params.strike);
1905
- const date = validateHyphenDate(params.date, "date");
1906
- const strikeRange = optionalPositiveInteger2(params.strikeRange, "strike_range");
1907
- const chunks = await client.callStream(
1908
- "GetOptionAtTimeQuote",
1909
- endpointRequest(client.queryInfo(), {
1910
- contractSpec: {
1911
- symbol,
1912
- expiration,
1913
- strike,
1914
- right: thetaRequestRight(params.right)
1915
- },
1916
- startDate: date,
1917
- endDate: date,
1918
- timeOfDay: mddsTime(params.time, "09:45:00.000"),
1919
- expiration,
1920
- ...strikeRange == null ? {} : { strikeRange }
1921
- })
1922
- );
1923
- return decodeThetaRows(chunks).map(normalizeThetaQuoteRow);
1924
- }
1925
1892
  async function optionHistoryGreeksFirstOrder(client, params) {
1926
1893
  const symbol = validateSymbol(params.symbol);
1927
1894
  const expiration = validateHyphenDate(params.expiration, "expiration");
@@ -1975,57 +1942,6 @@ async function optionHistoryGreeksFirstOrderBand(client, params) {
1975
1942
  );
1976
1943
  return decodeThetaRows(chunks).map(normalizeThetaFirstOrderGreekRow);
1977
1944
  }
1978
- async function optionHistoryImpliedVolatilityBand(client, params) {
1979
- const symbol = validateSymbol(params.symbol);
1980
- const expiration = params.expiration === "*" ? "*" : validateHyphenDate(params.expiration, "expiration");
1981
- const date = validateHyphenDate(params.date, "date");
1982
- const strikeRange = optionalPositiveInteger2(params.strikeRange, "strike_range");
1983
- const chunks = await client.callStream(
1984
- "GetOptionHistoryGreeksImpliedVolatility",
1985
- endpointRequest(client.queryInfo(), {
1986
- contractSpec: {
1987
- symbol,
1988
- expiration,
1989
- strike: THETA_WILDCARD_STRIKE,
1990
- right: "both"
1991
- },
1992
- expiration,
1993
- date,
1994
- interval: params.interval ?? "1m",
1995
- startTime: mddsTime(params.startTime, "09:30:00.000"),
1996
- endTime: mddsTime(params.endTime, "16:00:00.000"),
1997
- rateType: params.rateType ?? "sofr",
1998
- ...params.annualDividend == null ? {} : { annualDividend: params.annualDividend },
1999
- version: "latest",
2000
- strikeRange
2001
- })
2002
- );
2003
- return decodeThetaRows(chunks).map(normalizeThetaImpliedVolatilityRow);
2004
- }
2005
- async function optionHistoryQuoteBand(client, params) {
2006
- const symbol = validateSymbol(params.symbol);
2007
- const expiration = params.expiration === "*" ? "*" : validateHyphenDate(params.expiration, "expiration");
2008
- const date = validateHyphenDate(params.date, "date");
2009
- const strikeRange = optionalPositiveInteger2(params.strikeRange, "strike_range");
2010
- const chunks = await client.callStream(
2011
- "GetOptionHistoryQuote",
2012
- endpointRequest(client.queryInfo(), {
2013
- contractSpec: {
2014
- symbol,
2015
- expiration,
2016
- strike: THETA_WILDCARD_STRIKE,
2017
- right: "both"
2018
- },
2019
- expiration,
2020
- date,
2021
- interval: params.interval ?? "1m",
2022
- startTime: mddsTime(params.startTime, "09:30:00.000"),
2023
- endTime: mddsTime(params.endTime, "16:00:00.000"),
2024
- ...strikeRange == null ? {} : { strikeRange }
2025
- })
2026
- );
2027
- return decodeThetaRows(chunks).map(normalizeThetaQuoteRow);
2028
- }
2029
1945
  async function optionHistoryOpenInterest(client, params) {
2030
1946
  const symbol = validateSymbol(params.symbol);
2031
1947
  const expiration = params.expiration === "*" ? "*" : validateHyphenDate(params.expiration, "expiration");
@@ -2079,25 +1995,335 @@ function computeFractionalDte(date, time, expiration) {
2079
1995
  return Math.max(dayDiff + remainingMinutes / (24 * 60), 0);
2080
1996
  }
2081
1997
 
1998
+ // src/utils/iv-solver-pool.ts
1999
+ import { Worker } from "worker_threads";
2000
+ import { availableParallelism } from "os";
2001
+ import { existsSync } from "fs";
2002
+ import { fileURLToPath } from "url";
2003
+
2004
+ // src/utils/iv-solver-worker.ts
2005
+ import { parentPort } from "worker_threads";
2006
+ function isFiniteNumber(value) {
2007
+ return typeof value === "number" && Number.isFinite(value);
2008
+ }
2009
+ function solveIvBatch(req) {
2010
+ const { id, count } = req;
2011
+ const delta = new Float64Array(count);
2012
+ const gamma = new Float64Array(count);
2013
+ const theta = new Float64Array(count);
2014
+ const vega = new Float64Array(count);
2015
+ const iv = new Float64Array(count);
2016
+ const ok = new Uint8Array(count);
2017
+ for (let i = 0; i < count; i++) {
2018
+ const result = computeLegGreeks(
2019
+ req.optionPrice[i],
2020
+ req.underlyingPrice[i],
2021
+ req.strike[i],
2022
+ req.dte[i],
2023
+ req.type[i] === 0 ? "C" : "P",
2024
+ req.riskFreeRate[i],
2025
+ req.dividendYield[i]
2026
+ );
2027
+ if (isFiniteNumber(result.delta) && isFiniteNumber(result.gamma) && isFiniteNumber(result.theta) && isFiniteNumber(result.vega) && isFiniteNumber(result.iv)) {
2028
+ delta[i] = result.delta;
2029
+ gamma[i] = result.gamma;
2030
+ theta[i] = result.theta;
2031
+ vega[i] = result.vega;
2032
+ iv[i] = result.iv;
2033
+ ok[i] = 1;
2034
+ } else {
2035
+ ok[i] = 0;
2036
+ }
2037
+ }
2038
+ return { id, count, delta, gamma, theta, vega, iv, ok };
2039
+ }
2040
+ if (parentPort) {
2041
+ const port = parentPort;
2042
+ port.on("message", (req) => {
2043
+ const reply = solveIvBatch(req);
2044
+ port.postMessage(reply, [
2045
+ reply.delta.buffer,
2046
+ reply.gamma.buffer,
2047
+ reply.theta.buffer,
2048
+ reply.vega.buffer,
2049
+ reply.iv.buffer,
2050
+ reply.ok.buffer
2051
+ ]);
2052
+ });
2053
+ }
2054
+
2055
+ // src/utils/iv-solver-pool.ts
2056
+ var DEFAULT_INLINE_THRESHOLD = 2e3;
2057
+ var MIN_SHARD = 1e3;
2058
+ function resolveWorkerUrl() {
2059
+ const jsUrl = new URL("./iv-solver-worker.js", import.meta.url);
2060
+ if (existsSync(fileURLToPath(jsUrl))) return jsUrl;
2061
+ return new URL("./iv-solver-worker.ts", import.meta.url);
2062
+ }
2063
+ function workersDisabledByEnv() {
2064
+ const flag = process.env.TRADEBLOCKS_IV_WORKERS;
2065
+ return flag === "0" || flag === "false" || flag === "off";
2066
+ }
2067
+ function defaultMaxWorkers() {
2068
+ const cores = Math.max(1, availableParallelism());
2069
+ return Math.max(1, cores - 1);
2070
+ }
2071
+ function jobsToColumns(jobs) {
2072
+ const count = jobs.length;
2073
+ const cols = {
2074
+ count,
2075
+ optionPrice: new Float64Array(count),
2076
+ underlyingPrice: new Float64Array(count),
2077
+ strike: new Float64Array(count),
2078
+ dte: new Float64Array(count),
2079
+ riskFreeRate: new Float64Array(count),
2080
+ dividendYield: new Float64Array(count),
2081
+ type: new Uint8Array(count)
2082
+ };
2083
+ for (let i = 0; i < count; i++) {
2084
+ const job = jobs[i];
2085
+ cols.optionPrice[i] = job.optionPrice;
2086
+ cols.underlyingPrice[i] = job.underlyingPrice;
2087
+ cols.strike[i] = job.strike;
2088
+ cols.dte[i] = job.dte;
2089
+ cols.riskFreeRate[i] = job.riskFreeRate;
2090
+ cols.dividendYield[i] = job.dividendYield;
2091
+ cols.type[i] = job.type === "C" ? 0 : 1;
2092
+ }
2093
+ return cols;
2094
+ }
2095
+ function shardRequest(cols, id, lo, hi) {
2096
+ return {
2097
+ id,
2098
+ count: hi - lo,
2099
+ optionPrice: cols.optionPrice.slice(lo, hi),
2100
+ underlyingPrice: cols.underlyingPrice.slice(lo, hi),
2101
+ strike: cols.strike.slice(lo, hi),
2102
+ dte: cols.dte.slice(lo, hi),
2103
+ riskFreeRate: cols.riskFreeRate.slice(lo, hi),
2104
+ dividendYield: cols.dividendYield.slice(lo, hi),
2105
+ type: cols.type.slice(lo, hi)
2106
+ };
2107
+ }
2108
+ function wholeRequest(cols, id) {
2109
+ return {
2110
+ id,
2111
+ count: cols.count,
2112
+ optionPrice: cols.optionPrice,
2113
+ underlyingPrice: cols.underlyingPrice,
2114
+ strike: cols.strike,
2115
+ dte: cols.dte,
2116
+ riskFreeRate: cols.riskFreeRate,
2117
+ dividendYield: cols.dividendYield,
2118
+ type: cols.type
2119
+ };
2120
+ }
2121
+ function columnsResultFrom(reply) {
2122
+ return {
2123
+ count: reply.count,
2124
+ delta: reply.delta,
2125
+ gamma: reply.gamma,
2126
+ theta: reply.theta,
2127
+ vega: reply.vega,
2128
+ iv: reply.iv,
2129
+ ok: reply.ok
2130
+ };
2131
+ }
2132
+ function resultsFromColumns(cols) {
2133
+ const out = new Array(cols.count);
2134
+ for (let i = 0; i < cols.count; i++) {
2135
+ out[i] = {
2136
+ delta: cols.delta[i],
2137
+ gamma: cols.gamma[i],
2138
+ theta: cols.theta[i],
2139
+ vega: cols.vega[i],
2140
+ iv: cols.iv[i],
2141
+ ok: cols.ok[i] === 1
2142
+ };
2143
+ }
2144
+ return out;
2145
+ }
2146
+ var IvSolverPool = class {
2147
+ maxWorkers;
2148
+ inlineThreshold;
2149
+ workerUrl;
2150
+ enabled;
2151
+ pool = [];
2152
+ nextRequestId = 1;
2153
+ // Serializes parallel solves against the shared worker set. The `busy`/slot
2154
+ // accounting in solveColumnsParallel is only correct when a single solve
2155
+ // owns the whole pool; without this gate, two concurrent callers could route
2156
+ // shards onto the same worker and corrupt the busy bookkeeping. Inline solves
2157
+ // don't touch workers and bypass the gate.
2158
+ parallelChain = Promise.resolve();
2159
+ constructor(options = {}) {
2160
+ this.maxWorkers = Math.max(1, options.maxWorkers ?? defaultMaxWorkers());
2161
+ this.inlineThreshold = options.inlineThreshold ?? DEFAULT_INLINE_THRESHOLD;
2162
+ this.workerUrl = resolveWorkerUrl();
2163
+ this.enabled = !workersDisabledByEnv() && this.maxWorkers > 1;
2164
+ }
2165
+ /**
2166
+ * Solve a batch of jobs, returning one result per job in input order. Thin
2167
+ * wrapper over `solveColumns` — convenient for callers (and tests) that hold
2168
+ * an `IvSolveJob[]`. The hot ingest path uses `solveColumns` directly to
2169
+ * avoid materializing the object array.
2170
+ */
2171
+ async solve(jobs) {
2172
+ if (jobs.length === 0) return [];
2173
+ const result = await this.solveColumns(jobsToColumns(jobs));
2174
+ return resultsFromColumns(result);
2175
+ }
2176
+ /**
2177
+ * Solve a pre-built column batch. Uses the worker pool when the batch is
2178
+ * large enough and workers are enabled; otherwise solves inline via the same
2179
+ * `solveIvBatch` the workers run (so inline and parallel are interchangeable).
2180
+ * The reply columns are positionally aligned with the input.
2181
+ */
2182
+ async solveColumns(cols) {
2183
+ if (cols.count === 0) {
2184
+ return {
2185
+ count: 0,
2186
+ delta: new Float64Array(0),
2187
+ gamma: new Float64Array(0),
2188
+ theta: new Float64Array(0),
2189
+ vega: new Float64Array(0),
2190
+ iv: new Float64Array(0),
2191
+ ok: new Uint8Array(0)
2192
+ };
2193
+ }
2194
+ const shardCount = this.enabled ? Math.min(this.maxWorkers, Math.ceil(cols.count / MIN_SHARD)) : 1;
2195
+ if (shardCount <= 1 || cols.count < this.inlineThreshold) {
2196
+ return columnsResultFrom(solveIvBatch(wholeRequest(cols, 0)));
2197
+ }
2198
+ const run = this.parallelChain.catch(() => void 0).then(() => this.solveColumnsParallel(cols, shardCount));
2199
+ this.parallelChain = run;
2200
+ return run;
2201
+ }
2202
+ async solveColumnsParallel(cols, shardCount) {
2203
+ this.ensureWorkers(shardCount);
2204
+ const out = {
2205
+ count: cols.count,
2206
+ delta: new Float64Array(cols.count),
2207
+ gamma: new Float64Array(cols.count),
2208
+ theta: new Float64Array(cols.count),
2209
+ vega: new Float64Array(cols.count),
2210
+ iv: new Float64Array(cols.count),
2211
+ ok: new Uint8Array(cols.count)
2212
+ };
2213
+ const base = Math.floor(cols.count / shardCount);
2214
+ const remainder = cols.count % shardCount;
2215
+ const shardPromises = [];
2216
+ let cursor = 0;
2217
+ for (let s = 0; s < shardCount; s++) {
2218
+ const size = base + (s < remainder ? 1 : 0);
2219
+ const lo = cursor;
2220
+ const hi = cursor + size;
2221
+ cursor = hi;
2222
+ if (size === 0) continue;
2223
+ const request = shardRequest(cols, this.nextRequestId++, lo, hi);
2224
+ shardPromises.push(
2225
+ this.runOnWorker(request).then((reply) => {
2226
+ out.delta.set(reply.delta, lo);
2227
+ out.gamma.set(reply.gamma, lo);
2228
+ out.theta.set(reply.theta, lo);
2229
+ out.vega.set(reply.vega, lo);
2230
+ out.iv.set(reply.iv, lo);
2231
+ out.ok.set(reply.ok, lo);
2232
+ })
2233
+ );
2234
+ }
2235
+ await Promise.all(shardPromises);
2236
+ return out;
2237
+ }
2238
+ ensureWorkers(target) {
2239
+ while (this.pool.length < target) {
2240
+ const worker = new Worker(this.workerUrl);
2241
+ worker.setMaxListeners(0);
2242
+ worker.on("error", (err) => {
2243
+ throw err;
2244
+ });
2245
+ this.pool.push({ worker, busy: false });
2246
+ }
2247
+ }
2248
+ runOnWorker(request) {
2249
+ return new Promise((resolve, reject) => {
2250
+ const slot = this.acquire();
2251
+ const onMessage = (reply) => {
2252
+ if (reply.id !== request.id) return;
2253
+ cleanup();
2254
+ slot.busy = false;
2255
+ resolve(reply);
2256
+ };
2257
+ const onError = (err) => {
2258
+ cleanup();
2259
+ slot.busy = false;
2260
+ reject(err);
2261
+ };
2262
+ const cleanup = () => {
2263
+ slot.worker.off("message", onMessage);
2264
+ slot.worker.off("error", onError);
2265
+ };
2266
+ slot.worker.on("message", onMessage);
2267
+ slot.worker.once("error", onError);
2268
+ slot.worker.postMessage(request, [
2269
+ request.optionPrice.buffer,
2270
+ request.underlyingPrice.buffer,
2271
+ request.strike.buffer,
2272
+ request.dte.buffer,
2273
+ request.riskFreeRate.buffer,
2274
+ request.dividendYield.buffer,
2275
+ request.type.buffer
2276
+ ]);
2277
+ });
2278
+ }
2279
+ acquire() {
2280
+ const free = this.pool.find((w) => !w.busy);
2281
+ const slot = free ?? this.pool[0];
2282
+ slot.busy = true;
2283
+ return slot;
2284
+ }
2285
+ /** Terminate all workers. Idempotent. */
2286
+ async destroy() {
2287
+ const workers = this.pool;
2288
+ this.pool = [];
2289
+ await Promise.all(workers.map(({ worker }) => worker.terminate()));
2290
+ }
2291
+ };
2292
+ var sharedPool = null;
2293
+ function getSharedIvSolverPool() {
2294
+ if (!sharedPool) {
2295
+ sharedPool = new IvSolverPool();
2296
+ }
2297
+ return sharedPool;
2298
+ }
2299
+
2082
2300
  // src/utils/option-quote-greeks.ts
2083
2301
  var OPTION_QUOTE_GREEKS_REVISION = 3;
2084
2302
  var OPTION_QUOTE_GREEKS_RATE_TYPE = "sofr";
2085
2303
  var OPTION_QUOTE_GREEKS_GAMMA_SOURCE = "computed_sofr_q0";
2086
2304
  var OPTION_QUOTE_GREEKS_DIVIDEND_YIELD = 0;
2087
- function isFiniteNumber(value) {
2305
+ function isFiniteNumber2(value) {
2088
2306
  return typeof value === "number" && Number.isFinite(value);
2089
2307
  }
2308
+ var sofrRateByDateKey = /* @__PURE__ */ new Map();
2309
+ function memoizedSofrRate(dateKey) {
2310
+ const cached = sofrRateByDateKey.get(dateKey);
2311
+ if (cached !== void 0) return cached;
2312
+ const rate = getSofrRateByKey(dateKey);
2313
+ sofrRateByDateKey.set(dateKey, rate);
2314
+ return rate;
2315
+ }
2090
2316
  function hasQuoteGreeks(row) {
2091
- return isFiniteNumber(row.delta ?? null) && isFiniteNumber(row.gamma ?? null) && isFiniteNumber(row.theta ?? null) && isFiniteNumber(row.vega ?? null) && isFiniteNumber(row.iv ?? null);
2317
+ return isFiniteNumber2(row.delta ?? null) && isFiniteNumber2(row.gamma ?? null) && isFiniteNumber2(row.theta ?? null) && isFiniteNumber2(row.vega ?? null) && isFiniteNumber2(row.iv ?? null);
2092
2318
  }
2093
2319
  function hasProviderFirstOrderGreeks(row) {
2094
- return row.greeks_source === "thetadata" && isFiniteNumber(row.delta ?? null) && (row.gamma == null || isFiniteNumber(row.gamma)) && isFiniteNumber(row.theta ?? null) && isFiniteNumber(row.vega ?? null) && isFiniteNumber(row.iv ?? null);
2320
+ return row.greeks_source === "thetadata" && isFiniteNumber2(row.delta ?? null) && (row.gamma == null || isFiniteNumber2(row.gamma)) && isFiniteNumber2(row.theta ?? null) && isFiniteNumber2(row.vega ?? null) && isFiniteNumber2(row.iv ?? null);
2095
2321
  }
2096
2322
  function hasExistingQuoteGreeks(row) {
2097
2323
  return hasQuoteGreeks(row) || hasProviderFirstOrderGreeks(row);
2098
2324
  }
2099
2325
  function hasQuoteGreekProvenanceFields(row) {
2100
- return row.rate_type === OPTION_QUOTE_GREEKS_RATE_TYPE && isFiniteNumber(row.rate_value ?? null) && row.gamma_source === OPTION_QUOTE_GREEKS_GAMMA_SOURCE;
2326
+ return row.rate_type === OPTION_QUOTE_GREEKS_RATE_TYPE && isFiniteNumber2(row.rate_value ?? null) && row.gamma_source === OPTION_QUOTE_GREEKS_GAMMA_SOURCE;
2101
2327
  }
2102
2328
  function normalizeExistingQuoteGreeks(row, defaultSource) {
2103
2329
  if (!hasExistingQuoteGreeks(row)) return;
@@ -2108,55 +2334,8 @@ function normalizeExistingQuoteGreeks(row, defaultSource) {
2108
2334
  row.greeks_revision = OPTION_QUOTE_GREEKS_REVISION;
2109
2335
  }
2110
2336
  }
2111
- function computeQuoteGreeks(params) {
2112
- const {
2113
- optionPrice,
2114
- underlyingPrice,
2115
- strike,
2116
- date,
2117
- time,
2118
- expiration,
2119
- contractType
2120
- } = params;
2121
- if (!(optionPrice > 0) || !(underlyingPrice > 0) || !(strike > 0)) return null;
2122
- const dte = computeFractionalDte(date, time.slice(0, 5), expiration);
2123
- if (!(dte >= 0)) return null;
2124
- const riskFreeRate = getSofrRateByKey(date) / 100;
2125
- const result = computeLegGreeks(
2126
- optionPrice,
2127
- underlyingPrice,
2128
- strike,
2129
- dte,
2130
- contractType === "call" ? "C" : "P",
2131
- riskFreeRate,
2132
- OPTION_QUOTE_GREEKS_DIVIDEND_YIELD
2133
- );
2134
- if (!hasQuoteGreeks(result)) return null;
2337
+ function emptyStats() {
2135
2338
  return {
2136
- delta: result.delta,
2137
- gamma: result.gamma,
2138
- theta: result.theta,
2139
- vega: result.vega,
2140
- iv: result.iv,
2141
- greeks_source: "computed",
2142
- greeks_revision: OPTION_QUOTE_GREEKS_REVISION,
2143
- rate_type: OPTION_QUOTE_GREEKS_RATE_TYPE,
2144
- rate_value: riskFreeRate,
2145
- gamma_source: OPTION_QUOTE_GREEKS_GAMMA_SOURCE
2146
- };
2147
- }
2148
- function applyQuoteGreeks(params) {
2149
- const {
2150
- rows,
2151
- getDate,
2152
- getTime,
2153
- getMid,
2154
- getContractMeta,
2155
- getUnderlyingPrice,
2156
- mode = "auto",
2157
- defaultProviderSource
2158
- } = params;
2159
- const stats = {
2160
2339
  rowsVisited: 0,
2161
2340
  existingGreeksRows: 0,
2162
2341
  computedRows: 0,
@@ -2165,55 +2344,132 @@ function applyQuoteGreeks(params) {
2165
2344
  mathFailedRows: 0,
2166
2345
  unresolvedRows: 0
2167
2346
  };
2168
- for (const row of rows) {
2169
- stats.rowsVisited++;
2170
- if (mode !== "compute" && hasExistingQuoteGreeks(row)) {
2171
- normalizeExistingQuoteGreeks(row, defaultProviderSource);
2347
+ }
2348
+ function resolveQuoteGreeksRow(row, params, defaultProviderSource, mode) {
2349
+ if (mode !== "compute" && hasExistingQuoteGreeks(row)) {
2350
+ normalizeExistingQuoteGreeks(row, defaultProviderSource);
2351
+ return { kind: "existing" };
2352
+ }
2353
+ const meta = params.getContractMeta(row);
2354
+ if (!meta) return { kind: "missingContract" };
2355
+ if (mode === "provider") return { kind: "providerUnresolved" };
2356
+ const date = params.getDate(row);
2357
+ const time = params.getTime(row).slice(0, 5);
2358
+ const underlyingPrice = params.getUnderlyingPrice(date, time);
2359
+ if (!(underlyingPrice != null && underlyingPrice > 0)) {
2360
+ return { kind: "missingUnderlying" };
2361
+ }
2362
+ const optionPrice = params.getMid(row);
2363
+ if (!(optionPrice > 0) || !(meta.strike > 0)) return { kind: "mathFailed" };
2364
+ const dte = computeFractionalDte(date, time, meta.expiration);
2365
+ if (!(dte >= 0)) return { kind: "mathFailed" };
2366
+ const riskFreeRate = memoizedSofrRate(date) / 100;
2367
+ return {
2368
+ kind: "compute",
2369
+ optionPrice,
2370
+ underlyingPrice,
2371
+ strike: meta.strike,
2372
+ dte,
2373
+ type: meta.contract_type === "call" ? 0 : 1,
2374
+ riskFreeRate
2375
+ };
2376
+ }
2377
+ function writeComputedGreeks(row, greeks, riskFreeRate) {
2378
+ row.delta = greeks.delta;
2379
+ row.gamma = greeks.gamma;
2380
+ row.theta = greeks.theta;
2381
+ row.vega = greeks.vega;
2382
+ row.iv = greeks.iv;
2383
+ row.greeks_source = "computed";
2384
+ row.greeks_revision = OPTION_QUOTE_GREEKS_REVISION;
2385
+ row.rate_type = OPTION_QUOTE_GREEKS_RATE_TYPE;
2386
+ row.rate_value = riskFreeRate;
2387
+ row.gamma_source = OPTION_QUOTE_GREEKS_GAMMA_SOURCE;
2388
+ }
2389
+ function tallySkip(stats, kind) {
2390
+ switch (kind) {
2391
+ case "existing":
2172
2392
  stats.existingGreeksRows++;
2173
- continue;
2174
- }
2175
- const meta = getContractMeta(row);
2176
- if (!meta) {
2393
+ return;
2394
+ case "missingContract":
2177
2395
  stats.missingContractRows++;
2178
2396
  stats.unresolvedRows++;
2179
- continue;
2180
- }
2181
- if (mode === "provider") {
2397
+ return;
2398
+ case "providerUnresolved":
2182
2399
  stats.unresolvedRows++;
2183
- continue;
2184
- }
2185
- const date = getDate(row);
2186
- const time = getTime(row).slice(0, 5);
2187
- const underlyingPrice = getUnderlyingPrice(date, time);
2188
- if (!(underlyingPrice != null && underlyingPrice > 0)) {
2400
+ return;
2401
+ case "missingUnderlying":
2189
2402
  stats.missingUnderlyingRows++;
2190
2403
  stats.unresolvedRows++;
2404
+ return;
2405
+ case "mathFailed":
2406
+ stats.mathFailedRows++;
2407
+ stats.unresolvedRows++;
2408
+ return;
2409
+ case "compute":
2410
+ return;
2411
+ }
2412
+ }
2413
+ async function applyQuoteGreeksParallel(params) {
2414
+ const { rows, mode = "auto", defaultProviderSource } = params;
2415
+ const stats = emptyStats();
2416
+ const pool = params.pool ?? getSharedIvSolverPool();
2417
+ const n = rows.length;
2418
+ const optionPrice = new Float64Array(n);
2419
+ const underlyingPrice = new Float64Array(n);
2420
+ const strike = new Float64Array(n);
2421
+ const dte = new Float64Array(n);
2422
+ const riskFreeRate = new Float64Array(n);
2423
+ const dividendYield = new Float64Array(n);
2424
+ const type = new Uint8Array(n);
2425
+ const jobRowIndex = new Int32Array(n);
2426
+ let count = 0;
2427
+ for (let i = 0; i < n; i++) {
2428
+ stats.rowsVisited++;
2429
+ const resolution = resolveQuoteGreeksRow(rows[i], params, defaultProviderSource, mode);
2430
+ if (resolution.kind !== "compute") {
2431
+ tallySkip(stats, resolution.kind);
2191
2432
  continue;
2192
2433
  }
2193
- const greeks = computeQuoteGreeks({
2194
- optionPrice: getMid(row),
2195
- underlyingPrice,
2196
- strike: meta.strike,
2197
- date,
2198
- time,
2199
- expiration: meta.expiration,
2200
- contractType: meta.contract_type
2201
- });
2202
- if (!greeks) {
2434
+ optionPrice[count] = resolution.optionPrice;
2435
+ underlyingPrice[count] = resolution.underlyingPrice;
2436
+ strike[count] = resolution.strike;
2437
+ dte[count] = resolution.dte;
2438
+ riskFreeRate[count] = resolution.riskFreeRate;
2439
+ dividendYield[count] = OPTION_QUOTE_GREEKS_DIVIDEND_YIELD;
2440
+ type[count] = resolution.type;
2441
+ jobRowIndex[count] = i;
2442
+ count++;
2443
+ }
2444
+ if (count === 0) return stats;
2445
+ const columns = {
2446
+ count,
2447
+ optionPrice: optionPrice.subarray(0, count),
2448
+ underlyingPrice: underlyingPrice.subarray(0, count),
2449
+ strike: strike.subarray(0, count),
2450
+ dte: dte.subarray(0, count),
2451
+ riskFreeRate: riskFreeRate.subarray(0, count),
2452
+ dividendYield: dividendYield.subarray(0, count),
2453
+ type: type.subarray(0, count)
2454
+ };
2455
+ const result = await pool.solveColumns(columns);
2456
+ for (let j = 0; j < count; j++) {
2457
+ if (result.ok[j] !== 1) {
2203
2458
  stats.mathFailedRows++;
2204
2459
  stats.unresolvedRows++;
2205
2460
  continue;
2206
2461
  }
2207
- row.delta = greeks.delta;
2208
- row.gamma = greeks.gamma;
2209
- row.theta = greeks.theta;
2210
- row.vega = greeks.vega;
2211
- row.iv = greeks.iv;
2212
- row.greeks_source = greeks.greeks_source;
2213
- row.greeks_revision = greeks.greeks_revision;
2214
- row.rate_type = greeks.rate_type;
2215
- row.rate_value = greeks.rate_value;
2216
- row.gamma_source = greeks.gamma_source;
2462
+ writeComputedGreeks(
2463
+ rows[jobRowIndex[j]],
2464
+ {
2465
+ delta: result.delta[j],
2466
+ gamma: result.gamma[j],
2467
+ theta: result.theta[j],
2468
+ vega: result.vega[j],
2469
+ iv: result.iv[j]
2470
+ },
2471
+ riskFreeRate[j]
2472
+ );
2217
2473
  stats.computedRows++;
2218
2474
  }
2219
2475
  return stats;
@@ -2235,19 +2491,19 @@ function joinKey(row) {
2235
2491
  row.timestamp
2236
2492
  ].join("|");
2237
2493
  }
2238
- function isFiniteNumber2(value) {
2494
+ function isFiniteNumber3(value) {
2239
2495
  return typeof value === "number" && Number.isFinite(value);
2240
2496
  }
2241
2497
  function hasProviderFirstOrderGreeks2(row) {
2242
- return isFiniteNumber2(row.delta) && isFiniteNumber2(row.theta) && isFiniteNumber2(row.vega) && isFiniteNumber2(row.iv);
2498
+ return isFiniteNumber3(row.delta) && isFiniteNumber3(row.theta) && isFiniteNumber3(row.vega) && isFiniteNumber3(row.iv);
2243
2499
  }
2244
2500
  function splitTimestamp(timestamp) {
2245
2501
  const [date, time = ""] = timestamp.split(" ");
2246
2502
  return { date, time: time.slice(0, 5) };
2247
2503
  }
2248
2504
  function computeGamma(row, greek) {
2249
- if (!isFiniteNumber2(greek.iv) || !(greek.iv > 0)) return { gamma: null, rateValue: null };
2250
- if (!isFiniteNumber2(greek.underlyingPrice) || !(greek.underlyingPrice > 0)) {
2505
+ if (!isFiniteNumber3(greek.iv) || !(greek.iv > 0)) return { gamma: null, rateValue: null };
2506
+ if (!isFiniteNumber3(greek.underlyingPrice) || !(greek.underlyingPrice > 0)) {
2251
2507
  return { gamma: null, rateValue: null };
2252
2508
  }
2253
2509
  if (!(row.strike > 0)) return { gamma: null, rateValue: null };
@@ -2303,7 +2559,7 @@ function joinThetaQuotesAndFirstOrderGreeks(options) {
2303
2559
  }
2304
2560
  const rows = [];
2305
2561
  for (const quote of options.quotes) {
2306
- if (!isFiniteNumber2(quote.bid) || !isFiniteNumber2(quote.ask)) {
2562
+ if (!isFiniteNumber3(quote.bid) || !isFiniteNumber3(quote.ask)) {
2307
2563
  stats.droppedQuoteRows++;
2308
2564
  continue;
2309
2565
  }
@@ -2342,61 +2598,6 @@ function joinThetaQuotesAndFirstOrderGreeks(options) {
2342
2598
  return { rows, stats };
2343
2599
  }
2344
2600
 
2345
- // src/utils/providers/thetadata/quote-mid-greeks.ts
2346
- var OPTION_QUOTE_MID_GREEKS_REVISION = 6;
2347
- var OPTION_QUOTE_MID_GREEKS_DIVIDEND_YIELD = OPTION_QUOTE_GREEKS_DIVIDEND_YIELD;
2348
- var OPTION_QUOTE_MID_GREEKS_GAMMA_SOURCE = "computed_thetadata_quote_mid_sofr_q0";
2349
- function isFiniteNumber3(value) {
2350
- return typeof value === "number" && Number.isFinite(value);
2351
- }
2352
- function splitTimestamp2(timestamp) {
2353
- const [date, time = ""] = timestamp.split(" ");
2354
- return { date, time: time.slice(0, 5) };
2355
- }
2356
- function computeThetaQuoteMidGreekRow(params) {
2357
- const { quote, underlyingPrice } = params;
2358
- if (!isFiniteNumber3(quote.bid) || !isFiniteNumber3(quote.ask)) return null;
2359
- if (!isFiniteNumber3(underlyingPrice) || !(underlyingPrice > 0)) return null;
2360
- if (!(quote.strike > 0)) return null;
2361
- const optionPrice = (quote.bid + quote.ask) / 2;
2362
- if (!(optionPrice > 0)) return null;
2363
- const { date, time } = splitTimestamp2(quote.timestamp);
2364
- const dte = computeFractionalDte(date, time, quote.expiration);
2365
- if (!(dte > 0)) return null;
2366
- const rateValue = getSofrRateByKey(date) / 100;
2367
- const greeks = computeLegGreeks(
2368
- optionPrice,
2369
- underlyingPrice,
2370
- quote.strike,
2371
- dte,
2372
- quote.right === "call" ? "C" : "P",
2373
- rateValue,
2374
- OPTION_QUOTE_MID_GREEKS_DIVIDEND_YIELD
2375
- );
2376
- if (!isFiniteNumber3(greeks.delta) || !isFiniteNumber3(greeks.gamma) || !isFiniteNumber3(greeks.theta) || !isFiniteNumber3(greeks.vega) || !isFiniteNumber3(greeks.iv)) {
2377
- return null;
2378
- }
2379
- return {
2380
- ticker: buildTicker(quote),
2381
- timestamp: quote.timestamp,
2382
- bid: quote.bid,
2383
- ask: quote.ask,
2384
- delta: greeks.delta,
2385
- gamma: greeks.gamma,
2386
- theta: greeks.theta,
2387
- // vega is stored per 1% IV move, matching computeLegGreeks' convention and
2388
- // the rest of the emit path (the per-contract x100 multiplier belongs to the
2389
- // consumer, not the stored greek).
2390
- vega: greeks.vega,
2391
- iv: greeks.iv,
2392
- greeks_source: "computed",
2393
- greeks_revision: OPTION_QUOTE_MID_GREEKS_REVISION,
2394
- rate_type: OPTION_QUOTE_GREEKS_RATE_TYPE,
2395
- rate_value: rateValue,
2396
- gamma_source: OPTION_QUOTE_MID_GREEKS_GAMMA_SOURCE
2397
- };
2398
- }
2399
-
2400
2601
  // src/utils/providers/thetadata.ts
2401
2602
  var BULK_YIELD_CHUNK = 5e4;
2402
2603
  var BULK_GREEKS_STRIKE_RANGE = 20;
@@ -2844,74 +3045,17 @@ function _resetProvider() {
2844
3045
 
2845
3046
  export {
2846
3047
  BACHELIER_DTE_THRESHOLD,
2847
- pdf,
2848
- cdf,
2849
3048
  bsPrice,
2850
- bsDelta,
2851
- bsGamma,
2852
- bsTheta,
2853
- bsVega,
2854
- solveIV,
2855
3049
  bachelierPrice,
2856
- bachelierDelta,
2857
- bachelierGamma,
2858
- bachelierTheta,
2859
- bachelierVega,
2860
- solveNormalIV,
2861
- computeLegGreeks,
2862
- resolveMassiveDataTier,
2863
- MassiveBarSchema,
2864
- MassiveAggregateResponseSchema,
2865
- MassiveQuoteSchema,
2866
- MassiveQuotesResponseSchema,
2867
- MassiveSnapshotContractSchema,
2868
- MassiveSnapshotResponseSchema,
2869
- MASSIVE_BASE_URL,
2870
- MASSIVE_MAX_LIMIT,
2871
- MASSIVE_MAX_PAGES,
2872
- toMassiveTicker,
2873
- fromMassiveTicker,
2874
- massiveTimestampToETDate,
2875
- massiveTimestampToETTime,
2876
- nanosToETMinuteKey,
2877
- MassiveProvider,
2878
3050
  markPrice,
2879
- findNearestTimestamp,
2880
3051
  parseLegsString,
2881
3052
  buildOccTicker,
2882
3053
  computeStrategyPnlPath,
2883
3054
  computeReplayMfeMae,
2884
- ThetaMddsClient,
2885
- thetaTimestampToEtMinute,
2886
- decodeThetaResponseData,
2887
- normalizeThetaFirstOrderGreekRow,
2888
- normalizeThetaOpenInterestRow,
2889
- normalizeThetaStockOhlcRow,
2890
- normalizeThetaStockEodRow,
2891
- normalizeThetaIndexOhlcRow,
2892
- normalizeThetaIndexEodRow,
2893
- optionHistoryQuote,
2894
- stockHistoryOhlc,
2895
- stockHistoryEod,
2896
- indexHistoryOhlc,
2897
- indexHistoryEod,
2898
- optionAtTimeQuote,
2899
- optionHistoryGreeksFirstOrder,
2900
- optionHistoryGreeksFirstOrderBand,
2901
- optionHistoryImpliedVolatilityBand,
2902
- optionHistoryQuoteBand,
2903
- optionHistoryOpenInterest,
2904
- optionListContracts,
2905
- computeFractionalDte,
2906
- OPTION_QUOTE_GREEKS_REVISION,
2907
- hasQuoteGreeks,
2908
- applyQuoteGreeks,
2909
- joinThetaQuotesAndFirstOrderGreeks,
2910
- OPTION_QUOTE_MID_GREEKS_REVISION,
2911
- OPTION_QUOTE_MID_GREEKS_GAMMA_SOURCE,
2912
- computeThetaQuoteMidGreekRow,
3055
+ MassiveProvider,
3056
+ applyQuoteGreeksParallel,
2913
3057
  ThetaDataProvider,
2914
3058
  getProvider,
2915
3059
  _resetProvider
2916
3060
  };
2917
- //# sourceMappingURL=chunk-CCITWAAI.js.map
3061
+ //# sourceMappingURL=chunk-PNKG7RY7.js.map