tradeblocks-mcp 2.2.0-beta.7 → 2.2.0

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.
@@ -1740,6 +1740,21 @@ var MassiveAggregateResponseSchema = z.object({
1740
1740
  request_id: z.string(),
1741
1741
  next_url: z.string().optional()
1742
1742
  });
1743
+ var MassiveQuoteSchema = z.object({
1744
+ bid_price: z.number(),
1745
+ ask_price: z.number(),
1746
+ sip_timestamp: z.number(),
1747
+ // nanoseconds
1748
+ bid_size: z.number(),
1749
+ ask_size: z.number(),
1750
+ sequence_number: z.number()
1751
+ });
1752
+ var MassiveQuotesResponseSchema = z.object({
1753
+ status: z.string(),
1754
+ request_id: z.string(),
1755
+ results: z.array(MassiveQuoteSchema).default([]),
1756
+ next_url: z.string().optional()
1757
+ });
1743
1758
  var MassiveSnapshotGreeksSchema = z.object({
1744
1759
  delta: z.number(),
1745
1760
  gamma: z.number(),
@@ -1830,6 +1845,12 @@ function massiveTimestampToETTime(unixMs) {
1830
1845
  hour12: false
1831
1846
  });
1832
1847
  }
1848
+ function nanosToETMinuteKey(nanosTimestamp) {
1849
+ const ms = Math.floor(nanosTimestamp / 1e6);
1850
+ const date = massiveTimestampToETDate(ms);
1851
+ const time = massiveTimestampToETTime(ms);
1852
+ return `${date} ${time}`;
1853
+ }
1833
1854
  function getApiKey() {
1834
1855
  const key = process.env.MASSIVE_API_KEY;
1835
1856
  if (!key) {
@@ -2030,8 +2051,86 @@ var MassiveProvider = class {
2030
2051
  url = null;
2031
2052
  }
2032
2053
  }
2054
+ const quotesEnabled2 = process.env.MASSIVE_QUOTES_ENABLED === "true" || process.env.MASSIVE_QUOTES_ENABLED === "1";
2055
+ if (quotesEnabled2 && assetClass === "option" && timespan !== "day" && allRows.length > 0) {
2056
+ const quotesMap = await this.fetchQuotesForBars(apiTicker, headers, from, to);
2057
+ if (quotesMap.size > 0) {
2058
+ for (const row of allRows) {
2059
+ if (row.time != null) {
2060
+ const key = `${row.date} ${row.time}`;
2061
+ const quote = quotesMap.get(key);
2062
+ if (quote != null) {
2063
+ row.bid = quote.bid;
2064
+ row.ask = quote.ask;
2065
+ }
2066
+ }
2067
+ }
2068
+ }
2069
+ }
2033
2070
  return allRows;
2034
2071
  }
