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,10 +1,9 @@
1
- #!/usr/bin/env node
2
1
  import {
3
2
  getSofrRateByKey
4
- } from "./chunk-72GKJE2U.js";
3
+ } from "./chunk-QTTR7AAW.js";
5
4
  import {
6
5
  loadMddsProtoRoot
7
- } from "./chunk-FBNDMCT5.js";
6
+ } from "./chunk-FGZH632F.js";
8
7
 
9
8
  // src/utils/providers/massive.ts
10
9
  import { z } from "zod";
@@ -752,18 +751,18 @@ var MassiveProvider = class {
752
751
  const s3Path = `s3massive:flatfiles/${assetPath}/${year}/${month}/${date}.csv.gz`;
753
752
  const tmpDir = assetClass === "index" ? "/tmp/massive-flat-index" : "/tmp/massive-flat";
754
753
  const localPath = `${tmpDir}/${date}.csv.gz`;
755
- const { existsSync, mkdirSync } = await import("fs");
754
+ const { existsSync: existsSync2, mkdirSync } = await import("fs");
756
755
  const { execFile } = await import("child_process");
757
756
  const { promisify } = await import("util");
758
757
  const execFileAsync = promisify(execFile);
759
758
  mkdirSync(tmpDir, { recursive: true });
760
- if (existsSync(localPath)) return localPath;
759
+ if (existsSync2(localPath)) return localPath;
761
760
  try {
762
761
  await execFileAsync("rclone", ["copy", s3Path, `${tmpDir}/`], { timeout: 12e4 });
763
762
  } catch {
764
763
  return null;
765
764
  }
766
- return existsSync(localPath) ? localPath : null;
765
+ return existsSync2(localPath) ? localPath : null;
767
766
  }
