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 KB
1import { defineSource } from '@jaspers-ai/sdk'
2import { z } from 'zod'
3import { HOSTS, baseField, get, iso, list, upper, type Row } from './client'
4
5// Options: contracts reference, the chain snapshot for an underlying, one contract's snapshot.
6// Option bars, trades, and quotes are the aggregates, trades, and quotes sources with O: tickers.
7
8const contractType = z.enum(['call', 'put']).optional().describe('call or put. Leave out for both.')
9const n = (v: unknown): number | null => (typeof v === 'number' && Number.isFinite(v) ? v : null)
10
11/** One option snapshot as a flat row. */
12export function optionRow(r: any): Row {
13 const d = r?.details ?? {}
14 const q = r?.last_quote ?? {}
15 const t = r?.last_trade ?? {}
16 const g = r?.greeks ?? {}
17 const day = r?.day ?? {}
18 const u = r?.underlying_asset ?? {}
19 return {
20 contract: d.ticker ?? r?.ticker ?? null,
21 type: d.contract_type ?? null,
22 strike: n(d.strike_price),
23 expiration: d.expiration_date ?? null,
24 bid: n(q.bid),
25 ask: n(q.ask),
26 mid: n(q.midpoint),
27 last: n(t.price) ?? n(day.close),
28 volume: n(day.volume),
29 open_interest: n(r?.open_interest),
30 iv: n(r?.implied_volatility),
31 delta: n(g.delta),
32 gamma: n(g.gamma),
33 theta: n(g.theta),
34 vega: n(g.vega),
35 break_even: n(r?.break_even_price),
36 change: n(day.change),
37 change_percent: n(day.change_percent),
38 underlying: u.ticker ?? null,
39 underlying_price: n(u.price),
40 updated: iso(q.last_updated ?? t.sip_timestamp ?? day.last_updated),
41 }
42}
43
44const optionContracts = defineSource({
45 description: 'Option contracts for an underlying: ticker, type, strike, expiration, exercise style, shares per contract. Nearest expiration first.',
46 hosts: HOSTS,
47 input: z.object({
48 underlying: z.string().min(1).describe('The underlying ticker, like SPY.'),
49 expiration_from: z.string().optional().describe('Earliest expiration, YYYY-MM-DD.'),
50 expiration_to: z.string().optional().describe('Latest expiration, YYYY-MM-DD.'),
51 contract_type: contractType,
52 strike_min: z.number().optional().describe('Lowest strike.'),
53 strike_max: z.number().optional().describe('Highest strike.'),
54 as_of: z.string().optional().describe('Contracts as they were on this date, YYYY-MM-DD. Leave out for today.'),
55 expired: z.boolean().default(false).describe('Include expired contracts.'),
56 limit: z.number().int().min(1).max(10000).default(1000).describe('Most contracts to answer.'),
57 base: baseField,
58 }),
59 async run(args, ctx) {
60 const res = await list(
61 ctx,
62 args.base,
63 '/v3/reference/options/contracts',
64 {
65 underlying_ticker: upper(args.underlying),
66 'expiration_date.gte': args.expiration_from,
67 'expiration_date.lte': args.expiration_to,
68 contract_type: args.contract_type,
69 'strike_price.gte': args.strike_min,
70 'strike_price.lte': args.strike_max,
71 as_of: args.as_of,
72 expired: args.expired,
73 sort: 'expiration_date',
74 order: 'asc',
75 limit: Math.min(args.limit, 1000),
76 },
77 args.limit,
78 )
79 const rows = res.rows.map((r) => ({
80 contract: r.ticker,
81 underlying: r.underlying_ticker ?? null,
82 type: r.contract_type ?? null,
83 strike: r.strike_price ?? null,
84 expiration: r.expiration_date ?? null,
85 exercise_style: r.exercise_style ?? null,
86 shares_per_contract: r.shares_per_contract ?? null,
87 primary_exchange: r.primary_exchange ?? null,
88 }))
89 return { rows, more: res.more }
90 },
91})
92
93const optionChain = defineSource({
94 description:
95 'The option chain snapshot for an underlying: contract, type, strike, expiration, bid, ask, mid, last, volume, open interest, IV, delta, gamma, theta, vega, break-even, underlying price. Sorted by expiration then strike.',
96 hosts: HOSTS,
97 input: z.object({
98 underlying: z.string().min(1).describe('The underlying ticker, like SPY.'),
99 expiration: z.string().optional().describe('One expiration, YYYY-MM-DD.'),
100 expiration_from: z.string().optional().describe('Earliest expiration, YYYY-MM-DD.'),
101 expiration_to: z.string().optional().describe('Latest expiration, YYYY-MM-DD.'),
102 contract_type: contractType,
103 strike_min: z.number().optional().describe('Lowest strike.'),
104 strike_max: z.number().optional().describe('Highest strike.'),
105 limit: z.number().int().min(1).max(5000).default(500).describe('Most contracts to answer.'),
106 base: baseField,
107 }),
108 async run(args, ctx) {
109 const res = await list(
110 ctx,
111 args.base,
112 `/v3/snapshot/options/${upper(args.underlying)}`,
113 {
114 expiration_date: args.expiration,
115 'expiration_date.gte': args.expiration_from,
116 'expiration_date.lte': args.expiration_to,
117 contract_type: args.contract_type,
118 'strike_price.gte': args.strike_min,
119 'strike_price.lte': args.strike_max,
120 sort: 'expiration_date',
121 order: 'asc',
122 limit: Math.min(args.limit, 250),
123 },
124 args.limit,
125 20,
126 )
127 const rows = res.rows.map(optionRow)
128 rows.sort(
129 (a, b) =>
130 String(a.expiration).localeCompare(String(b.expiration)) ||
131 Number(a.strike) - Number(b.strike) ||
132 String(a.type).localeCompare(String(b.type)),
133 )
134 return { rows, more: res.more, pages: res.pages }
135 },
136})
137
138const optionContractSnapshot = defineSource({
139 description: 'The snapshot of one option contract, with the same fields as option-chain. One row.',
140 hosts: HOSTS,
141 input: z.object({
142 underlying: z.string().min(1).describe('The underlying ticker, like SPY.'),
143 contract: z.string().min(1).describe('The option ticker, like O:SPY251219C00600000.'),
144 base: baseField,
145 }),
146 async run(args, ctx) {
147 const c = upper(args.contract)
148 const contract = c.startsWith('O:') ? c : `O:${c}`
149 const body = await get(ctx, args.base, `/v3/snapshot/options/${upper(args.underlying)}/${contract}`)
150 return body?.results ? [optionRow(body.results)] : []
151 },
152})
153
154export const optionSources = {
155 'option-contracts': optionContracts,
156 'option-chain': optionChain,
157 'option-contract-snapshot': optionContractSnapshot,
158}