You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

150 lines
4.2 KiB
TypeScript

// 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
);
}
}