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