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.
200 lines
6.6 KiB
TypeScript
200 lines
6.6 KiB
TypeScript
import { optionContractDatabase } from "../optiondb/lmdbx.js";
|
|
import type { CalendarDatabase } from "./interfaces.js";
|
|
|
|
/** Largest possible key according to the `ordered-binary` (used by lmdbx) docs. */
|
|
const MAXIMUM_KEY = Buffer.from([0xff]);
|
|
|
|
function makeCalendarDatabase(): CalendarDatabase {
|
|
const getAggregatesSync = ({
|
|
key: { symbol, frontExpirationDate, backExpirationDate, strike, type },
|
|
date,
|
|
}) => {
|
|
const frontOptionContractAggregates =
|
|
optionContractDatabase.getAggregatesSync({
|
|
date,
|
|
key: { symbol, expirationDate: frontExpirationDate, strike, type },
|
|
});
|
|
const backOptionContractAggregates =
|
|
optionContractDatabase.getAggregatesSync({
|
|
date,
|
|
key: { symbol, expirationDate: backExpirationDate, strike, type },
|
|
});
|
|
const calendarAggregates = [];
|
|
let i = 0;
|
|
let j = 0;
|
|
while (
|
|
i < frontOptionContractAggregates.length &&
|
|
j < backOptionContractAggregates.length
|
|
) {
|
|
if (
|
|
frontOptionContractAggregates[i].tsStart ===
|
|
backOptionContractAggregates[j].tsStart
|
|
) {
|
|
calendarAggregates.push({
|
|
tsStart: frontOptionContractAggregates[i].tsStart,
|
|
open:
|
|
backOptionContractAggregates[j].open -
|
|
frontOptionContractAggregates[i].open,
|
|
close:
|
|
backOptionContractAggregates[j].close -
|
|
frontOptionContractAggregates[i].close,
|
|
// the high and low are not exactly correct since we don't know if each contract's high and low happened at the same moment as the other:
|
|
high:
|
|
backOptionContractAggregates[j].high -
|
|
frontOptionContractAggregates[i].high,
|
|
low:
|
|
backOptionContractAggregates[j].low -
|
|
frontOptionContractAggregates[i].low,
|
|
});
|
|
i++;
|
|
j++;
|
|
} else if (
|
|
frontOptionContractAggregates[i].tsStart >
|
|
backOptionContractAggregates[j].tsStart
|
|
) {
|
|
j++;
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
return calendarAggregates;
|
|
};
|
|
const calendarDatabase: Omit<CalendarDatabase, "getCalendars"> = {
|
|
getKeys: async ({ key: { symbol }, date }) => {
|
|
const optionContracts = await optionContractDatabase.getOptionContracts({
|
|
date,
|
|
key: { symbol },
|
|
});
|
|
return optionContracts.flatMap(
|
|
(frontOptionContract, i, optionContracts) =>
|
|
optionContracts
|
|
.filter((_, j) => i !== j)
|
|
.map((backOptionContract) => ({
|
|
symbol,
|
|
frontExpirationDate: frontOptionContract.expirationDate,
|
|
backExpirationDate: backOptionContract.expirationDate,
|
|
strike: frontOptionContract.strike,
|
|
type: frontOptionContract.type,
|
|
})),
|
|
);
|
|
},
|
|
getAggregates: async ({
|
|
key: { symbol, frontExpirationDate, backExpirationDate, strike, type },
|
|
date,
|
|
}) =>
|
|
getAggregatesSync({
|
|
key: { symbol, frontExpirationDate, backExpirationDate, strike, type },
|
|
date,
|
|
}),
|
|
getAggregatesSync,
|
|
insertAggregates: async (aggregates) => {
|
|
// right now, no-op
|
|
},
|
|
getClosingPrice: async ({
|
|
key: { symbol, strike, type, frontExpirationDate, backExpirationDate },
|
|
}) => {
|
|
// get unix timestamp, in milliseconds, of the start of the last hour, which is 03:30PM in the `America/New_York` timezone on the front expiration date:
|
|
const startOfLastHourUnix = new Date(
|
|
`${frontExpirationDate}T19:30:00Z`
|
|
).getTime();
|
|
const endOfLastHourUnix = startOfLastHourUnix + 3600 * 1000;
|
|
const frontOptionContractAggregates = (
|
|
await optionContractDatabase.getAggregates({
|
|
date: frontExpirationDate,
|
|
key: { symbol, expirationDate: frontExpirationDate, strike, type },
|
|
})
|
|
).filter(
|
|
({ tsStart }) =>
|
|
tsStart >= startOfLastHourUnix && tsStart < endOfLastHourUnix
|
|
);
|
|
const backOptionContractAggregates = (
|
|
await optionContractDatabase.getAggregates({
|
|
date: frontExpirationDate,
|
|
key: { symbol, expirationDate: backExpirationDate, strike, type },
|
|
})
|
|
).filter(
|
|
({ tsStart }) =>
|
|
tsStart >= startOfLastHourUnix && tsStart < endOfLastHourUnix
|
|
);
|
|
let i = 0;
|
|
let j = 0;
|
|
let minPrice = 0;
|
|
while (
|
|
i < frontOptionContractAggregates.length &&
|
|
j < backOptionContractAggregates.length
|
|
) {
|
|
if (
|
|
frontOptionContractAggregates[i].tsStart ===
|
|
backOptionContractAggregates[j].tsStart
|
|
) {
|
|
const calendarClosePrice =
|
|
backOptionContractAggregates[j].close -
|
|
frontOptionContractAggregates[i].close;
|
|
if (calendarClosePrice < minPrice || minPrice === 0) {
|
|
minPrice = calendarClosePrice;
|
|
}
|
|
i++;
|
|
j++;
|
|
} else if (
|
|
frontOptionContractAggregates[i].tsStart >
|
|
backOptionContractAggregates[j].tsStart
|
|
) {
|
|
j++;
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
return minPrice;
|
|
},
|
|
getAggregate: async ({
|
|
key: { symbol, frontExpirationDate, backExpirationDate, strike, type },
|
|
tsStart,
|
|
}) => {
|
|
const [frontOptionContractAggregate, backOptionContractAggregate] =
|
|
await Promise.all([
|
|
optionContractDatabase.getAggregate({
|
|
key: { symbol, expirationDate: frontExpirationDate, strike, type },
|
|
tsStart,
|
|
}),
|
|
optionContractDatabase.getAggregate({
|
|
key: { symbol, expirationDate: backExpirationDate, strike, type },
|
|
tsStart,
|
|
}),
|
|
]);
|
|
// only return the calendar aggregate if its constituent front and back option contract aggregates exist:
|
|
if (frontOptionContractAggregate && backOptionContractAggregate) {
|
|
return {
|
|
tsStart,
|
|
open:
|
|
backOptionContractAggregate.open -
|
|
frontOptionContractAggregate.open,
|
|
close:
|
|
backOptionContractAggregate.close -
|
|
frontOptionContractAggregate.close,
|
|
high:
|
|
backOptionContractAggregate.high -
|
|
frontOptionContractAggregate.high,
|
|
low:
|
|
backOptionContractAggregate.low - frontOptionContractAggregate.low,
|
|
};
|
|
}
|
|
return undefined;
|
|
},
|
|
getTargetPriceByProbability: async ({
|
|
symbol,
|
|
calendarSpan,
|
|
strikePercentageFromTheMoney,
|
|
historicalProbabilityOfSuccess,
|
|
}) => {
|
|
return 0.24;
|
|
},
|
|
};
|
|
|
|
return {
|
|
...calendarDatabase,
|
|
getCalendars: calendarDatabase.getKeys,
|
|
};
|
|
}
|
|
|
|
export const calendarDatabase: CalendarDatabase = makeCalendarDatabase();
|