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.
- 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 +3 -3
- 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/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,9 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import {
|
|
2
3
|
getSofrRateByKey
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-72GKJE2U.js";
|
|
4
5
|
import {
|
|
5
6
|
loadMddsProtoRoot
|
|
6
|
-
} from "./chunk-
|
|
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 (
|
|
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
|
|
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-
|
|
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 =
|
|
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
|
|
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
|
|
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" &&
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
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
|
-
|
|
2174
|
-
|
|
2175
|
-
const meta = getContractMeta(row);
|
|
2176
|
-
if (!meta) {
|
|
2393
|
+
return;
|
|
2394
|
+
case "missingContract":
|
|
2177
2395
|
stats.missingContractRows++;
|
|
2178
2396
|
stats.unresolvedRows++;
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
if (mode === "provider") {
|
|
2397
|
+
return;
|
|
2398
|
+
case "providerUnresolved":
|
|
2182
2399
|
stats.unresolvedRows++;
|
|
2183
|
-
|
|
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
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
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
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
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
|
|
2494
|
+
function isFiniteNumber3(value) {
|
|
2239
2495
|
return typeof value === "number" && Number.isFinite(value);
|
|
2240
2496
|
}
|
|
2241
2497
|
function hasProviderFirstOrderGreeks2(row) {
|
|
2242
|
-
return
|
|
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 (!
|
|
2250
|
-
if (!
|
|
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 (!
|
|
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
|
-
|
|
2885
|
-
|
|
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-
|
|
3061
|
+
//# sourceMappingURL=chunk-PNKG7RY7.js.map
|