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.
| 1 | import { defineSource } from '@jaspers-ai/sdk' |
| 2 | import { z } from 'zod' |
| 3 | import { 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 | |
| 8 | const contractType = z.enum(['call', 'put']).optional().describe('call or put. Leave out for both.') |
| 9 | const n = (v: unknown): number | null => (typeof v === 'number' && Number.isFinite(v) ? v : null) |
| 10 | |
| 11 | /** One option snapshot as a flat row. */ |
| 12 | export 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 | |
| 44 | const 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 | |
| 93 | const 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 | |
| 138 | const 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 | |
| 154 | export const optionSources = { |
| 155 | 'option-contracts': optionContracts, |
| 156 | 'option-chain': optionChain, |
| 157 | 'option-contract-snapshot': optionContractSnapshot, |
| 158 | } |