// import pThrottle from "p-throttle"; import pRetry from "p-retry"; const apiKey = "H95NTsatM1iTWLUwDLxM2J5zhUVYdCEz"; // export const getApiKey = pThrottle({ limit: 5, interval: 60000 })(() => apiKey); export const getApiKey = () => apiKey; export const optionContractToTicker = ({ symbol, expirationDate, strike, type, }: { symbol: string; expirationDate: string; strike: number; type: "call" | "put"; }) => `O:${symbol}${expirationDate.substring(2, 4)}${expirationDate.substring( 5, 7 )}${expirationDate.substring(8, 10)}${ type === "call" ? "C" : "P" }${Math.floor(strike * 1000) .toString() .padStart(8, "0")}`; type PolygonOptionContractsResponse = { next_url?: string; results: Array<{ ticker: string; expiration_date: string; strike_price: number; contract_type: "call" | "put"; }>; }; export async function* makeGetOptionContractsIterator( symbol: string, date: string ) { let latestBatchResponse = await pRetry( async () => (await ( await fetch( `https://api.polygon.io/v3/reference/options/contracts?underlying_ticker=${symbol}&as_of=${date}&sort=ticker&limit=1000&apiKey=${await getApiKey()}` ) ).json()) as PolygonOptionContractsResponse, { forever: true, factor: 2, maxTimeout: 120000 } ); yield latestBatchResponse.results.map((result) => ({ asOfDate: date, symbol, expirationDate: result.expiration_date, strike: result.strike_price, type: result.contract_type, })); // as long as there's a `next_url`, call that: while (latestBatchResponse.hasOwnProperty("next_url")) { latestBatchResponse = await pRetry( async () => (await ( await fetch( `${latestBatchResponse.next_url}&apiKey=${await getApiKey()}` ) ).json()) as PolygonOptionContractsResponse, { forever: true, factor: 2, maxTimeout: 120000 } ); yield latestBatchResponse.results?.map((result) => ({ asOfDate: date, symbol, expirationDate: result.expiration_date, strike: result.strike_price, type: result.contract_type, })) || []; } } type PolygonOptionContractAggregatesResponse = { next_url?: string; status: string; resultsCount: number; results: Array<{ c: number; h: number; n: number; l: number; o: number; t: number; v: number; vw: number; }>; }; export type OptionContract = { symbol: string; expirationDate: string; strike: number; type: "call" | "put"; }; export async function* makeGetOptionContractAggregatesIterator({ symbol, expirationDate, strike, type, firstDate, }: OptionContract & { firstDate: string }) { const optionContractTicker = optionContractToTicker({ symbol: symbol, expirationDate, strike, type, }); const expirationDateAsDateObject = new Date(expirationDate); const currentDateAsDateObject = new Date(firstDate); while (currentDateAsDateObject <= expirationDateAsDateObject) { const asOfDate = currentDateAsDateObject.toISOString().substring(0, 10); const url = `https://api.polygon.io/v2/aggs/ticker/${optionContractTicker}/range/1/minute/${asOfDate}/${asOfDate}?adjusted=false&sort=asc&limit=50000&apiKey=${await getApiKey()}`; let latestBatchResponse; latestBatchResponse = await pRetry( async () => (await ( await fetch(url) ).json()) as PolygonOptionContractAggregatesResponse, { forever: true, factor: 2, maxTimeout: 120000 } ); if (latestBatchResponse.status.toLowerCase() === "ok") { yield latestBatchResponse.results?.map((result) => ({ symbol, expirationDate, strike, type, tsStart: (result.t || 0) / 1000, open: result.o, close: result.c, low: result.l, high: result.h, })) || []; } else if (latestBatchResponse.status === "NOT_AUTHORIZED") { console.error("Skipping due to:", latestBatchResponse); } else { console.error(latestBatchResponse); throw new Error(`error fetching option contract aggregate ${url}`); } currentDateAsDateObject.setUTCDate( currentDateAsDateObject.getUTCDate() + 1 ); } }