2072
+ /**
2073
+ * Fetches historical quotes (bid/ask) for an option ticker over a date range.
2074
+ * Returns a Map keyed by "YYYY-MM-DD HH:MM" ET minute key.
2075
+ * Any error (network, HTTP error, parse failure) silently returns an empty Map.
2076
+ */
2077
+ async fetchQuotesForBars(apiTicker, headers, from, to) {
2078
+ const result = /* @__PURE__ */ new Map();
2079
+ try {
2080
+ let url = `${MASSIVE_BASE_URL}/v3/quotes/${encodeURIComponent(apiTicker)}?timestamp.gte=${from}&timestamp.lte=${to}&order=asc&limit=${MASSIVE_MAX_LIMIT}`;
2081
+ const seenCursors = /* @__PURE__ */ new Set();
2082
+ const QUOTES_MAX_PAGES = 100;
2083
+ let pageCount = 0;
2084
+ while (url) {
2085
+ pageCount++;
2086
+ if (pageCount > QUOTES_MAX_PAGES) {
2087
+ break;
2088
+ }
2089
+ let response;
2090
+ try {
2091
+ response = await fetch(url, {
2092
+ headers,
2093
+ signal: AbortSignal.timeout(3e4)
2094
+ });
2095
+ } catch {
2096
+ return result;
2097
+ }
2098
+ if (!response.ok) {
2099
+ return result;
2100
+ }
2101
+ const json = await response.json();
2102
+ const parsed = MassiveQuotesResponseSchema.safeParse(json);
2103
+ if (!parsed.success) {
2104
+ return result;
2105
+ }
2106
+ const data = parsed.data;
2107
+ for (const quote of data.results) {
2108
+ const key = nanosToETMinuteKey(quote.sip_timestamp);
2109
+ result.set(key, { bid: quote.bid_price, ask: quote.ask_price });
2110
+ }
2111
+ if (data.next_url) {
2112
+ const nextUrlObj = new URL(data.next_url);
2113
+ const cursor = nextUrlObj.searchParams.get("cursor") ?? data.next_url;
2114
+ if (seenCursors.has(cursor)) {
2115
+ break;
2116
+ }
2117
+ seenCursors.add(cursor);
2118
+ url = data.next_url;
2119
+ } else {
2120
+ url = null;
2121
+ }
2122
+ }
2123
+ } catch {
2124
+ return /* @__PURE__ */ new Map();
2125
+ }
2126
+ return result;
2127
+ }
2128
+ async fetchQuotes(ticker, from, to) {
2129
+ const apiKey = getApiKey();
2130
+ const apiTicker = toMassiveTicker(ticker, "option");
2131
+ const headers = { Authorization: `Bearer ${apiKey}` };
2132
+ return this.fetchQuotesForBars(apiTicker, headers, from, to);
2133
+ }
2035
2134
  async fetchOptionSnapshot(options) {
2036
2135
  const apiKey = getApiKey();
2037
2136
  const { underlying } = options;
@@ -4317,6 +4416,9 @@ function computeReplayMfeMae(pnlPath) {
4317
4416
  import { z as z5 } from "zod";
4318
4417
 
4319
4418
  // src/utils/bar-cache.ts
4419
+ function quotesEnabled() {
4420
+ return process.env.MASSIVE_QUOTES_ENABLED === "true" || process.env.MASSIVE_QUOTES_ENABLED === "1";
4421
+ }
4320
4422
  async function fetchBarsWithCache(opts) {
4321
4423
  const { ticker, from, to, timespan, assetClass, baseDir } = opts;
4322
4424
  try {
@@ -4332,7 +4434,7 @@ async function fetchBarsWithCache(opts) {
4332
4434
  );
4333
4435
  const rows = cached.getRows();
4334
4436
  if (rows.length > 0) {
4335
- return rows.map((row) => ({
4437
+ const bars2 = rows.map((row) => ({
4336
4438
  open: Number(row[0]),
4337
4439
  high: Number(row[1]),
4338
4440
  low: Number(row[2]),
@@ -4345,6 +4447,44 @@ async function fetchBarsWithCache(opts) {
4345
4447
  volume: 0
4346
4448
  // market.intraday has no volume column
4347
4449
  }));
4450
+ const missingQuotes = assetClass === "option" && quotesEnabled() && bars2.some((b) => b.bid == null && b.ask == null);
4451
+ if (missingQuotes) {
4452
+ try {
4453
+ const provider = getProvider();
4454
+ if (provider.fetchQuotes) {
4455
+ const quotesMap = await provider.fetchQuotes(ticker, from, to);
4456
+ if (quotesMap.size > 0) {
4457
+ const updates = [];
4458
+ for (const bar of bars2) {
4459
+ if (bar.time != null) {
4460
+ const key = `${bar.date} ${bar.time}`;
4461
+ const quote = quotesMap.get(key);
4462
+ if (quote != null) {
4463
+ bar.bid = quote.bid;
4464
+ bar.ask = quote.ask;
4465
+ updates.push(
4466
+ `('${escaped}', '${bar.date}', '${bar.time}', ${quote.bid}, ${quote.ask})`
4467
+ );
4468
+ }
4469
+ }
4470
+ }
4471
+ if (updates.length > 0) {
4472
+ const updateConn = opts.conn ?? await getConnection(baseDir ?? ".");
4473
+ for (let i = 0; i < updates.length; i += 500) {
4474
+ const chunk = updates.slice(i, i + 500);
4475
+ await updateConn.run(
4476
+ `UPDATE market.intraday AS m SET bid = v.bid, ask = v.ask
4477
+ FROM (VALUES ${chunk.join(", ")}) AS v(ticker, date, time, bid, ask)
4478
+ WHERE m.ticker = v.ticker AND m.date = v.date AND m.time = v.time`
4479
+ );
4480
+ }
4481
+ }
4482
+ }
4483
+ }
4484
+ } catch {
4485
+ }
4486
+ }
4487
+ return bars2;
4348
4488
  }
4349
4489
  } catch {
4350
4490
  }
@@ -6266,6 +6406,8 @@ export {
6266
6406
  MassiveAggregateResponseSchema,
6267
6407
  MassiveBarSchema,
6268
6408
  MassiveProvider,
6409
+ MassiveQuoteSchema,
6410
+ MassiveQuotesResponseSchema,
6269
6411
  MassiveSnapshotContractSchema,
6270
6412
  MassiveSnapshotResponseSchema,
6271
6413
  OPEN_KNOWN_FIELDS,
@@ -6365,6 +6507,7 @@ export {
6365
6507
  markPrice,
6366
6508
  massiveTimestampToETDate,
6367
6509
  massiveTimestampToETTime,
6510
+ nanosToETMinuteKey,
6368
6511
  parseLegsString,
6369
6512
  pdf,
6370
6513
  performTailRiskAnalysis,