From 907e6e8aac7967dd4ba71089b194655e30aba2c3 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Thu, 22 Jun 2023 12:18:08 -0400 Subject: [PATCH 1/7] feat: add polygon.io service --- src/services/index.ts | 1 + src/services/polygon.ts | 337 ++++++++++++++++++++++++++++++++++ test/services/polygon.test.ts | 47 +++++ 3 files changed, 385 insertions(+) create mode 100644 src/services/polygon.ts create mode 100644 test/services/polygon.test.ts diff --git a/src/services/index.ts b/src/services/index.ts index 9d0610a0..88569671 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -2,6 +2,7 @@ export * from './bing-web-search' export * from './diffbot' export * from './metaphor' export * from './novu' +export * from './polygon' export * from './serpapi' export * from './slack' export * from './twilio-conversation' diff --git a/src/services/polygon.ts b/src/services/polygon.ts new file mode 100644 index 00000000..7143f041 --- /dev/null +++ b/src/services/polygon.ts @@ -0,0 +1,337 @@ +import defaultKy from 'ky' + +export const POLYGON_API_BASE_URL = 'https://api.polygon.io' + +/** + * Ticker Details v3 input parameters. + * + * @see {@link https://polygon.io/docs/stocks/get_v3_reference_tickers__ticker} + */ +export type TickerDetailsInput = { + /** + * The ticker symbol of the asset. + */ + ticker: string + + /** + * Specify a point in time to get information about the ticker available on that date (formatted as YYYY-MM-DD). + */ + date?: string +} + +/** + * Daily Open/Close input parameters. + * + * @see {@link https://polygon.io/docs/stocks/get_v1_open-close__stocksticker___date} + */ +export type DailyOpenCloseInput = { + /** + * The ticker symbol of the stock/equity. + */ + ticker: string + + /** + * The date of the requested open/close in the format YYYY-MM-DD. + */ + date: string + + /** + * Whether or not the results are adjusted for splits. By default, results are adjusted. + */ + adjusted?: boolean +} + +/** + * Result returned by the Daily Open/Close API. + */ +export interface DailyOpenCloseOutput { + /** The close price of the ticker symbol in after-hours trading. */ + afterHours: number + + /** The close price for the symbol in the given time period. */ + close: number + + /** The requested date. */ + from: string + + /** The highest price for the symbol in the given time period. */ + high: number + + /** The lowest price for the symbol in the given time period. */ + low: number + + /** The open price for the symbol in the given time period. */ + open: number + + /** The open price of the ticker symbol in pre-market trading. */ + preMarket: number + + /** The status of this request's response. */ + status: string + + /** The exchange symbol that this item is traded under. */ + symbol: string + + /** The trading volume of the symbol in the given time period. */ + volume: number +} + +/** + * Result returned by the Previous Close API. + */ +export interface PreviousCloseOutput { + /** Whether or not this response was adjusted for splits. */ + adjusted: boolean + + /** The number of aggregates (minute or day) used to generate the response. */ + queryCount: number + + /** A request id assigned by the server. */ + requestId: string + + /** Array of results, each containing details for the symbol in the given time period. */ + results: { + /** The exchange symbol that this item is traded under. */ + T: string + + /** The close price for the symbol in the given time period. */ + c: number + + /** The highest price for the symbol in the given time period. */ + h: number + + /** The lowest price for the symbol in the given time period. */ + l: number + + /** The open price for the symbol in the given time period. */ + o: number + + /** The Unix Msec timestamp for the start of the aggregate window. */ + t: number + + /** The trading volume of the symbol in the given time period. */ + v: number + + /** The volume weighted average price. */ + vw: number + }[] + + /** The total number of results for this request. */ + resultsCount: number + + /** The status of this request's response. */ + status: string + + /** The exchange symbol that this item is traded under. */ + ticker: string +} + +/** + * Result returned by the Ticker Details v3 API. + */ +export interface TickerDetailsOutput { + /** A request id assigned by the server. */ + requestId: string + + /** Detailed results for the specific ticker. */ + results: { + /** Whether the ticker is actively traded. */ + active: boolean + + /** Address of the company. */ + address: { + /** The first line of the company's headquarters address. */ + address1: string + + /** The city of the company's headquarters address. */ + city: string + + /** The postal code of the company's headquarters address. */ + postalCode: string + + /** The state of the company's headquarters address. */ + state: string + } + + /** Branding details of the company. */ + branding: { + /** A link to this ticker's company's icon. Icon's are generally smaller, square images that represent the company at a glance. */ + iconUrl: string + + /** A link to this ticker's company's logo. Note that you must provide an API key when accessing this URL. See the "Authentication" section at the top of this page for more details. */ + logoUrl: string + } + + /** Central Index Key (CIK) of the company. */ + cik: string + + /** Composite Financial Instrument Global Identifier (FIGI). */ + compositeFigi: string + + /** Name of the currency in which the company trades. */ + currencyName: string + + /** Date and time the company was delisted, if applicable. */ + delistedUtc?: string + + /** Description of the company. */ + description: string + + /** The company's homepage URL. */ + homepageUrl: string + + /** The date when the company was listed. */ + listDate: string + + /** Locale of the company. */ + locale: string + + /** Market in which the company trades. */ + market: string + + /** Market capitalization of the company. */ + marketCap: number + + /** Name of the company. */ + name: string + + /** Phone number of the company. */ + phoneNumber: string + + /** The primary exchange on which the company trades. */ + primaryExchange: string + + /** Round lot size for the company's stock. */ + roundLot: number + + /** Share class FIGI. */ + shareClassFigi: string + + /** The number of outstanding shares for the share class. */ + shareClassSharesOutstanding: number + + /** The Standard Industrial Classification (SIC) code of the company. */ + sicCode: string + + /** Description of the SIC code. */ + sicDescription: string + + /** The ticker symbol of the company. */ + ticker: string + + /** The root of the ticker symbol. */ + tickerRoot: string + + /** The suffix of the ticker symbol, if applicable. */ + tickerSuffix?: string + + /** The total number of employees in the company. */ + totalEmployees: number + + /** The type of the ticker (e.g., common stock, preferred stock, etc.). */ + type: string + + /** The number of weighted outstanding shares. */ + weightedSharesOutstanding: number + } + + /** The status of this request's response. */ + status: string +} + +export class PolygonClient { + /** + * HTTP client for the Polygon API. + */ + readonly api: typeof defaultKy + + /** + * Polygon API key. + */ + readonly apiKey: string + + /** + * Polygon API base URL. + */ + readonly apiBaseUrl: string + + constructor({ + apiKey = process.env.POLYGON_API_KEY, + apiBaseUrl = POLYGON_API_BASE_URL, + ky = defaultKy + }: { + apiKey?: string + apiBaseUrl?: string + ky?: typeof defaultKy + } = {}) { + if (!apiKey) { + throw new Error(`Error PolygonClient missing required "apiKey"`) + } + + this.apiKey = apiKey + this.apiBaseUrl = apiBaseUrl + + this.api = ky.extend({ + prefixUrl: this.apiBaseUrl, + headers: { + Authorization: `Bearer ${this.apiKey}` + } + }) + } + + /** + * Returns detailed information about a single ticker. + * + * @param params - input parameters (`ticker` symbol and optional `date`) + * @returns promise that resolves to detailed information about a single ticker + */ + async getTickerDetails(params: TickerDetailsInput) { + let searchParams + if (params.date) { + searchParams = { + date: params.date + } + } + + return this.api + .get(`v3/reference/tickers/${params.ticker}`, { + searchParams + }) + .json() + } + + /** + * Returns the open, close and after hours prices of a stock symbol on a certain date. + * + * @param params - input parameters (`ticker` symbol and `date`) + * @returns promise that resolves to the open, close and after hours prices of a stock symbol on a certain date + */ + async getDailyOpenClose(params: DailyOpenCloseInput) { + return this.api + .get(`v1/open-close/${params.ticker}/${params.date}`, { + searchParams: { + adjusted: params.adjusted ?? true + } + }) + .json() + } + + /** + * Returns the previous day's open, high, low, and close (OHLC) for the specified stock ticker. + * + * @see {@link https://polygon.io/docs/stocks/get_v2_aggs_ticker__stocksticker__prev} + * + * @param ticker - ticker symbol of the stock/equity + * @param adjusted - whether or not the results are adjusted for splits + * @returns promise that resolves to the previous day's open, high, low, and close (OHLC) for the specified stock ticker + */ + async getPreviousClose(ticker: string, adjusted = true) { + return this.api + .get(`v2/aggs/ticker/${ticker}/prev`, { + searchParams: { + adjusted + } + }) + .json() + } +} diff --git a/test/services/polygon.test.ts b/test/services/polygon.test.ts new file mode 100644 index 00000000..f14b387c --- /dev/null +++ b/test/services/polygon.test.ts @@ -0,0 +1,47 @@ +import test from 'ava' + +import { PolygonClient } from '@/services/polygon' + +import { ky } from '../_utils' + +test('PolygonClient.getTickerDetails', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.getTickerDetails({ ticker: 'AAPL' }) + t.truthy(result.results) + t.is(result.results.ticker, 'AAPL') +}) + +test('PolygonClient.getDailyOpenClose', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.getDailyOpenClose({ + ticker: 'AAPL', + date: '2023-06-21' + }) + t.truthy(result.from) + t.is(result.symbol, 'AAPL') +}) + +test('PolygonClient.getPreviousClose', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.getPreviousClose('AAPL') + t.truthy(result.ticker) + t.is(result.ticker, 'AAPL') +}) From 7af6b7da6e8c41cc7e05d1085281ad06c321b73d Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Thu, 22 Jun 2023 22:14:13 -0400 Subject: [PATCH 2/7] feat: add support for more endpoints --- src/services/polygon.ts | 560 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 531 insertions(+), 29 deletions(-) diff --git a/src/services/polygon.ts b/src/services/polygon.ts index 7143f041..cfb97544 100644 --- a/src/services/polygon.ts +++ b/src/services/polygon.ts @@ -4,47 +4,33 @@ export const POLYGON_API_BASE_URL = 'https://api.polygon.io' /** * Ticker Details v3 input parameters. - * - * @see {@link https://polygon.io/docs/stocks/get_v3_reference_tickers__ticker} */ -export type TickerDetailsInput = { - /** - * The ticker symbol of the asset. - */ +export type PolygonTickerDetailsInput = { + /** The ticker symbol of the asset. */ ticker: string - /** - * Specify a point in time to get information about the ticker available on that date (formatted as YYYY-MM-DD). - */ + /** Specify a point in time to get information about the ticker available on that date (formatted as YYYY-MM-DD). */ date?: string } /** * Daily Open/Close input parameters. - * - * @see {@link https://polygon.io/docs/stocks/get_v1_open-close__stocksticker___date} */ -export type DailyOpenCloseInput = { - /** - * The ticker symbol of the stock/equity. - */ +export type PolygonDailyOpenCloseInput = { + /** The ticker symbol */ ticker: string - /** - * The date of the requested open/close in the format YYYY-MM-DD. - */ + /** The date of the requested open/close in the format YYYY-MM-DD. */ date: string - /** - * Whether or not the results are adjusted for splits. By default, results are adjusted. - */ + /** Whether or not the results are adjusted for splits. By default, results are adjusted. */ adjusted?: boolean } /** * Result returned by the Daily Open/Close API. */ -export interface DailyOpenCloseOutput { +export interface PolygonDailyOpenCloseOutput { /** The close price of the ticker symbol in after-hours trading. */ afterHours: number @@ -79,7 +65,7 @@ export interface DailyOpenCloseOutput { /** * Result returned by the Previous Close API. */ -export interface PreviousCloseOutput { +export interface PolygonPreviousCloseOutput { /** Whether or not this response was adjusted for splits. */ adjusted: boolean @@ -129,7 +115,7 @@ export interface PreviousCloseOutput { /** * Result returned by the Ticker Details v3 API. */ -export interface TickerDetailsOutput { +export interface PolygonTickerDetailsOutput { /** A request id assigned by the server. */ requestId: string @@ -239,6 +225,410 @@ export interface TickerDetailsOutput { status: string } +/** + * Input parameters for technical indicators. + */ +export type PolygonIndicatorInput = { + /** The ticker symbol for which to get data. */ + ticker: string + + /** Query by timestamp. Either a date with the format YYYY-MM-DD or a millisecond timestamp. */ + timestamp?: string + + /** The size of the aggregate time window. */ + timespan?: string + + /** Whether or not the aggregates are adjusted for splits. By default, aggregates are adjusted. Set this to false to get results that are NOT adjusted for splits. */ + adjusted?: boolean + + /** The window size used to calculate the indicator. i.e. a window size of 10 with daily aggregates would result in a 10 day moving average. */ + window?: number + + /** The price in the aggregate which will be used to calculate the indicator. */ + series_type?: string + + /** Whether or not to include the aggregates used to calculate this indicator in the response. */ + expand_underlying?: boolean + + /** The order in which to return the results, ordered by timestamp. */ + order?: string + + /** Limit the number of results returned, default is 10 and max is 5000 */ + limit?: number +} + +/** + * Represents an aggregate, which includes data for a given time period. + */ +interface PolygonIndicatorAggregate { + /** The close price for the symbol in the given time period. */ + c: number + + /** The highest price for the symbol in the given time period. */ + h: number + + /** The lowest price for the symbol in the given time period. */ + l: number + + /** The number of transactions in the aggregate window. */ + n: number + + /** The open price for the symbol in the given time period. */ + o: number + + /** The Unix Msec timestamp for the start of the aggregate window. */ + t: number + + /** The trading volume of the symbol in the given time period. */ + v: number + + /** The volume weighted average price. */ + vw: number +} + +/** + * Represents a value of the indicator, which includes timestamp and value itself. + */ +interface PolygonIndicatorValue { + /** The Unix Msec timestamp from the last aggregate used in this calculation. */ + timestamp: number + + /** The indicator value for this period. */ + value: number +} + +/** + * The output response from the technical indicator API. + */ +interface PolygonIndicatorOutput { + /** If present, this value can be used to fetch the next page of data. */ + next_url: string + + /** A request id assigned by the server. */ + request_id: string + + /** Results object containing underlying aggregates and values array. */ + results: { + /** Underlying object containing aggregates and a URL to fetch underlying data. */ + underlying: { + /** Array of aggregates used for calculation. */ + aggregates: PolygonIndicatorAggregate[] + + /** The URL which can be used to request the underlying aggregates used in this request. */ + url: string + } + + /** Array of calculated indicator values. */ + values: PolygonIndicatorValue[] + } + + /** The status of this request's response. */ + status: string +} + +/** + * Input parameters for the /v3/reference/tickers API. + */ +export type PolygonTickerInput = { + /** Specify a ticker symbol. Defaults to empty string which queries all tickers. */ + ticker?: string + + /** Specify the type of the tickers. */ + type?: string + + /** Filter by market type. */ + market?: 'crypto' + + /** Specify the primary exchange of the asset in the ISO code format. */ + exchange?: string + + /** Specify the CUSIP code of the asset you want to search for. */ + cusip?: string + + /** Specify the CIK of the asset you want to search for. */ + cik?: string + + /** Specify a point in time to retrieve tickers available on that date. */ + date?: string + + /** Search for terms within the ticker and/or company name. */ + search?: string + + /** Specify if the tickers returned should be actively traded on the queried date. */ + active?: boolean + + /** Order results based on the sort field. */ + order?: string + + /** Limit the number of results returned. */ + limit?: number + + /** Sort field used for ordering. */ + sort?: string +} + +/** + * Represents a ticker that matches the query. + */ +interface PolygonTicker { + /** Whether or not the asset is actively traded. */ + active: boolean + + /** The CIK number for this ticker. */ + cik: string + + /** The composite OpenFIGI number for this ticker. */ + composite_figi: string + + /** The name of the currency that this asset is traded with. */ + currency_name: string + + /** The last date that the asset was traded. */ + delisted_utc: string + + /** The information is accurate up to this time. */ + last_updated_utc: string + + /** The locale of the asset. */ + locale: 'us' | 'global' + + /** The market type of the asset. */ + market: 'stocks' | 'crypto' | 'fx' | 'otc' | 'indices' + + /** The name of the asset. */ + name: string + + /** The ISO code of the primary listing exchange for this asset. */ + primary_exchange: string + + /** The share Class OpenFIGI number for this ticker. */ + share_class_figi: string + + /** The exchange symbol that this item is traded under. */ + ticker: string + + /** The type of the asset. */ + type: string +} + +/** + * The output response from the /v3/reference/tickers API. + */ +interface PolygonTickerOutput { + /** The total number of results for this request. */ + count: number + + /** If present, this value can be used to fetch the next page of data. */ + next_url: string + + /** A request id assigned by the server. */ + request_id: string + + /** An array of tickers that match your query. */ + results: PolygonTicker[] + + /** The status of this request's response. */ + status: string +} + +/** + * Output parameters for the market status API. + */ +export interface PolygonMarketStatusOutput { + /** Whether or not the market is in post-market hours. */ + afterHours: boolean + + /** The status of the crypto and forex markets. */ + currencies: { + /** The status of the crypto market. */ + crypto: string + /** The status of the forex market. */ + fx: string + } + + /** Whether or not the market is in pre-market hours. */ + earlyHours: boolean + + /** The status of the Nasdaq, NYSE and OTC markets. */ + exchanges: { + /** The status of the Nasdaq market. */ + nasdaq: string + /** The status of the NYSE market. */ + nyse: string + /** The status of the OTC market. */ + otc: string + } + + /** The status of the market as a whole. */ + market: string + + /** The current time of the server. */ + serverTime: string +} + +/** + * Output parameters for the market holidays API. + */ +export interface PolygonMarketHolidayOutput { + /** The market close time on the holiday (if it's not closed). */ + close?: string + + /** The date of the holiday. */ + date: string + + /** Which market the record is for. */ + exchange: string + + /** The name of the holiday. */ + name: string + + /** The market open time on the holiday (if it's not closed). */ + open?: string + + /** The status of the market on the holiday. */ + status: string +} + +/** + * Input parameters for the ticker types API. + */ +export type PolygonTickerTypesInput = { + /** Filter by asset class. */ + asset_class?: string + + /** Filter by locale. */ + locale?: string +} + +/** + * Output parameters for the ticker types API. + */ +export interface PolygonTickerTypesOutput { + /** The total number of results for this request. */ + count: number + + /** A request ID assigned by the server. */ + request_id: string + + /** The results of the query. */ + results: PolygonTickerType[] + + /** The status of this request's response. */ + status: string +} + +/** + * Ticker type parameters. + */ +export interface PolygonTickerType { + /** An identifier for a group of similar financial instruments. */ + asset_class: string + + /** A code used by Polygon.io to refer to this ticker type. */ + code: string + + /** A short description of this ticker type. */ + description: string + + /** An identifier for a geographical location. */ + locale: string +} + +/** + * Input parameters for the ticker news API. + */ +export type PolygonTickerNewsInput = { + /** Ticker symbol to return results for. */ + ticker: string + + /** Date to return results published on, before, or after. */ + published_utc?: string + + /** Order results based on the sort field. */ + order?: string + + /** Limit the number of results returned, default is 10 and max is 1000. */ + limit?: number + + /** Sort field used for ordering. */ + sort?: string +} + +/** + * Output parameters for the ticker news API. + */ +export interface PolygonTickerNewsOutput { + /** The total number of results for this request. */ + count: number + + /** If present, this value can be used to fetch the next page of data. */ + next_url: string + + /** A request id assigned by the server. */ + request_id: string + + /** The results of the query. */ + results: PolygonTickerNews[] + + /** The status of this request's response. */ + status: string +} + +/** + * Ticker news parameters. + */ +export interface PolygonTickerNews { + /** The mobile friendly Accelerated Mobile Page (AMP) URL. */ + amp_url?: string + + /** A link to the news article. */ + article_url: string + + /** The article's author. */ + author: string + + /** A description of the article. */ + description?: string + + /** Unique identifier for the article. */ + id: string + + /** The article's image URL. */ + image_url?: string + + /** The keywords associated with the article (which will vary depending on the publishing source). */ + keywords?: string[] + + /** The date the article was published on. */ + published_utc: string + + /** The publisher's details. */ + publisher: PolygonPublisher + + /** The ticker symbols associated with the article. */ + tickers: string[] + + /** The title of the news article. */ + title: string +} + +/** + * Publisher parameters. + */ +export interface PolygonPublisher { + /** The publisher's homepage favicon URL. */ + favicon_url?: string + + /** The publisher's homepage URL. */ + homepage_url: string + + /** The publisher's logo URL. */ + logo_url: string + + /** The publisher's name. */ + name: string +} + export class PolygonClient { /** * HTTP client for the Polygon API. @@ -285,7 +675,7 @@ export class PolygonClient { * @param params - input parameters (`ticker` symbol and optional `date`) * @returns promise that resolves to detailed information about a single ticker */ - async getTickerDetails(params: TickerDetailsInput) { + async getTickerDetails(params: PolygonTickerDetailsInput) { let searchParams if (params.date) { searchParams = { @@ -297,7 +687,7 @@ export class PolygonClient { .get(`v3/reference/tickers/${params.ticker}`, { searchParams }) - .json() + .json() } /** @@ -306,14 +696,14 @@ export class PolygonClient { * @param params - input parameters (`ticker` symbol and `date`) * @returns promise that resolves to the open, close and after hours prices of a stock symbol on a certain date */ - async getDailyOpenClose(params: DailyOpenCloseInput) { + async getDailyOpenClose(params: PolygonDailyOpenCloseInput) { return this.api .get(`v1/open-close/${params.ticker}/${params.date}`, { searchParams: { adjusted: params.adjusted ?? true } }) - .json() + .json() } /** @@ -332,6 +722,118 @@ export class PolygonClient { adjusted } }) - .json() + .json() + } + + /** + * Get the simple moving average (SMA) for a ticker symbol over a given time range. + * + * @param params - input parameters + * @returns promise that resolves to the simple moving average (SMA) for a ticker symbol over a given time range + */ + async sma(params: PolygonIndicatorInput) { + return this.api + .get(`v1/indicators/sma/${params.ticker}`, { + searchParams: params + }) + .json() + } + + /** + * Get the exponential moving average (EMA) for a ticker symbol over a given time range. + * + * @param params - input parameters + * @returns promise that resolves to the exponential moving average (EMA) for a ticker symbol over a given time range + */ + async ema(params: PolygonIndicatorInput) { + return this.api + .get(`v1/indicators/ema/${params.ticker}`, { + searchParams: params + }) + .json() + } + + /** + * Get moving average convergence/divergence (MACD) for a ticker symbol over a given time range. + * + * @param params - input parameters + * @returns promise that resolves to the moving average convergence/divergence (MACD) for a ticker symbol over a given time range + */ + async macd(params: PolygonIndicatorInput) { + return this.api + .get(`v1/indicators/ema/${params.ticker}`, { + searchParams: params + }) + .json() + } + + /** + * Get the relative strength index (RSI) for a ticker symbol over a given time range. + * + * @param params - input parameters + * @returns promise that resolves to the relative strength index (RSI) for a ticker symbol over a given time range + */ + async rsi(params: PolygonIndicatorInput) { + return this.api + .get(`v1/indicators/rsi/${params.ticker}`, { + searchParams: params + }) + .json() + } + + /** + * Query all ticker symbols which are supported by Polygon.io. Currently includes Stocks/Equities, Indices, Forex, and Crypto. + * + * @param params - input parameters to filter the list of ticker symbols + * @returns promise that resolves to a list of ticker symbols and their details + */ + async getTickers(params: PolygonTickerInput): Promise { + return this.api + .get('v3/reference/tickers', { searchParams: params }) + .json() + } + + /** + * List all ticker types that Polygon.io has. + * + * @param params - input parameters (`asset_class` and `locale`) + * @returns promise that resolves to ticker types + */ + async getTickerTypes(params: PolygonTickerTypesInput) { + return this.api + .get('v3/reference/tickers/types', { searchParams: params }) + .json() + } + + /** + * Get the most recent news articles relating to a stock ticker symbol. + * + * @param params - input parameters (`ticker`, `published_utc`, `order`, `limit`, `sort`) + * @returns promise that resolves to ticker news + */ + async getTickerNews(params: PolygonTickerNewsInput) { + return this.api + .get('v2/reference/news', { searchParams: params }) + .json() + } + + /** + * Returns the current trading status of the exchanges and overall financial markets. + * + * @returns promise that resolves to the market status + */ + async getMarketStatus() { + return this.api.get('v1/marketstatus/now').json() + } + + /** + * Gets upcoming market holidays and their open/close times. + * + * @returns promise that resolves to an array of market holidays + */ + async getMarketHolidays(): Promise { + return this.api + .get('v1/marketstatus/upcoming') + .json() } } From c8ce3501f669f500f98e2a088e7acdf244189597 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Thu, 22 Jun 2023 22:22:40 -0400 Subject: [PATCH 3/7] feat: add support for more endpoints --- src/services/polygon.ts | 75 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/services/polygon.ts b/src/services/polygon.ts index cfb97544..4069c4cb 100644 --- a/src/services/polygon.ts +++ b/src/services/polygon.ts @@ -629,6 +629,69 @@ export interface PolygonPublisher { name: string } +/** + * Input parameters for the exchanges API. + */ +export type PolygonExchangesInput = { + /** Filter by asset class. */ + asset_class?: string + + /** Filter by locale. */ + locale?: string +} + +/** + * Output parameters for the exchanges API. + */ +export interface PolygonExchangesOutput { + /** The total number of results for this request. */ + count: number + + /** A request ID assigned by the server. */ + request_id: string + + /** The results of the query. */ + results: PolygonExchange[] + + /** The status of this request's response. */ + status: string +} + +/** + * Exchange parameters. + */ +export interface PolygonExchange { + /** A commonly used abbreviation for this exchange. */ + acronym?: string + + /** An identifier for a group of similar financial instruments. */ + asset_class: 'stocks' | 'options' | 'crypto' | 'fx' + + /** A unique identifier used by Polygon.io for this exchange. */ + id: number + + /** An identifier for a geographical location. */ + locale: 'us' | 'global' + + /** The Market Identifer Code of this exchange (see ISO 10383). */ + mic: string + + /** Name of this exchange. */ + name: string + + /** The MIC of the entity that operates this exchange. */ + operating_mic: string + + /** The ID used by SIP's to represent this exchange. */ + participant_id?: string + + /** Represents the type of exchange. */ + type: 'exchange' | 'TRF' | 'SIP' + + /** A link to this exchange's website, if one exists. */ + url?: string +} + export class PolygonClient { /** * HTTP client for the Polygon API. @@ -836,4 +899,16 @@ export class PolygonClient { .get('v1/marketstatus/upcoming') .json() } + + /** + * List all exchanges that Polygon.io knows about. + * + * @param params - input parameters (`asset_class`, `locale`) + * @returns promise that resolves to list of exchanges + */ + async getExchanges(params: PolygonExchangesInput) { + return this.api + .get('v3/reference/exchanges', { searchParams: params }) + .json() + } } From 016bdff1a6d3cdadffde4929734535a092467077 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Fri, 23 Jun 2023 09:59:50 -0400 Subject: [PATCH 4/7] feat: add support for aggregation endpoints --- src/services/polygon.ts | 231 ++++++++++++++++++++++++++++++---- test/services/polygon.test.ts | 12 +- 2 files changed, 216 insertions(+), 27 deletions(-) diff --git a/src/services/polygon.ts b/src/services/polygon.ts index 4069c4cb..4ebe0c0a 100644 --- a/src/services/polygon.ts +++ b/src/services/polygon.ts @@ -2,6 +2,142 @@ import defaultKy from 'ky' export const POLYGON_API_BASE_URL = 'https://api.polygon.io' +/** + * Asset classes available on Polygon. + */ +export type POLYGON_ASSET_CLASS = 'stocks' | 'options' | 'crypto' | 'fx' + +/** + * Supported time spans for Polygon's indicator APIs. + */ +export type POLYGON_TIMESPAN = + | 'minute' + | 'hour' + | 'day' + | 'week' + | 'month' + | 'quarter' + | 'year' + +/** + * Supported series types for Polygon's indicator APIs. + */ +export type POLYGON_SERIES_TYPE = 'close' | 'open' | 'high' | 'low' + +/** + * Order types available on Polygon. + */ +export type POLYGON_ORDER_TYPE = 'asc' | 'desc' + +/** + * Input parameters for the aggregates API. + */ +export interface PolygonAggregatesInput { + /** The ticker symbol of the stock/equity. */ + stocksTicker: string + + /** The size of the timespan multiplier. */ + multiplier: number + + /** The size of the time window. */ + timespan: POLYGON_TIMESPAN + + /** The start of the aggregate time window. Either a date with the format YYYY-MM-DD or a millisecond timestamp. */ + from: string | number + + /** The end of the aggregate time window. Either a date with the format YYYY-MM-DD or a millisecond timestamp. */ + to: string | number + + /** Whether or not the results are adjusted for splits. By default, results are adjusted. Set this to false to get results that are NOT adjusted for splits. */ + adjusted?: boolean + + /** Sort the results by timestamp. "asc" will return results in ascending order (oldest at the top), "desc" will return results in descending order (newest at the top). */ + sort?: POLYGON_ORDER_TYPE + + /** Limits the number of base aggregates queried to create the aggregate results. Max 50000 and Default 5000. */ + limit?: number +} + +/** + * Output parameters for the aggregates API. + */ +export interface PolygonAggregatesOutput { + /** The exchange symbol that this item is traded under. */ + ticker: string + + /** Whether or not this response was adjusted for splits. */ + adjusted: boolean + + /** The number of aggregates (minute or day) used to generate the response. */ + queryCount: number + + /** A request id assigned by the server. */ + request_id: string + + /** The total number of results for this request. */ + resultsCount: number + + /** The status of this request's response. */ + status: string + + /** The results of the query. */ + results: PolygonAggregate[] + + /** If present, this value can be used to fetch the next page of data. */ + next_url?: string +} + +/** + * Input parameters for the grouped daily API. + */ +export type PolygonGroupedDailyInput = { + /** The beginning date for the aggregate window. */ + date: string + + /** Whether or not the results are adjusted for splits. By default, results are adjusted. Set this to false to get results that are NOT adjusted for splits. */ + adjusted?: boolean +} + +/** + * Input parameters for the grouped daily API for stocks. + */ +export interface PolygonGroupedDailyInputStocks + extends PolygonGroupedDailyInput { + /** Include OTC securities in the response. Default is false (don't include OTC securities). */ + include_otc?: boolean +} + +/** + * Output parameters for the grouped daily API. + */ +export interface PolygonGroupedDailyOutput { + /** Whether or not this response was adjusted for splits. */ + adjusted: boolean + + /** The number of aggregates (minute or day) used to generate the response. */ + queryCount: number + + /** A request id assigned by the server. */ + request_id: string + + /** The total number of results for this request. */ + resultsCount: number + + /** The status of this request's response. */ + status: string + + /** The results of the query. */ + results: PolygonAggregateDaily[] +} + +/** + * AggregateDaily parameters. + */ +export interface PolygonAggregateDaily extends PolygonAggregate { + /** The exchange symbol that this item is traded under. */ + T: string +} + /** * Ticker Details v3 input parameters. */ @@ -236,7 +372,7 @@ export type PolygonIndicatorInput = { timestamp?: string /** The size of the aggregate time window. */ - timespan?: string + timespan?: POLYGON_TIMESPAN /** Whether or not the aggregates are adjusted for splits. By default, aggregates are adjusted. Set this to false to get results that are NOT adjusted for splits. */ adjusted?: boolean @@ -245,13 +381,13 @@ export type PolygonIndicatorInput = { window?: number /** The price in the aggregate which will be used to calculate the indicator. */ - series_type?: string + series_type?: POLYGON_SERIES_TYPE /** Whether or not to include the aggregates used to calculate this indicator in the response. */ expand_underlying?: boolean /** The order in which to return the results, ordered by timestamp. */ - order?: string + order?: POLYGON_ORDER_TYPE /** Limit the number of results returned, default is 10 and max is 5000 */ limit?: number @@ -260,7 +396,7 @@ export type PolygonIndicatorInput = { /** * Represents an aggregate, which includes data for a given time period. */ -interface PolygonIndicatorAggregate { +interface PolygonAggregate { /** The close price for the symbol in the given time period. */ c: number @@ -276,6 +412,9 @@ interface PolygonIndicatorAggregate { /** The open price for the symbol in the given time period. */ o: number + /** Whether or not this aggregate is for an OTC ticker. This field will be left off if false. */ + otc?: boolean + /** The Unix Msec timestamp for the start of the aggregate window. */ t: number @@ -283,7 +422,7 @@ interface PolygonIndicatorAggregate { v: number /** The volume weighted average price. */ - vw: number + vw?: number } /** @@ -312,7 +451,7 @@ interface PolygonIndicatorOutput { /** Underlying object containing aggregates and a URL to fetch underlying data. */ underlying: { /** Array of aggregates used for calculation. */ - aggregates: PolygonIndicatorAggregate[] + aggregates: PolygonAggregate[] /** The URL which can be used to request the underlying aggregates used in this request. */ url: string @@ -358,7 +497,7 @@ export type PolygonTickerInput = { active?: boolean /** Order results based on the sort field. */ - order?: string + order?: POLYGON_ORDER_TYPE /** Limit the number of results returned. */ limit?: number @@ -494,7 +633,7 @@ export interface PolygonMarketHolidayOutput { */ export type PolygonTickerTypesInput = { /** Filter by asset class. */ - asset_class?: string + asset_class?: POLYGON_ASSET_CLASS /** Filter by locale. */ locale?: string @@ -522,7 +661,7 @@ export interface PolygonTickerTypesOutput { */ export interface PolygonTickerType { /** An identifier for a group of similar financial instruments. */ - asset_class: string + asset_class: POLYGON_ASSET_CLASS /** A code used by Polygon.io to refer to this ticker type. */ code: string @@ -545,7 +684,7 @@ export type PolygonTickerNewsInput = { published_utc?: string /** Order results based on the sort field. */ - order?: string + order?: POLYGON_ORDER_TYPE /** Limit the number of results returned, default is 10 and max is 1000. */ limit?: number @@ -634,7 +773,7 @@ export interface PolygonPublisher { */ export type PolygonExchangesInput = { /** Filter by asset class. */ - asset_class?: string + asset_class?: POLYGON_ASSET_CLASS /** Filter by locale. */ locale?: string @@ -665,7 +804,7 @@ export interface PolygonExchange { acronym?: string /** An identifier for a group of similar financial instruments. */ - asset_class: 'stocks' | 'options' | 'crypto' | 'fx' + asset_class: POLYGON_ASSET_CLASS /** A unique identifier used by Polygon.io for this exchange. */ id: number @@ -738,7 +877,7 @@ export class PolygonClient { * @param params - input parameters (`ticker` symbol and optional `date`) * @returns promise that resolves to detailed information about a single ticker */ - async getTickerDetails(params: PolygonTickerDetailsInput) { + async tickerDetails(params: PolygonTickerDetailsInput) { let searchParams if (params.date) { searchParams = { @@ -759,7 +898,7 @@ export class PolygonClient { * @param params - input parameters (`ticker` symbol and `date`) * @returns promise that resolves to the open, close and after hours prices of a stock symbol on a certain date */ - async getDailyOpenClose(params: PolygonDailyOpenCloseInput) { + async dailyOpenClose(params: PolygonDailyOpenCloseInput) { return this.api .get(`v1/open-close/${params.ticker}/${params.date}`, { searchParams: { @@ -778,7 +917,7 @@ export class PolygonClient { * @param adjusted - whether or not the results are adjusted for splits * @returns promise that resolves to the previous day's open, high, low, and close (OHLC) for the specified stock ticker */ - async getPreviousClose(ticker: string, adjusted = true) { + async previousClose(ticker: string, adjusted = true) { return this.api .get(`v2/aggs/ticker/${ticker}/prev`, { searchParams: { @@ -850,7 +989,7 @@ export class PolygonClient { * @param params - input parameters to filter the list of ticker symbols * @returns promise that resolves to a list of ticker symbols and their details */ - async getTickers(params: PolygonTickerInput): Promise { + async tickers(params: PolygonTickerInput): Promise { return this.api .get('v3/reference/tickers', { searchParams: params }) .json() @@ -862,7 +1001,7 @@ export class PolygonClient { * @param params - input parameters (`asset_class` and `locale`) * @returns promise that resolves to ticker types */ - async getTickerTypes(params: PolygonTickerTypesInput) { + async tickerTypes(params: PolygonTickerTypesInput) { return this.api .get('v3/reference/tickers/types', { searchParams: params }) .json() @@ -874,7 +1013,7 @@ export class PolygonClient { * @param params - input parameters (`ticker`, `published_utc`, `order`, `limit`, `sort`) * @returns promise that resolves to ticker news */ - async getTickerNews(params: PolygonTickerNewsInput) { + async tickerNews(params: PolygonTickerNewsInput) { return this.api .get('v2/reference/news', { searchParams: params }) .json() @@ -885,7 +1024,7 @@ export class PolygonClient { * * @returns promise that resolves to the market status */ - async getMarketStatus() { + async marketStatus() { return this.api.get('v1/marketstatus/now').json() } @@ -894,7 +1033,7 @@ export class PolygonClient { * * @returns promise that resolves to an array of market holidays */ - async getMarketHolidays(): Promise { + async marketHolidays(): Promise { return this.api .get('v1/marketstatus/upcoming') .json() @@ -906,9 +1045,59 @@ export class PolygonClient { * @param params - input parameters (`asset_class`, `locale`) * @returns promise that resolves to list of exchanges */ - async getExchanges(params: PolygonExchangesInput) { + async exchanges(params: PolygonExchangesInput) { return this.api .get('v3/reference/exchanges', { searchParams: params }) .json() } + + /** + * Get aggregate bars for a stock over a given date range in custom time window sizes. + * + * @param params - input parameters + * @returns promise that resolves to list of aggregates + */ + async aggregates(params: PolygonAggregatesInput) { + const { stocksTicker, multiplier, timespan, from, to, ...otherParams } = + params + const endpoint = `v2/aggs/ticker/${stocksTicker}/range/${multiplier}/${timespan}/${from}/${to}` + return this.api + .get(endpoint, { searchParams: otherParams }) + .json() + } + + /** + * Get the daily open, high, low, and close (OHLC) for the entire markets. + * + * @param assetClass - the asset class to get data for + * @param params - input parameters (`date`, `adjusted`, `include_otc`) + * @returns promise that resolves to list of aggregates + */ + async groupedDaily( + assetClass: 'stocks', + params: PolygonGroupedDailyInputStocks + ): Promise + + /** + * Get the daily open, high, low, and close (OHLC) for the entire markets. + * + * @param assetClass - the asset class to get data for + * @param params - input parameters (`date`, `adjusted`) + * @returns promise that resolves to list of aggregates + */ + async groupedDaily( + assetClass: 'options' | 'crypto' | 'fx', + params: PolygonGroupedDailyInput + ): Promise + + async groupedDaily( + assetClass: POLYGON_ASSET_CLASS, + params: PolygonGroupedDailyInput + ) { + const { date, ...otherParams } = params + const endpoint = `v2/aggs/grouped/locale/us/market/${assetClass}/${date}` + return this.api + .get(endpoint, { searchParams: otherParams }) + .json() + } } diff --git a/test/services/polygon.test.ts b/test/services/polygon.test.ts index f14b387c..87d0b691 100644 --- a/test/services/polygon.test.ts +++ b/test/services/polygon.test.ts @@ -4,7 +4,7 @@ import { PolygonClient } from '@/services/polygon' import { ky } from '../_utils' -test('PolygonClient.getTickerDetails', async (t) => { +test('PolygonClient.tickerDetails', async (t) => { if (!process.env.POLYGON_API_KEY) { return t.pass() } @@ -12,12 +12,12 @@ test('PolygonClient.getTickerDetails', async (t) => { t.timeout(2 * 60 * 1000) const client = new PolygonClient({ ky }) - const result = await client.getTickerDetails({ ticker: 'AAPL' }) + const result = await client.tickerDetails({ ticker: 'AAPL' }) t.truthy(result.results) t.is(result.results.ticker, 'AAPL') }) -test('PolygonClient.getDailyOpenClose', async (t) => { +test('PolygonClient.dailyOpenClose', async (t) => { if (!process.env.POLYGON_API_KEY) { return t.pass() } @@ -25,7 +25,7 @@ test('PolygonClient.getDailyOpenClose', async (t) => { t.timeout(2 * 60 * 1000) const client = new PolygonClient({ ky }) - const result = await client.getDailyOpenClose({ + const result = await client.dailyOpenClose({ ticker: 'AAPL', date: '2023-06-21' }) @@ -33,7 +33,7 @@ test('PolygonClient.getDailyOpenClose', async (t) => { t.is(result.symbol, 'AAPL') }) -test('PolygonClient.getPreviousClose', async (t) => { +test('PolygonClient.previousClose', async (t) => { if (!process.env.POLYGON_API_KEY) { return t.pass() } @@ -41,7 +41,7 @@ test('PolygonClient.getPreviousClose', async (t) => { t.timeout(2 * 60 * 1000) const client = new PolygonClient({ ky }) - const result = await client.getPreviousClose('AAPL') + const result = await client.previousClose('AAPL') t.truthy(result.ticker) t.is(result.ticker, 'AAPL') }) From 42ff3f96a382190484e69179743de9a3df11275e Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Sat, 24 Jun 2023 11:10:25 -0400 Subject: [PATCH 5/7] docs: add service readme file --- docs/polygon.md | 21 +++++++++++++++++++++ src/services/polygon.ts | 5 +++++ 2 files changed, 26 insertions(+) create mode 100644 docs/polygon.md diff --git a/docs/polygon.md b/docs/polygon.md new file mode 100644 index 00000000..da672f97 --- /dev/null +++ b/docs/polygon.md @@ -0,0 +1,21 @@ +

Polygon.io Agentic Service

+ +## Intro + +[Polygon.io][polygon] is a powerful stock market data API provider that provides extensive APIs that range from the realtime and historical data on stocks, options, indices, forex, crypto. Built for developers by developers, Polygon.io is the ideal data provider for your next project. + +## Pre-requisites + +Ensure the following environment variable is set: + +- `POLYGON_API_KEY` - API key for Polygon.io. + +Otherwise, you can pass it in as an argument to the `PolygonClient ` constructor. + +### How to Retrieve API Key + +1. Open [Polygon.io][polygon] and sign up for an account or log in. +2. On the left side of the screen, click on the `API Keys` menu item. +3. Locate your API key and copy it to your clipboard by clicking on the respective button. + +[polygon]: https://polygon.io diff --git a/src/services/polygon.ts b/src/services/polygon.ts index 4ebe0c0a..727267d9 100644 --- a/src/services/polygon.ts +++ b/src/services/polygon.ts @@ -831,6 +831,11 @@ export interface PolygonExchange { url?: string } +/** + * Client for the Polygon.io REST API. + * + * @see {@link https://polygon.io/docs} + */ export class PolygonClient { /** * HTTP client for the Polygon API. From fbb95733ddb5682a830ccfd4f83032b8fb422b6d Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Sat, 24 Jun 2023 12:51:49 -0400 Subject: [PATCH 6/7] feat: minor fcn signature changes and add tests --- docs/polygon.md | 4 +- src/services/polygon.ts | 13 +-- test/services/polygon.test.ts | 197 +++++++++++++++++++++++++++++++++- 3 files changed, 201 insertions(+), 13 deletions(-) diff --git a/docs/polygon.md b/docs/polygon.md index da672f97..0ea24cfc 100644 --- a/docs/polygon.md +++ b/docs/polygon.md @@ -16,6 +16,8 @@ Otherwise, you can pass it in as an argument to the `PolygonClient ` constructor 1. Open [Polygon.io][polygon] and sign up for an account or log in. 2. On the left side of the screen, click on the `API Keys` menu item. -3. Locate your API key and copy it to your clipboard by clicking on the respective button. +3. Locate your API key and copy it to your clipboard by clicking on the "Copy" button. + + ![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-06-24/6c09ebfa-b326-4446-b8e8-89bf01b2bff3/user_cropped_screenshot.jpeg?tl_px=25,0&br_px=1145,630&force_format=png&width=560&wat_scale=50&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=479,111) [polygon]: https://polygon.io diff --git a/src/services/polygon.ts b/src/services/polygon.ts index 727267d9..6f8cd1c1 100644 --- a/src/services/polygon.ts +++ b/src/services/polygon.ts @@ -34,7 +34,7 @@ export type POLYGON_ORDER_TYPE = 'asc' | 'desc' */ export interface PolygonAggregatesInput { /** The ticker symbol of the stock/equity. */ - stocksTicker: string + ticker: string /** The size of the timespan multiplier. */ multiplier: number @@ -916,8 +916,6 @@ export class PolygonClient { /** * Returns the previous day's open, high, low, and close (OHLC) for the specified stock ticker. * - * @see {@link https://polygon.io/docs/stocks/get_v2_aggs_ticker__stocksticker__prev} - * * @param ticker - ticker symbol of the stock/equity * @param adjusted - whether or not the results are adjusted for splits * @returns promise that resolves to the previous day's open, high, low, and close (OHLC) for the specified stock ticker @@ -1006,7 +1004,7 @@ export class PolygonClient { * @param params - input parameters (`asset_class` and `locale`) * @returns promise that resolves to ticker types */ - async tickerTypes(params: PolygonTickerTypesInput) { + async tickerTypes(params: PolygonTickerTypesInput = {}) { return this.api .get('v3/reference/tickers/types', { searchParams: params }) .json() @@ -1050,7 +1048,7 @@ export class PolygonClient { * @param params - input parameters (`asset_class`, `locale`) * @returns promise that resolves to list of exchanges */ - async exchanges(params: PolygonExchangesInput) { + async exchanges(params: PolygonExchangesInput = {}) { return this.api .get('v3/reference/exchanges', { searchParams: params }) .json() @@ -1063,9 +1061,8 @@ export class PolygonClient { * @returns promise that resolves to list of aggregates */ async aggregates(params: PolygonAggregatesInput) { - const { stocksTicker, multiplier, timespan, from, to, ...otherParams } = - params - const endpoint = `v2/aggs/ticker/${stocksTicker}/range/${multiplier}/${timespan}/${from}/${to}` + const { ticker, multiplier, timespan, from, to, ...otherParams } = params + const endpoint = `v2/aggs/ticker/${ticker}/range/${multiplier}/${timespan}/${from}/${to}` return this.api .get(endpoint, { searchParams: otherParams }) .json() diff --git a/test/services/polygon.test.ts b/test/services/polygon.test.ts index 87d0b691..142f6e59 100644 --- a/test/services/polygon.test.ts +++ b/test/services/polygon.test.ts @@ -4,7 +4,7 @@ import { PolygonClient } from '@/services/polygon' import { ky } from '../_utils' -test('PolygonClient.tickerDetails', async (t) => { +test('PolygonClient.aggregates', async (t) => { if (!process.env.POLYGON_API_KEY) { return t.pass() } @@ -12,9 +12,20 @@ test('PolygonClient.tickerDetails', async (t) => { t.timeout(2 * 60 * 1000) const client = new PolygonClient({ ky }) - const result = await client.tickerDetails({ ticker: 'AAPL' }) - t.truthy(result.results) - t.is(result.results.ticker, 'AAPL') + const result = await client.aggregates({ + ticker: 'AAPL', + from: '2023-01-01', + to: '2023-01-03', + multiplier: 1, + timespan: 'day', + limit: 1 + }) + t.is(typeof result.status, 'string', 'Status should be a string') + t.is(typeof result.request_id, 'string', 'Request_id should be a string') + t.is(typeof result.queryCount, 'number', 'queryCount should be a number') + t.is(typeof result.resultsCount, 'number', 'resultsCount should be a number') + t.is(typeof result.adjusted, 'boolean', 'adjusted should be a boolean') + t.is(typeof result.results, 'object', 'results should be an object') }) test('PolygonClient.dailyOpenClose', async (t) => { @@ -33,6 +44,95 @@ test('PolygonClient.dailyOpenClose', async (t) => { t.is(result.symbol, 'AAPL') }) +test('PolygonClient.ema', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.ema({ ticker: 'AAPL' }) + t.truthy(result.results) + t.true(Array.isArray(result.results.values)) +}) + +test('PolygonClient.exchanges', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.exchanges({ + asset_class: 'stocks' + }) + t.truthy(result.status) + t.true(Array.isArray(result.results)) +}) + +test('PolygonClient.groupedDaily', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.groupedDaily('stocks', { date: '2023-06-21' }) + t.truthy(result.status) + t.true(Array.isArray(result.results)) +}) + +test('PolygonClient.macd', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.macd({ ticker: 'AAPL' }) + t.truthy(result.results) + t.true(Array.isArray(result.results.values)) +}) + +test('PolygonClient.marketHolidays', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.marketHolidays() + t.true(Array.isArray(result), 'Result should be an array') +}) + +test('PolygonClient.marketStatus', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.marketStatus() + + t.true(typeof result.afterHours === 'boolean') + t.truthy(result.currencies) + t.true(typeof result.currencies.crypto === 'string') + t.true(typeof result.currencies.fx === 'string') + t.true(typeof result.earlyHours === 'boolean') + t.truthy(result.exchanges) + t.true(typeof result.exchanges.nasdaq === 'string') + t.true(typeof result.exchanges.nyse === 'string') + t.true(typeof result.exchanges.otc === 'string') + t.true(typeof result.market === 'string') + t.true(typeof result.serverTime === 'string') +}) + test('PolygonClient.previousClose', async (t) => { if (!process.env.POLYGON_API_KEY) { return t.pass() @@ -45,3 +145,92 @@ test('PolygonClient.previousClose', async (t) => { t.truthy(result.ticker) t.is(result.ticker, 'AAPL') }) + +test('PolygonClient.rsi', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.rsi({ ticker: 'AAPL' }) + t.truthy(result.results) + t.true(Array.isArray(result.results.values)) +}) + +test('PolygonClient.sma', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.sma({ ticker: 'AAPL' }) + t.truthy(result.results) + t.true(Array.isArray(result.results.values)) +}) + +test('PolygonClient.tickerDetails', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.tickerDetails({ ticker: 'AAPL' }) + t.truthy(result.results) + t.is(result.results.ticker, 'AAPL') +}) + +test('PolygonClient.tickerNews', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + let result = await client.tickerNews({ ticker: 'AAPL', limit: 3 }) + t.truthy(result.status) + t.true(Array.isArray(result.results)) + t.is(result.results.length, 3) + + result = await client.tickerNews({ ticker: 'NFLX', limit: 1 }) + t.truthy(result.status) + t.true(Array.isArray(result.results)) + t.is(result.results.length, 1) +}) + +test('PolygonClient.tickerTypes', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.tickerTypes() + t.is(typeof result.status, 'string', 'Status should be a string') + t.is(typeof result.request_id, 'string', 'Request_id should be a string') + t.is(typeof result.count, 'number', 'Count should be a number') +}) + +test('PolygonClient.tickers', async (t) => { + if (!process.env.POLYGON_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new PolygonClient({ ky }) + + const result = await client.tickers({ + ticker: 'AAPL', + limit: 1 + }) + t.is(typeof result.status, 'string', 'Status should be a string') + t.is(typeof result.request_id, 'string', 'Request_id should be a string') + t.is(typeof result.count, 'number', 'Count should be a number') +}) From cf2868837c35b8d92a5c2d233b23fe1c88de1718 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Sat, 24 Jun 2023 15:09:11 -0400 Subject: [PATCH 7/7] fix: use getEnv function --- src/services/polygon.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/polygon.ts b/src/services/polygon.ts index 6f8cd1c1..51777942 100644 --- a/src/services/polygon.ts +++ b/src/services/polygon.ts @@ -1,5 +1,7 @@ import defaultKy from 'ky' +import { getEnv } from '@/env' + export const POLYGON_API_BASE_URL = 'https://api.polygon.io' /** @@ -853,7 +855,7 @@ export class PolygonClient { readonly apiBaseUrl: string constructor({ - apiKey = process.env.POLYGON_API_KEY, + apiKey = getEnv('POLYGON_API_KEY'), apiBaseUrl = POLYGON_API_BASE_URL, ky = defaultKy }: {