tradeblocks-mcp 3.0.0 → 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.
- package/{server/chunk-JAAQMESY.js → dist/chunk-XXYOUIZY.js} +699 -111
- package/dist/chunk-XXYOUIZY.js.map +1 -0
- package/dist/iv-solver-worker.js +295 -0
- package/dist/{market-provider-GGLA7ACC.js → market-provider-VDRJUEF2.js} +2 -2
- package/dist/test-exports.js +10 -2
- package/dist/test-exports.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +4 -2
- package/{dist/chunk-CCITWAAI.js → server/chunk-PNKG7RY7.js} +455 -311
- package/server/chunk-PNKG7RY7.js.map +1 -0
- package/server/index.js +18 -6
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +295 -0
- package/server/{market-provider-D5SI47K7.js → market-provider-VOYYVYWT.js} +2 -2
- package/src/index.ts +20 -3
- package/src/market/ingestor/market-ingestor.ts +2 -2
- package/src/test-exports.ts +8 -0
- package/src/utils/iv-solver-pool.ts +396 -0
- package/src/utils/iv-solver-worker.ts +112 -0
- package/src/utils/option-quote-greeks.ts +246 -51
- package/src/utils/providers/thetadata/decode.ts +16 -10
- package/dist/chunk-CCITWAAI.js.map +0 -1
- package/server/chunk-JAAQMESY.js.map +0 -1
- /package/dist/{market-provider-GGLA7ACC.js.map → market-provider-VDRJUEF2.js.map} +0 -0
- /package/server/{market-provider-D5SI47K7.js.map → market-provider-VOYYVYWT.js.map} +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import {
|
|
3
2
|
getSofrRateByKey
|
|
4
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QTTR7AAW.js";
|
|
5
4
|
import {
|
|
6
5
|
loadMddsProtoRoot
|
|
7
|
-
} from "./chunk-
|
|
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 (
|
|
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
|
|
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-
|
|
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 =
|
|
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
|
|
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
|
|
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" &&
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
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
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
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
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
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
|
|
2625
|
+
function isFiniteNumber3(value) {
|
|
2154
2626
|
return typeof value === "number" && Number.isFinite(value);
|
|
2155
2627
|
}
|
|
2156
2628
|
function hasProviderFirstOrderGreeks2(row) {
|
|
2157
|
-
return
|
|
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 (!
|
|
2165
|
-
if (!
|
|
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 (!
|
|
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
|
-
|
|
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-
|
|
3308
|
+
//# sourceMappingURL=chunk-XXYOUIZY.js.map
|