Jaspers Hub

biseroff / massive

pluginReviewed★ 0↓ 3

Massive Market Data (Polygon.io): Market data plugin wrapping the Polygon.io/Massive REST API: bars, snapshots, options, news, financials, indicators, and four chart/watchlist views.

6.4 KB
1import { defineSource } from '@jaspers-ai/sdk'
2import { z } from 'zod'
3import { HOSTS, baseField, get, iso, list, upper, type Row } from './client'
4
5// Snapshots: stock tickers, gainers and losers, the universal snapshot, last trade and last quote.
6
7const n = (v: unknown): number | null => (typeof v === 'number' && Number.isFinite(v) ? v : null)
8/** The first number that is there and not zero (Massive zeroes the day before the open). */
9const firstNonZero = (...vs: unknown[]): number | null => {
10 for (const v of vs) if (typeof v === 'number' && v !== 0 && Number.isFinite(v)) return v
11 return null
12}
13
14/** One v2 snapshot (stocks, forex, crypto) as a flat row. */
15export function snapRow(s: any): Row {
16 const day = s?.day ?? {}
17 const prev = s?.prevDay ?? {}
18 const lt = s?.lastTrade ?? {}
19 const lq = s?.lastQuote ?? {}
20 const min = s?.min ?? {}
21 const bid = n(lq.p ?? lq.b)
22 const ask = n(lq.P ?? lq.a)
23 const mid = bid !== null && ask !== null && bid > 0 && ask > 0 ? (bid + ask) / 2 : null
24 return {
25 ticker: s?.ticker ?? null,
26 last: firstNonZero(lt.p, min.c, mid, day.c, prev.c),
27 change: n(s?.todaysChange),
28 changePercent: n(s?.todaysChangePerc),
29 open: n(day.o),
30 high: n(day.h),
31 low: n(day.l),
32 close: n(day.c),
33 volume: n(day.v),
34 vwap: n(day.vw),
35 prevClose: n(prev.c),
36 bid,
37 ask,
38 updated: iso(s?.updated),
39 }
40}
41
42function universalRow(r: any): Row {
43 const s = r.session ?? {}
44 const lq = r.last_quote ?? {}
45 const g = r.greeks ?? {}
46 return {
47 ticker: r.ticker ?? null,
48 type: r.type ?? null,
49 name: r.name ?? null,
50 market_status: r.market_status ?? null,
51 price: firstNonZero(r.value, r.last_trade?.price, s.price, s.close, s.previous_close),
52 change: n(s.change),
53 changePercent: n(s.change_percent),
54 open: n(s.open),
55 high: n(s.high),
56 low: n(s.low),
57 close: n(s.close),
58 previousClose: n(s.previous_close),
59 volume: n(s.volume),
60 bid: n(lq.bid),
61 ask: n(lq.ask),
62 implied_volatility: n(r.implied_volatility),
63 open_interest: n(r.open_interest),
64 delta: n(g.delta),
65 gamma: n(g.gamma),
66 theta: n(g.theta),
67 vega: n(g.vega),
68 underlying: r.underlying_asset?.ticker ?? null,
69 updated: iso(r.last_updated ?? s.last_updated),
70 error: r.error ? `${r.error}: ${r.message ?? ''}`.trim() : null,
71 }
72}
73
74const snapshot = defineSource({
75 description:
76 'Stock snapshots: ticker, last price, change, change %, day open/high/low/close/volume/vwap, previous close, bid, ask, updated. Pass tickers, or none for every US stock (largest volume first).',
77 hosts: HOSTS,
78 input: z.object({
79 tickers: z.array(z.string()).max(250).default([]).describe('Stock tickers like AAPL. Empty for all.'),
80 include_otc: z.boolean().default(false).describe('Include OTC securities when listing all.'),
81 limit: z.number().int().min(1).max(15000).default(500).describe('Most rows to answer.'),
82 base: baseField,
83 }),
84 async run(args, ctx) {
85 const tickers = args.tickers.map(upper).filter(Boolean)
86 const path = '/v2/snapshot/locale/us/markets/stocks/tickers'
87 if (tickers.length === 1) {
88 const body = await get(ctx, args.base, `${path}/${tickers[0]}`)
89 return body?.ticker ? [snapRow(body.ticker)] : []
90 }
91 const body = await get(ctx, args.base, path, {
92 tickers: tickers.length ? tickers.join(',') : undefined,
93 include_otc: args.include_otc,
94 })
95 const rows = ((body?.tickers ?? []) as any[]).map(snapRow)
96 if (tickers.length) {
97 const order = new Map(tickers.map((t, i) => [t, i]))
98 rows.sort((a, b) => (order.get(String(a.ticker)) ?? 1e9) - (order.get(String(b.ticker)) ?? 1e9))
99 } else rows.sort((a, b) => Number(b.volume ?? 0) - Number(a.volume ?? 0))
100 return rows.slice(0, args.limit)
101 },
102})
103
104const gainersLosers = defineSource({
105 description: "The day's top 20 US stock gainers or losers by percent change, flattened like snapshot.",
106 hosts: HOSTS,
107 input: z.object({
108 direction: z.enum(['gainers', 'losers']).default('gainers').describe('gainers or losers.'),
109 include_otc: z.boolean().default(false).describe('Include OTC securities.'),
110 base: baseField,
111 }),
112 async run(args, ctx) {
113 const body = await get(ctx, args.base, `/v2/snapshot/locale/us/markets/stocks/${args.direction}`, { include_otc: args.include_otc })
114 return ((body?.tickers ?? []) as any[]).map(snapRow)
115 },
116})
117
118const universalSnapshot = defineSource({
119 description:
120 'Snapshots across asset classes in one call (stocks, options O:, indices I:, forex C:, crypto X:): ticker, type, name, price, change, change %, session OHLC, volume, bid, ask, greeks for options.',
121 hosts: HOSTS,
122 input: z.object({
123 tickers: z.array(z.string()).min(1).max(250).describe('Tickers of any asset class, like AAPL, I:SPX, X:BTCUSD.'),
124 base: baseField,
125 }),
126 async run(args, ctx) {
127 const tickers = args.tickers.map((t) => t.trim()).filter(Boolean)
128 const res = await list(ctx, args.base, '/v3/snapshot', { 'ticker.any_of': tickers.join(','), limit: 250 }, 250)
129 return res.rows.map(universalRow)
130 },
131})
132
133const lastTrade = defineSource({
134 description: 'The most recent trade for a stock or option ticker: price, size, exchange, conditions, time.',
135 hosts: HOSTS,
136 input: z.object({ ticker: z.string().min(1).describe('A stock or option ticker, like AAPL.'), base: baseField }),
137 async run(args, ctx) {
138 const t = upper(args.ticker)
139 const r = (await get(ctx, args.base, `/v2/last/trade/${t}`))?.results ?? {}
140 return [
141 {
142 ticker: r.T ?? t,
143 time: iso(r.t),
144 price: n(r.p),
145 size: n(r.s),
146 exchange: r.x ?? null,
147 conditions: Array.isArray(r.c) ? r.c.join(', ') : null,
148 tape: r.z ?? null,
149 },
150 ]
151 },
152})
153
154const lastQuote = defineSource({
155 description: 'The most recent NBBO quote for a stock: bid, bid size, ask, ask size, exchanges, time.',
156 hosts: HOSTS,
157 input: z.object({ ticker: z.string().min(1).describe('A stock ticker, like AAPL.'), base: baseField }),
158 async run(args, ctx) {
159 const t = upper(args.ticker)
160 const r = (await get(ctx, args.base, `/v2/last/nbbo/${t}`))?.results ?? {}
161 return [
162 {
163 ticker: r.T ?? t,
164 time: iso(r.t),
165 bid: n(r.p),
166 bid_size: n(r.s),
167 bid_exchange: r.x ?? null,
168 ask: n(r.P),
169 ask_size: n(r.S),
170 ask_exchange: r.X ?? null,
171 },
172 ]
173 },
174})
175
176export const snapSources = {
177 snapshot,
178 'gainers-losers': gainersLosers,
179 'universal-snapshot': universalSnapshot,
180 'last-trade': lastTrade,
181 'last-quote': lastQuote,
182}