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.

2.8 KB
1import { defineSource } from '@jaspers-ai/sdk'
2import { z } from 'zod'
3import { HOSTS, baseField, list, upper } from './client'
4
5// Ticker news, newest first, with the per-ticker sentiment insights Massive attaches.
6
7export const news = defineSource({
8 description:
9 'News articles, newest first: published (ISO), title, publisher, author, url, summary, tickers, keywords, and sentiment insights (ticker: sentiment, reasoning). Pass one or more tickers, or none for all news.',
10 hosts: HOSTS,
11 input: z.object({
12 tickers: z.array(z.string()).max(20).default([]).describe('Tickers whose news to read, like AAPL. Empty for all news.'),
13 from: z.string().optional().describe('Earliest publish time, YYYY-MM-DD or ISO date-time (published_utc.gte).'),
14 to: z.string().optional().describe('Latest publish time, YYYY-MM-DD or ISO date-time (published_utc.lte).'),
15 limit: z.number().int().min(1).max(1000).default(50).describe('Most articles to answer, across all tickers.'),
16 base: baseField,
17 }),
18 async run(args, ctx) {
19 const tickers = args.tickers.map(upper).filter(Boolean)
20 const per = Math.min(args.limit, 1000)
21 const read = (ticker?: string) =>
22 list(
23 ctx,
24 args.base,
25 '/v2/reference/news',
26 { ticker, 'published_utc.gte': args.from, 'published_utc.lte': args.to, order: 'desc', sort: 'published_utc', limit: per },
27 args.limit,
28 )
29 const results = await Promise.all(tickers.length ? tickers.map((t) => read(t)) : [read()])
30 const seen = new Set<string>()
31 const articles: any[] = []
32 for (const r of results)
33 for (const a of r.rows) {
34 const key = String(a.id ?? a.article_url)
35 if (!seen.has(key)) {
36 seen.add(key)
37 articles.push(a)
38 }
39 }
40 articles.sort((a, b) => String(b.published_utc).localeCompare(String(a.published_utc)))
41 const rows = articles.slice(0, args.limit).map((a) => {
42 const insights = (Array.isArray(a.insights) ? a.insights : []) as any[]
43 const mine = insights.find((i) => tickers.includes(String(i.ticker))) ?? insights[0]
44 return {
45 id: a.id ?? null,
46 published: a.published_utc ?? null,
47 title: a.title ?? null,
48 publisher: a.publisher?.name ?? null,
49 author: a.author ?? null,
50 url: a.article_url ?? null,
51 summary: a.description ?? null,
52 image_url: a.image_url ?? null,
53 tickers: Array.isArray(a.tickers) ? a.tickers.join(', ') : null,
54 keywords: Array.isArray(a.keywords) ? a.keywords.join(', ') : null,
55 sentiment: mine?.sentiment ?? null,
56 sentiment_ticker: mine?.ticker ?? null,
57 sentiment_reasoning: mine?.sentiment_reasoning ?? null,
58 insights: insights.map((i) => `${i.ticker}: ${i.sentiment}`).join(', ') || null,
59 }
60 })
61 return { rows, more: results.some((r) => r.more) }
62 },
63})