768
767
  async downloadBulkData(options) {
769
768
  const { date, dataset, assetClass, tickers, outputPath } = options;
@@ -1386,7 +1385,7 @@ var ThetaMddsClient = class {
1386
1385
  fetchImpl;
1387
1386
  loadGrpcPackage;
1388
1387
  constructor(env = process.env, fetchImpl = fetch, loadGrpcPackage = async () => {
1389
- const { loadMddsGrpcPackage } = await import("./proto-DWRZJO4E.js");
1388
+ const { loadMddsGrpcPackage } = await import("./proto-3HZTCWK4.js");
1390
1389
  return loadMddsGrpcPackage();
1391
1390
  }) {
1392
1391
  this.env = env;
@@ -1528,22 +1527,23 @@ var PRICE_TYPE_FACTORS = [
1528
1527
  1e9
1529
1528
  ];
1530
1529
  var ET_MINUTE_PATTERN = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/;
1530
+ var ET_MINUTE_FORMAT = new Intl.DateTimeFormat("en-CA", {
1531
+ timeZone: "America/New_York",
1532
+ year: "numeric",
1533
+ month: "2-digit",
1534
+ day: "2-digit",
1535
+ hour: "2-digit",
1536
+ minute: "2-digit",
1537
+ hour12: false,
1538
+ hourCycle: "h23"
1539
+ });
1531
1540
  function thetaPriceToNumber(price) {
1532
1541
  return price.type === 0 ? Number.NaN : price.value * PRICE_TYPE_FACTORS[price.type];
1533
1542
  }
1534
1543
  function thetaTimestampToEtMinute(value) {
1535
1544
  if (typeof value === "string" && ET_MINUTE_PATTERN.test(value)) return value;
1536
1545
  const date = value instanceof Date ? value : new Date(value);
1537
- const parts = new Intl.DateTimeFormat("en-CA", {
1538
- timeZone: "America/New_York",
1539
- year: "numeric",
1540
- month: "2-digit",
1541
- day: "2-digit",
1542
- hour: "2-digit",
1543
- minute: "2-digit",
1544
- hour12: false,
1545
- hourCycle: "h23"
1546
- }).formatToParts(date);
1546
+ const parts = ET_MINUTE_FORMAT.formatToParts(date);
1547
1547
  const get = (type) => parts.find((part) => part.type === type)?.value ?? "";
1548
1548
  return `${get("year")}-${get("month")}-${get("day")} ${get("hour")}:${get("minute")}`;
1549
1549
  }
@@ -1669,6 +1669,17 @@ function normalizeThetaFirstOrderGreekRow(row) {
1669
1669
  underlyingPrice: asNumber(row.underlying_price)
1670
1670
  };
1671
1671
  }
1672
+ function normalizeThetaImpliedVolatilityRow(row) {
1673
+ return {
1674
+ ...normalizeThetaQuoteRow(row),
1675
+ bidIv: asNumber(row.bid_implied_vol),
1676
+ midIv: asNumber(row.implied_vol ?? row.midpoint_implied_vol),
1677
+ askIv: asNumber(row.ask_implied_vol),
1678
+ ivError: asNumber(row.iv_error),
1679
+ underlyingTimestamp: row.underlying_timestamp == null ? null : thetaTimestampToEtMinute(asText(row.underlying_timestamp)),
1680
+ underlyingPrice: asNumber(row.underlying_price)
1681
+ };
1682
+ }
1672
1683
  function normalizeThetaContractListRow(row) {
1673
1684
  return {
1674
1685
  symbol: requiredText(row.symbol, "contract-list row", "symbol").toUpperCase(),
@@ -1888,6 +1899,30 @@ async function indexHistoryEod(client, params) {
1888
1899
  );
1889
1900
  return decodeThetaRows(chunks).map(normalizeThetaIndexEodRow);
1890
1901
  }
1902
+ async function optionAtTimeQuote(client, params) {
1903
+ const symbol = validateSymbol(params.symbol);
1904
+ const expiration = validateHyphenDate(params.expiration, "expiration");
1905
+ const strike = validateStrike(params.strike);
1906
+ const date = validateHyphenDate(params.date, "date");
1907
+ const strikeRange = optionalPositiveInteger2(params.strikeRange, "strike_range");
1908
+ const chunks = await client.callStream(
1909
+ "GetOptionAtTimeQuote",
1910
+ endpointRequest(client.queryInfo(), {
1911
+ contractSpec: {
1912
+ symbol,
1913
+ expiration,
1914
+ strike,
1915
+ right: thetaRequestRight(params.right)
1916
+ },
1917
+ startDate: date,
1918
+ endDate: date,
1919
+ timeOfDay: mddsTime(params.time, "09:45:00.000"),
1920
+ expiration,
1921
+ ...strikeRange == null ? {} : { strikeRange }
1922
+ })
1923
+ );
1924
+ return decodeThetaRows(chunks).map(normalizeThetaQuoteRow);
1925
+ }
1891
1926
  async function optionHistoryGreeksFirstOrder(client, params) {
1892
1927
  const symbol = validateSymbol(params.symbol);
1893
1928
  const expiration = validateHyphenDate(params.expiration, "expiration");
@@ -1941,6 +1976,57 @@ async function optionHistoryGreeksFirstOrderBand(client, params) {
1941
1976
  );
1942
1977
  return decodeThetaRows(chunks).map(normalizeThetaFirstOrderGreekRow);
1943
1978
  }
1979
+ async function optionHistoryImpliedVolatilityBand(client, params) {
1980
+ const symbol = validateSymbol(params.symbol);
1981
+ const expiration = params.expiration === "*" ? "*" : validateHyphenDate(params.expiration, "expiration");
1982
+ const date = validateHyphenDate(params.date, "date");
1983
+ const strikeRange = optionalPositiveInteger2(params.strikeRange, "strike_range");
1984
+ const chunks = await client.callStream(
1985
+ "GetOptionHistoryGreeksImpliedVolatility",
1986
+ endpointRequest(client.queryInfo(), {
1987
+ contractSpec: {
1988
+ symbol,
1989
+ expiration,
1990
+ strike: THETA_WILDCARD_STRIKE,
1991
+ right: "both"
1992
+ },
1993
+ expiration,
1994
+ date,
1995
+ interval: params.interval ?? "1m",
1996
+ startTime: mddsTime(params.startTime, "09:30:00.000"),
1997
+ endTime: mddsTime(params.endTime, "16:00:00.000"),
1998
+ rateType: params.rateType ?? "sofr",
1999
+ ...params.annualDividend == null ? {} : { annualDividend: params.annualDividend },
2000
+ version: "latest",
2001
+ strikeRange
2002
+ })
2003
+ );
2004
+ return decodeThetaRows(chunks).map(normalizeThetaImpliedVolatilityRow);
2005
+ }
2006
+ async function optionHistoryQuoteBand(client, params) {
2007
+ const symbol = validateSymbol(params.symbol);
2008
+ const expiration = params.expiration === "*" ? "*" : validateHyphenDate(params.expiration, "expiration");
2009
+ const date = validateHyphenDate(params.date, "date");
2010
+ const strikeRange = optionalPositiveInteger2(params.strikeRange, "strike_range");
2011
+ const chunks = await client.callStream(
2012
+ "GetOptionHistoryQuote",
2013
+ endpointRequest(client.queryInfo(), {
2014
+ contractSpec: {
2015
+ symbol,
2016
+ expiration,
2017
+ strike: THETA_WILDCARD_STRIKE,
2018
+ right: "both"
2019
+ },
2020
+ expiration,
2021
+ date,
2022
+ interval: params.interval ?? "1m",
2023
+ startTime: mddsTime(params.startTime, "09:30:00.000"),
2024
+ endTime: mddsTime(params.endTime, "16:00:00.000"),
2025
+ ...strikeRange == null ? {} : { strikeRange }
2026
+ })
2027
+ );
2028
+ return decodeThetaRows(chunks).map(normalizeThetaQuoteRow);
2029
+ }
1944
2030
  async function optionHistoryOpenInterest(client, params) {
1945
2031
  const symbol = validateSymbol(params.symbol);
1946
2032
  const expiration = params.expiration === "*" ? "*" : validateHyphenDate(params.expiration, "expiration");
@@ -1994,25 +2080,342 @@ function computeFractionalDte(date, time, expiration) {
1994
2080
  return Math.max(dayDiff + remainingMinutes / (24 * 60), 0);
1995
2081
  }
1996
2082
 
2083
+ // src/utils/iv-solver-pool.ts
2084
+ import { Worker } from "worker_threads";
2085
+ import { availableParallelism } from "os";
2086
+ import { existsSync } from "fs";
2087
+ import { fileURLToPath } from "url";
2088
+
2089
+ // src/utils/iv-solver-worker.ts
2090
+ import { parentPort } from "worker_threads";
2091
+ function isFiniteNumber(value) {
2092
+ return typeof value === "number" && Number.isFinite(value);
2093
+ }
2094
+ function solveIvBatch(req) {
2095
+ const { id, count } = req;
2096
+ const delta = new Float64Array(count);
2097
+ const gamma = new Float64Array(count);
2098
+ const theta = new Float64Array(count);
2099
+ const vega = new Float64Array(count);
2100
+ const iv = new Float64Array(count);
2101
+ const ok = new Uint8Array(count);
2102
+ for (let i = 0; i < count; i++) {
2103
+ const result = computeLegGreeks(
2104
+ req.optionPrice[i],
2105
+ req.underlyingPrice[i],
2106
+ req.strike[i],
2107
+ req.dte[i],
2108
+ req.type[i] === 0 ? "C" : "P",
2109
+ req.riskFreeRate[i],
2110
+ req.dividendYield[i]
2111
+ );
2112
+ if (isFiniteNumber(result.delta) && isFiniteNumber(result.gamma) && isFiniteNumber(result.theta) && isFiniteNumber(result.vega) && isFiniteNumber(result.iv)) {
2113
+ delta[i] = result.delta;
2114
+ gamma[i] = result.gamma;
2115
+ theta[i] = result.theta;
2116
+ vega[i] = result.vega;
2117
+ iv[i] = result.iv;
2118
+ ok[i] = 1;
2119
+ } else {
2120
+ ok[i] = 0;
2121
+ }
2122
+ }
2123
+ return { id, count, delta, gamma, theta, vega, iv, ok };
2124
+ }
2125
+ if (parentPort) {
2126
+ const port = parentPort;
2127
+ port.on("message", (req) => {
2128
+ const reply = solveIvBatch(req);
2129
+ port.postMessage(reply, [
2130
+ reply.delta.buffer,
2131
+ reply.gamma.buffer,
2132
+ reply.theta.buffer,
2133
+ reply.vega.buffer,
2134
+ reply.iv.buffer,
2135
+ reply.ok.buffer
2136
+ ]);
2137
+ });
2138
+ }
2139
+
2140
+ // src/utils/iv-solver-pool.ts
2141
+ var DEFAULT_INLINE_THRESHOLD = 2e3;
2142
+ var MIN_SHARD = 1e3;
2143
+ function resolveWorkerUrl() {
2144
+ const jsUrl = new URL("./iv-solver-worker.js", import.meta.url);
2145
+ if (existsSync(fileURLToPath(jsUrl))) return jsUrl;
2146
+ return new URL("./iv-solver-worker.ts", import.meta.url);
2147
+ }
2148
+ function workersDisabledByEnv() {
2149
+ const flag = process.env.TRADEBLOCKS_IV_WORKERS;
2150
+ return flag === "0" || flag === "false" || flag === "off";
2151
+ }
2152
+ function defaultMaxWorkers() {
2153
+ const cores = Math.max(1, availableParallelism());
2154
+ return Math.max(1, cores - 1);
2155
+ }
2156
+ function jobsToColumns(jobs) {
2157
+ const count = jobs.length;
2158
+ const cols = {
2159
+ count,
2160
+ optionPrice: new Float64Array(count),
2161
+ underlyingPrice: new Float64Array(count),
2162
+ strike: new Float64Array(count),
2163
+ dte: new Float64Array(count),
2164
+ riskFreeRate: new Float64Array(count),
2165
+ dividendYield: new Float64Array(count),
2166
+ type: new Uint8Array(count)
2167
+ };
2168
+ for (let i = 0; i < count; i++) {
2169
+ const job = jobs[i];
2170
+ cols.optionPrice[i] = job.optionPrice;
2171
+ cols.underlyingPrice[i] = job.underlyingPrice;
2172
+ cols.strike[i] = job.strike;
2173
+ cols.dte[i] = job.dte;
2174
+ cols.riskFreeRate[i] = job.riskFreeRate;
2175
+ cols.dividendYield[i] = job.dividendYield;
2176
+ cols.type[i] = job.type === "C" ? 0 : 1;
2177
+ }
2178
+ return cols;
2179
+ }
2180
+ function shardRequest(cols, id, lo, hi) {
2181
+ return {
2182
+ id,
2183
+ count: hi - lo,
2184
+ optionPrice: cols.optionPrice.slice(lo, hi),
2185
+ underlyingPrice: cols.underlyingPrice.slice(lo, hi),
2186
+ strike: cols.strike.slice(lo, hi),
2187
+ dte: cols.dte.slice(lo, hi),
2188
+ riskFreeRate: cols.riskFreeRate.slice(lo, hi),
2189
+ dividendYield: cols.dividendYield.slice(lo, hi),
2190
+ type: cols.type.slice(lo, hi)
2191
+ };
2192
+ }
2193
+ function wholeRequest(cols, id) {
2194
+ return {
2195
+ id,
2196
+ count: cols.count,
2197
+ optionPrice: cols.optionPrice,
2198
+ underlyingPrice: cols.underlyingPrice,
2199
+ strike: cols.strike,
2200
+ dte: cols.dte,
2201
+ riskFreeRate: cols.riskFreeRate,
2202
+ dividendYield: cols.dividendYield,
2203
+ type: cols.type
2204
+ };
2205
+ }
2206
+ function columnsResultFrom(reply) {
2207
+ return {
2208
+ count: reply.count,
2209
+ delta: reply.delta,
2210
+ gamma: reply.gamma,
2211
+ theta: reply.theta,
2212
+ vega: reply.vega,
2213
+ iv: reply.iv,
2214
+ ok: reply.ok
2215
+ };
2216
+ }
2217
+ function resultsFromColumns(cols) {
2218
+ const out = new Array(cols.count);
2219
+ for (let i = 0; i < cols.count; i++) {
2220
+ out[i] = {
2221
+ delta: cols.delta[i],
2222
+ gamma: cols.gamma[i],
2223
+ theta: cols.theta[i],
2224
+ vega: cols.vega[i],
2225
+ iv: cols.iv[i],
2226
+ ok: cols.ok[i] === 1
2227
+ };
2228
+ }
2229
+ return out;
2230
+ }
2231
+ var IvSolverPool = class {
2232
+ maxWorkers;
2233
+ inlineThreshold;
2234
+ workerUrl;
2235
+ enabled;
2236
+ pool = [];
2237
+ nextRequestId = 1;
2238
+ // Serializes parallel solves against the shared worker set. The `busy`/slot
2239
+ // accounting in solveColumnsParallel is only correct when a single solve
2240
+ // owns the whole pool; without this gate, two concurrent callers could route
2241
+ // shards onto the same worker and corrupt the busy bookkeeping. Inline solves
2242
+ // don't touch workers and bypass the gate.
2243
+ parallelChain = Promise.resolve();
2244
+ constructor(options = {}) {
2245
+ this.maxWorkers = Math.max(1, options.maxWorkers ?? defaultMaxWorkers());
2246
+ this.inlineThreshold = options.inlineThreshold ?? DEFAULT_INLINE_THRESHOLD;
2247
+ this.workerUrl = resolveWorkerUrl();
2248
+ this.enabled = !workersDisabledByEnv() && this.maxWorkers > 1;
2249
+ }
2250
+ /**
2251
+ * Solve a batch of jobs, returning one result per job in input order. Thin
2252
+ * wrapper over `solveColumns` — convenient for callers (and tests) that hold
2253
+ * an `IvSolveJob[]`. The hot ingest path uses `solveColumns` directly to
2254
+ * avoid materializing the object array.
2255
+ */
2256
+ async solve(jobs) {
2257
+ if (jobs.length === 0) return [];
2258
+ const result = await this.solveColumns(jobsToColumns(jobs));
2259
+ return resultsFromColumns(result);
2260
+ }
2261
+ /**
2262
+ * Solve a pre-built column batch. Uses the worker pool when the batch is
2263
+ * large enough and workers are enabled; otherwise solves inline via the same
2264
+ * `solveIvBatch` the workers run (so inline and parallel are interchangeable).
2265
+ * The reply columns are positionally aligned with the input.
2266
+ */
2267
+ async solveColumns(cols) {
2268
+ if (cols.count === 0) {
2269
+ return {
2270
+ count: 0,
2271
+ delta: new Float64Array(0),
2272
+ gamma: new Float64Array(0),
2273
+ theta: new Float64Array(0),
2274
+ vega: new Float64Array(0),
2275
+ iv: new Float64Array(0),
2276
+ ok: new Uint8Array(0)
2277
+ };
2278
+ }
2279
+ const shardCount = this.enabled ? Math.min(this.maxWorkers, Math.ceil(cols.count / MIN_SHARD)) : 1;
2280
+ if (shardCount <= 1 || cols.count < this.inlineThreshold) {
2281
+ return columnsResultFrom(solveIvBatch(wholeRequest(cols, 0)));
2282
+ }
2283
+ const run = this.parallelChain.catch(() => void 0).then(() => this.solveColumnsParallel(cols, shardCount));
2284
+ this.parallelChain = run;
2285
+ return run;
2286
+ }
2287
+ async solveColumnsParallel(cols, shardCount) {
2288
+ this.ensureWorkers(shardCount);
2289
+ const out = {
2290
+ count: cols.count,
2291
+ delta: new Float64Array(cols.count),
2292
+ gamma: new Float64Array(cols.count),
2293
+ theta: new Float64Array(cols.count),
2294
+ vega: new Float64Array(cols.count),
2295
+ iv: new Float64Array(cols.count),
2296
+ ok: new Uint8Array(cols.count)
2297
+ };
2298
+ const base = Math.floor(cols.count / shardCount);
2299
+ const remainder = cols.count % shardCount;
2300
+ const shardPromises = [];
2301
+ let cursor = 0;
2302
+ for (let s = 0; s < shardCount; s++) {
2303
+ const size = base + (s < remainder ? 1 : 0);
2304
+ const lo = cursor;
2305
+ const hi = cursor + size;
2306
+ cursor = hi;
2307
+ if (size === 0) continue;
2308
+ const request = shardRequest(cols, this.nextRequestId++, lo, hi);
2309
+ shardPromises.push(
2310
+ this.runOnWorker(request).then((reply) => {
2311
+ out.delta.set(reply.delta, lo);
2312
+ out.gamma.set(reply.gamma, lo);
2313
+ out.theta.set(reply.theta, lo);
2314
+ out.vega.set(reply.vega, lo);
2315
+ out.iv.set(reply.iv, lo);
2316
+ out.ok.set(reply.ok, lo);
2317
+ })
2318
+ );
2319
+ }
2320
+ await Promise.all(shardPromises);
2321
+ return out;
2322
+ }
2323
+ ensureWorkers(target) {
2324
+ while (this.pool.length < target) {
2325
+ const worker = new Worker(this.workerUrl);
2326
+ worker.setMaxListeners(0);
2327
+ worker.on("error", (err) => {
2328
+ throw err;
2329
+ });
2330
+ this.pool.push({ worker, busy: false });
2331
+ }
2332
+ }
2333
+ runOnWorker(request) {
2334
+ return new Promise((resolve, reject) => {
2335
+ const slot = this.acquire();
2336
+ const onMessage = (reply) => {
2337
+ if (reply.id !== request.id) return;
2338
+ cleanup();
2339
+ slot.busy = false;
2340
+ resolve(reply);
2341
+ };
2342
+ const onError = (err) => {
2343
+ cleanup();
2344
+ slot.busy = false;
2345
+ reject(err);
2346
+ };
2347
+ const cleanup = () => {
2348
+ slot.worker.off("message", onMessage);
2349
+ slot.worker.off("error", onError);
2350
+ };
2351
+ slot.worker.on("message", onMessage);
2352
+ slot.worker.once("error", onError);
2353
+ slot.worker.postMessage(request, [
2354
+ request.optionPrice.buffer,
2355
+ request.underlyingPrice.buffer,
2356
+ request.strike.buffer,
2357
+ request.dte.buffer,
2358
+ request.riskFreeRate.buffer,
2359
+ request.dividendYield.buffer,
2360
+ request.type.buffer
2361
+ ]);
2362
+ });
2363
+ }
2364
+ acquire() {
2365
+ const free = this.pool.find((w) => !w.busy);
2366
+ const slot = free ?? this.pool[0];
2367
+ slot.busy = true;
2368
+ return slot;
2369
+ }
2370
+ /** Terminate all workers. Idempotent. */
2371
+ async destroy() {
2372
+ const workers = this.pool;
2373
+ this.pool = [];
2374
+ await Promise.all(workers.map(({ worker }) => worker.terminate()));
2375
+ }
2376
+ };
2377
+ var sharedPool = null;
2378
+ function getSharedIvSolverPool() {
2379
+ if (!sharedPool) {
2380
+ sharedPool = new IvSolverPool();
2381
+ }
2382
+ return sharedPool;
2383
+ }
2384
+ async function destroySharedIvSolverPool() {
2385
+ if (sharedPool) {
2386
+ const pool = sharedPool;
2387
+ sharedPool = null;
2388
+ await pool.destroy();
2389
+ }
2390
+ }
2391
+
1997
2392
  // src/utils/option-quote-greeks.ts
1998
2393
  var OPTION_QUOTE_GREEKS_REVISION = 3;
1999
2394
  var OPTION_QUOTE_GREEKS_RATE_TYPE = "sofr";
2000
2395
  var OPTION_QUOTE_GREEKS_GAMMA_SOURCE = "computed_sofr_q0";
2001
2396
  var OPTION_QUOTE_GREEKS_DIVIDEND_YIELD = 0;
2002
- function isFiniteNumber(value) {
2397
+ function isFiniteNumber2(value) {
2003
2398
  return typeof value === "number" && Number.isFinite(value);
2004
2399
  }
2400
+ var sofrRateByDateKey = /* @__PURE__ */ new Map();
2401
+ function memoizedSofrRate(dateKey) {
2402
+ const cached = sofrRateByDateKey.get(dateKey);
2403
+ if (cached !== void 0) return cached;
2404
+ const rate = getSofrRateByKey(dateKey);
2405
+ sofrRateByDateKey.set(dateKey, rate);
2406
+ return rate;
2407
+ }
2005
2408
  function hasQuoteGreeks(row) {
2006
- return isFiniteNumber(row.delta ?? null) && isFiniteNumber(row.gamma ?? null) && isFiniteNumber(row.theta ?? null) && isFiniteNumber(row.vega ?? null) && isFiniteNumber(row.iv ?? null);
2409
+ return isFiniteNumber2(row.delta ?? null) && isFiniteNumber2(row.gamma ?? null) && isFiniteNumber2(row.theta ?? null) && isFiniteNumber2(row.vega ?? null) && isFiniteNumber2(row.iv ?? null);
2007
2410
  }
2008
2411
  function hasProviderFirstOrderGreeks(row) {
2009
- 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);
2412
+ 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);
2010
2413
  }
2011
2414
  function hasExistingQuoteGreeks(row) {
2012
2415
  return hasQuoteGreeks(row) || hasProviderFirstOrderGreeks(row);
2013
2416
  }
2014
2417
  function hasQuoteGreekProvenanceFields(row) {
2015
- return row.rate_type === OPTION_QUOTE_GREEKS_RATE_TYPE && isFiniteNumber(row.rate_value ?? null) && row.gamma_source === OPTION_QUOTE_GREEKS_GAMMA_SOURCE;
2418
+ return row.rate_type === OPTION_QUOTE_GREEKS_RATE_TYPE && isFiniteNumber2(row.rate_value ?? null) && row.gamma_source === OPTION_QUOTE_GREEKS_GAMMA_SOURCE;
2016
2419
  }
2017
2420
  function normalizeExistingQuoteGreeks(row, defaultSource) {
2018
2421
  if (!hasExistingQuoteGreeks(row)) return;
@@ -2023,55 +2426,8 @@ function normalizeExistingQuoteGreeks(row, defaultSource) {
2023
2426
  row.greeks_revision = OPTION_QUOTE_GREEKS_REVISION;
2024
2427
  }
2025
2428
  }
2026
- function computeQuoteGreeks(params) {
2027
- const {
2028
- optionPrice,
2029
- underlyingPrice,
2030
- strike,
2031
- date,
2032
- time,
2033
- expiration,
2034
- contractType
2035
- } = params;
2036
- if (!(optionPrice > 0) || !(underlyingPrice > 0) || !(strike > 0)) return null;
2037
- const dte = computeFractionalDte(date, time.slice(0, 5), expiration);
2038
- if (!(dte >= 0)) return null;
2039
- const riskFreeRate = getSofrRateByKey(date) / 100;
2040
- const result = computeLegGreeks(
2041
- optionPrice,
2042
- underlyingPrice,
2043
- strike,
2044
- dte,
2045
- contractType === "call" ? "C" : "P",
2046
- riskFreeRate,
2047
- OPTION_QUOTE_GREEKS_DIVIDEND_YIELD
2048
- );
2049
- if (!hasQuoteGreeks(result)) return null;
2429
+ function emptyStats() {
2050
2430
  return {
2051
- delta: result.delta,
2052
- gamma: result.gamma,
2053
- theta: result.theta,
2054
- vega: result.vega,
2055
- iv: result.iv,
2056
- greeks_source: "computed",
2057
- greeks_revision: OPTION_QUOTE_GREEKS_REVISION,
2058
- rate_type: OPTION_QUOTE_GREEKS_RATE_TYPE,
2059
- rate_value: riskFreeRate,
2060
- gamma_source: OPTION_QUOTE_GREEKS_GAMMA_SOURCE
2061
- };
2062
- }
2063
- function applyQuoteGreeks(params) {
2064
- const {
2065
- rows,
2066
- getDate,
2067
- getTime,
2068
- getMid,
2069
- getContractMeta,
2070
- getUnderlyingPrice,
2071
- mode = "auto",
2072
- defaultProviderSource
2073
- } = params;
2074
- const stats = {
2075
2431
  rowsVisited: 0,
2076
2432
  existingGreeksRows: 0,
2077
2433
  computedRows: 0,
@@ -2080,55 +2436,171 @@ function applyQuoteGreeks(params) {
2080
2436
  mathFailedRows: 0,
2081
2437
  unresolvedRows: 0
2082
2438
  };
2083
- for (const row of rows) {
2084
- stats.rowsVisited++;
2085
- if (mode !== "compute" && hasExistingQuoteGreeks(row)) {
2086
- normalizeExistingQuoteGreeks(row, defaultProviderSource);
2439
+ }
2440
+ function resolveQuoteGreeksRow(row, params, defaultProviderSource, mode) {
2441
+ if (mode !== "compute" && hasExistingQuoteGreeks(row)) {
2442
+ normalizeExistingQuoteGreeks(row, defaultProviderSource);
2443
+ return { kind: "existing" };
2444
+ }
2445
+ const meta = params.getContractMeta(row);
2446
+ if (!meta) return { kind: "missingContract" };
2447
+ if (mode === "provider") return { kind: "providerUnresolved" };
2448
+ const date = params.getDate(row);
2449
+ const time = params.getTime(row).slice(0, 5);
2450
+ const underlyingPrice = params.getUnderlyingPrice(date, time);
2451
+ if (!(underlyingPrice != null && underlyingPrice > 0)) {
2452
+ return { kind: "missingUnderlying" };
2453
+ }
2454
+ const optionPrice = params.getMid(row);
2455
+ if (!(optionPrice > 0) || !(meta.strike > 0)) return { kind: "mathFailed" };
2456
+ const dte = computeFractionalDte(date, time, meta.expiration);
2457
+ if (!(dte >= 0)) return { kind: "mathFailed" };
2458
+ const riskFreeRate = memoizedSofrRate(date) / 100;
2459
+ return {
2460
+ kind: "compute",
2461
+ optionPrice,
2462
+ underlyingPrice,
2463
+ strike: meta.strike,
2464
+ dte,
2465
+ type: meta.contract_type === "call" ? 0 : 1,
2466
+ riskFreeRate
2467
+ };
2468
+ }
2469
+ function writeComputedGreeks(row, greeks, riskFreeRate) {
2470
+ row.delta = greeks.delta;
2471
+ row.gamma = greeks.gamma;
2472
+ row.theta = greeks.theta;
2473
+ row.vega = greeks.vega;
2474
+ row.iv = greeks.iv;
2475
+ row.greeks_source = "computed";
2476
+ row.greeks_revision = OPTION_QUOTE_GREEKS_REVISION;
2477
+ row.rate_type = OPTION_QUOTE_GREEKS_RATE_TYPE;
2478
+ row.rate_value = riskFreeRate;
2479
+ row.gamma_source = OPTION_QUOTE_GREEKS_GAMMA_SOURCE;
2480
+ }
2481
+ function tallySkip(stats, kind) {
2482
+ switch (kind) {
2483
+ case "existing":
2087
2484
  stats.existingGreeksRows++;
2088
- continue;
2089
- }
2090
- const meta = getContractMeta(row);
2091
- if (!meta) {
2485
+ return;
2486
+ case "missingContract":
2092
2487
  stats.missingContractRows++;
2093
2488
  stats.unresolvedRows++;
2489
+ return;
2490
+ case "providerUnresolved":
2491
+ stats.unresolvedRows++;
2492
+ return;
2493
+ case "missingUnderlying":
2494
+ stats.missingUnderlyingRows++;
2495
+ stats.unresolvedRows++;
2496
+ return;
2497
+ case "mathFailed":
2498
+ stats.mathFailedRows++;
2499
+ stats.unresolvedRows++;
2500
+ return;
2501
+ case "compute":
2502
+ return;
2503
+ }
2504
+ }
2505
+ function applyQuoteGreeks(params) {
2506
+ const { rows, mode = "auto", defaultProviderSource } = params;
2507
+ const stats = emptyStats();
2508
+ for (const row of rows) {
2509
+ stats.rowsVisited++;
2510
+ const resolution = resolveQuoteGreeksRow(row, params, defaultProviderSource, mode);
2511
+ if (resolution.kind !== "compute") {
2512
+ tallySkip(stats, resolution.kind);
2094
2513
  continue;
2095
2514
  }
2096
- if (mode === "provider") {
2515
+ const result = computeLegGreeks(
2516
+ resolution.optionPrice,
2517
+ resolution.underlyingPrice,
2518
+ resolution.strike,
2519
+ resolution.dte,
2520
+ resolution.type === 0 ? "C" : "P",
2521
+ resolution.riskFreeRate,
2522
+ OPTION_QUOTE_GREEKS_DIVIDEND_YIELD
2523
+ );
2524
+ if (!hasQuoteGreeks(result)) {
2525
+ stats.mathFailedRows++;
2097
2526
  stats.unresolvedRows++;
2098
2527
  continue;
2099
2528
  }
2100
- const date = getDate(row);
2101
- const time = getTime(row).slice(0, 5);
2102
- const underlyingPrice = getUnderlyingPrice(date, time);
2103
- if (!(underlyingPrice != null && underlyingPrice > 0)) {
2104
- stats.missingUnderlyingRows++;
2105
- stats.unresolvedRows++;
2529
+ writeComputedGreeks(
2530
+ row,
2531
+ {
2532
+ delta: result.delta,
2533
+ gamma: result.gamma,
2534
+ theta: result.theta,
2535
+ vega: result.vega,
2536
+ iv: result.iv
2537
+ },
2538
+ resolution.riskFreeRate
2539
+ );
2540
+ stats.computedRows++;
2541
+ }
2542
+ return stats;
2543
+ }
2544
+ async function applyQuoteGreeksParallel(params) {
2545
+ const { rows, mode = "auto", defaultProviderSource } = params;
2546
+ const stats = emptyStats();
2547
+ const pool = params.pool ?? getSharedIvSolverPool();
2548
+ const n = rows.length;
2549
+ const optionPrice = new Float64Array(n);
2550
+ const underlyingPrice = new Float64Array(n);
2551
+ const strike = new Float64Array(n);
2552
+ const dte = new Float64Array(n);
2553
+ const riskFreeRate = new Float64Array(n);
2554
+ const dividendYield = new Float64Array(n);
2555
+ const type = new Uint8Array(n);
2556
+ const jobRowIndex = new Int32Array(n);
2557
+ let count = 0;
2558
+ for (let i = 0; i < n; i++) {
2559
+ stats.rowsVisited++;
2560
+ const resolution = resolveQuoteGreeksRow(rows[i], params, defaultProviderSource, mode);
2561
+ if (resolution.kind !== "compute") {
2562
+ tallySkip(stats, resolution.kind);
2106
2563
  continue;
2107
2564
  }
2108
- const greeks = computeQuoteGreeks({
2109
- optionPrice: getMid(row),
2110
- underlyingPrice,
2111
- strike: meta.strike,
2112
- date,
2113
- time,
2114
- expiration: meta.expiration,
2115
- contractType: meta.contract_type
2116
- });
2117
- if (!greeks) {
2565
+ optionPrice[count] = resolution.optionPrice;
2566
+ underlyingPrice[count] = resolution.underlyingPrice;
2567
+ strike[count] = resolution.strike;
2568
+ dte[count] = resolution.dte;
2569
+ riskFreeRate[count] = resolution.riskFreeRate;
2570
+ dividendYield[count] = OPTION_QUOTE_GREEKS_DIVIDEND_YIELD;
2571
+ type[count] = resolution.type;
2572
+ jobRowIndex[count] = i;
2573
+ count++;
2574
+ }
2575
+ if (count === 0) return stats;
2576
+ const columns = {
2577
+ count,
2578
+ optionPrice: optionPrice.subarray(0, count),
2579
+ underlyingPrice: underlyingPrice.subarray(0, count),
2580
+ strike: strike.subarray(0, count),
2581
+ dte: dte.subarray(0, count),
2582
+ riskFreeRate: riskFreeRate.subarray(0, count),
2583
+ dividendYield: dividendYield.subarray(0, count),
2584
+ type: type.subarray(0, count)
2585
+ };
2586
+ const result = await pool.solveColumns(columns);
2587
+ for (let j = 0; j < count; j++) {
2588
+ if (result.ok[j] !== 1) {
2118
2589
  stats.mathFailedRows++;
2119
2590
  stats.unresolvedRows++;
2120
2591
  continue;
2121
2592
  }
2122
- row.delta = greeks.delta;
2123
- row.gamma = greeks.gamma;
2124
- row.theta = greeks.theta;
2125
- row.vega = greeks.vega;
2126
- row.iv = greeks.iv;
2127
- row.greeks_source = greeks.greeks_source;
2128
- row.greeks_revision = greeks.greeks_revision;
2129
- row.rate_type = greeks.rate_type;
2130
- row.rate_value = greeks.rate_value;
2131
- row.gamma_source = greeks.gamma_source;
2593
+ writeComputedGreeks(
2594
+ rows[jobRowIndex[j]],
2595
+ {
2596
+ delta: result.delta[j],
2597
+ gamma: result.gamma[j],
2598
+ theta: result.theta[j],
2599
+ vega: result.vega[j],
2600
+ iv: result.iv[j]
2601
+ },
2602
+ riskFreeRate[j]
2603
+ );
2132
2604
  stats.computedRows++;
2133
2605
  }
2134
2606
  return stats;
@@ -2150,19 +2622,19 @@ function joinKey(row) {
2150
2622
  row.timestamp
2151
2623
  ].join("|");
2152
2624
  }
2153
- function isFiniteNumber2(value) {
2625
+ function isFiniteNumber3(value) {
2154
2626
  return typeof value === "number" && Number.isFinite(value);
2155
2627
  }
2156
2628
  function hasProviderFirstOrderGreeks2(row) {
2157
- return isFiniteNumber2(row.delta) && isFiniteNumber2(row.theta) && isFiniteNumber2(row.vega) && isFiniteNumber2(row.iv);
2629
+ return isFiniteNumber3(row.delta) && isFiniteNumber3(row.theta) && isFiniteNumber3(row.vega) && isFiniteNumber3(row.iv);
2158
2630
  }
2159
2631
  function splitTimestamp(timestamp) {
2160
2632
  const [date, time = ""] = timestamp.split(" ");
2161
2633
  return { date, time: time.slice(0, 5) };
2162
2634
  }
2163
2635
  function computeGamma(row, greek) {
2164
- if (!isFiniteNumber2(greek.iv) || !(greek.iv > 0)) return { gamma: null, rateValue: null };
2165
- if (!isFiniteNumber2(greek.underlyingPrice) || !(greek.underlyingPrice > 0)) {
2636
+ if (!isFiniteNumber3(greek.iv) || !(greek.iv > 0)) return { gamma: null, rateValue: null };
2637
+ if (!isFiniteNumber3(greek.underlyingPrice) || !(greek.underlyingPrice > 0)) {
2166
2638
  return { gamma: null, rateValue: null };
2167
2639
  }
2168
2640
  if (!(row.strike > 0)) return { gamma: null, rateValue: null };
@@ -2218,7 +2690,7 @@ function joinThetaQuotesAndFirstOrderGreeks(options) {
2218
2690
  }
2219
2691
  const rows = [];
2220
2692
  for (const quote of options.quotes) {
2221
- if (!isFiniteNumber2(quote.bid) || !isFiniteNumber2(quote.ask)) {
2693
+ if (!isFiniteNumber3(quote.bid) || !isFiniteNumber3(quote.ask)) {
2222
2694
  stats.droppedQuoteRows++;
2223
2695
  continue;
2224
2696
  }
@@ -2257,6 +2729,61 @@ function joinThetaQuotesAndFirstOrderGreeks(options) {
2257
2729
  return { rows, stats };
2258
2730
  }
2259
2731
 
2732
+ // src/utils/providers/thetadata/quote-mid-greeks.ts
2733
+ var OPTION_QUOTE_MID_GREEKS_REVISION = 6;
2734
+ var OPTION_QUOTE_MID_GREEKS_DIVIDEND_YIELD = OPTION_QUOTE_GREEKS_DIVIDEND_YIELD;
2735
+ var OPTION_QUOTE_MID_GREEKS_GAMMA_SOURCE = "computed_thetadata_quote_mid_sofr_q0";
2736
+ function isFiniteNumber4(value) {
2737
+ return typeof value === "number" && Number.isFinite(value);
2738
+ }
2739
+ function splitTimestamp2(timestamp) {
2740
+ const [date, time = ""] = timestamp.split(" ");
2741
+ return { date, time: time.slice(0, 5) };
2742
+ }
2743
+ function computeThetaQuoteMidGreekRow(params) {
2744
+ const { quote, underlyingPrice } = params;
2745
+ if (!isFiniteNumber4(quote.bid) || !isFiniteNumber4(quote.ask)) return null;
2746
+ if (!isFiniteNumber4(underlyingPrice) || !(underlyingPrice > 0)) return null;
2747
+ if (!(quote.strike > 0)) return null;
2748
+ const optionPrice = (quote.bid + quote.ask) / 2;
2749
+ if (!(optionPrice > 0)) return null;
2750
+ const { date, time } = splitTimestamp2(quote.timestamp);
2751
+ const dte = computeFractionalDte(date, time, quote.expiration);
2752
+ if (!(dte > 0)) return null;
2753
+ const rateValue = getSofrRateByKey(date) / 100;
2754
+ const greeks = computeLegGreeks(
2755
+ optionPrice,
2756
+ underlyingPrice,
2757
+ quote.strike,
2758
+ dte,
2759
+ quote.right === "call" ? "C" : "P",
2760
+ rateValue,
2761
+ OPTION_QUOTE_MID_GREEKS_DIVIDEND_YIELD
2762
+ );
2763
+ if (!isFiniteNumber4(greeks.delta) || !isFiniteNumber4(greeks.gamma) || !isFiniteNumber4(greeks.theta) || !isFiniteNumber4(greeks.vega) || !isFiniteNumber4(greeks.iv)) {
2764
+ return null;
2765
+ }
2766
+ return {
2767
+ ticker: buildTicker(quote),
2768
+ timestamp: quote.timestamp,
2769
+ bid: quote.bid,
2770
+ ask: quote.ask,
2771
+ delta: greeks.delta,
2772
+ gamma: greeks.gamma,
2773
+ theta: greeks.theta,
2774
+ // vega is stored per 1% IV move, matching computeLegGreeks' convention and
2775
+ // the rest of the emit path (the per-contract x100 multiplier belongs to the
2776
+ // consumer, not the stored greek).
2777
+ vega: greeks.vega,
2778
+ iv: greeks.iv,
2779
+ greeks_source: "computed",
2780
+ greeks_revision: OPTION_QUOTE_MID_GREEKS_REVISION,
2781
+ rate_type: OPTION_QUOTE_GREEKS_RATE_TYPE,
2782
+ rate_value: rateValue,
2783
+ gamma_source: OPTION_QUOTE_MID_GREEKS_GAMMA_SOURCE
2784
+ };
2785
+ }
2786
+
2260
2787
  // src/utils/providers/thetadata.ts
2261
2788
  var BULK_YIELD_CHUNK = 5e4;
2262
2789
  var BULK_GREEKS_STRIKE_RANGE = 20;
@@ -2704,17 +3231,78 @@ function _resetProvider() {
2704
3231
 
2705
3232
  export {
2706
3233
  BACHELIER_DTE_THRESHOLD,
3234
+ pdf,
3235
+ cdf,
2707
3236
  bsPrice,
3237
+ bsDelta,
3238
+ bsGamma,
3239
+ bsTheta,
3240
+ bsVega,
3241
+ solveIV,
2708
3242
  bachelierPrice,
3243
+ bachelierDelta,
3244
+ bachelierGamma,
3245
+ bachelierTheta,
3246
+ bachelierVega,
3247
+ solveNormalIV,
3248
+ computeLegGreeks,
3249
+ resolveMassiveDataTier,
3250
+ MassiveBarSchema,
3251
+ MassiveAggregateResponseSchema,
3252
+ MassiveQuoteSchema,
3253
+ MassiveQuotesResponseSchema,
3254
+ MassiveSnapshotContractSchema,
3255
+ MassiveSnapshotResponseSchema,
3256
+ MASSIVE_BASE_URL,
3257
+ MASSIVE_MAX_LIMIT,
3258
+ MASSIVE_MAX_PAGES,
3259
+ toMassiveTicker,
3260
+ fromMassiveTicker,
3261
+ massiveTimestampToETDate,
3262
+ massiveTimestampToETTime,
3263
+ nanosToETMinuteKey,
3264
+ MassiveProvider,
2709
3265
  markPrice,
3266
+ findNearestTimestamp,
2710
3267
  parseLegsString,
2711
3268
  buildOccTicker,
2712
3269
  computeStrategyPnlPath,
2713
3270
  computeReplayMfeMae,
2714
- MassiveProvider,
3271
+ ThetaMddsClient,
3272
+ thetaTimestampToEtMinute,
3273
+ decodeThetaResponseData,
3274
+ normalizeThetaFirstOrderGreekRow,
3275
+ normalizeThetaOpenInterestRow,
3276
+ normalizeThetaStockOhlcRow,
3277
+ normalizeThetaStockEodRow,
3278
+ normalizeThetaIndexOhlcRow,
3279
+ normalizeThetaIndexEodRow,
3280
+ optionHistoryQuote,
3281
+ stockHistoryOhlc,
3282
+ stockHistoryEod,
3283
+ indexHistoryOhlc,
3284
+ indexHistoryEod,
3285
+ optionAtTimeQuote,
3286
+ optionHistoryGreeksFirstOrder,
3287
+ optionHistoryGreeksFirstOrderBand,
3288
+ optionHistoryImpliedVolatilityBand,
3289
+ optionHistoryQuoteBand,
3290
+ optionHistoryOpenInterest,
3291
+ optionListContracts,
3292
+ computeFractionalDte,
3293
+ IvSolverPool,
3294
+ getSharedIvSolverPool,
3295
+ destroySharedIvSolverPool,
3296
+ OPTION_QUOTE_GREEKS_REVISION,
3297
+ hasQuoteGreeks,
2715
3298
  applyQuoteGreeks,
3299
+ applyQuoteGreeksParallel,
3300
+ joinThetaQuotesAndFirstOrderGreeks,
3301
+ OPTION_QUOTE_MID_GREEKS_REVISION,
3302
+ OPTION_QUOTE_MID_GREEKS_GAMMA_SOURCE,
3303
+ computeThetaQuoteMidGreekRow,
2716
3304
  ThetaDataProvider,
2717
3305
  getProvider,
2718
3306
  _resetProvider
2719
3307
  };
2720
- //# sourceMappingURL=chunk-JAAQMESY.js.map
3308
+ //# sourceMappingURL=chunk-XXYOUIZY.js.map