import type { CalendarDatabase } from "./interfaces.js"; import { open } from "lmdbx"; const calendarAggregatesDb = open({ path: "./calendar-aggregates.db", compression: true, }); const calendarExistenceDb = open({ path: "./calendar-existence.db", compression: true, }); /** Largest possible key according to the `ordered-binary` (used by lmdbx) docs. */ const MAXIMUM_KEY = Buffer.from([0xff]); function makeCalendarDatabase(): CalendarDatabase { const calendarDatabase: Omit = { getKeys: async ({ key: { symbol }, date }) => { return calendarExistenceDb .getRange({ start: [date, symbol], end: [date, symbol, MAXIMUM_KEY], }) .map(({ key }) => ({ symbol, frontExpirationDate: key[2], backExpirationDate: key[3], strike: key[4], type: key[5], })).asArray; }, getAggregates: async ({ key: { symbol, frontExpirationDate, backExpirationDate, strike, type }, date, }) => { const startOfDayUnix = new Date(`${date}T00:00:00Z`).valueOf(); const endOfDayUnix = startOfDayUnix + 3600 * 24 * 1000; return calendarAggregatesDb .getRange({ start: [ symbol, frontExpirationDate, backExpirationDate, strike, type, startOfDayUnix, ], end: [ symbol, frontExpirationDate, backExpirationDate, strike, type, endOfDayUnix, ], }) .map(({ value }) => ({ tsStart: value.tsStart, open: value.open, close: value.close, high: value.high, low: value.low, })).asArray; }, getAggregate: async ({ key: { symbol, frontExpirationDate, backExpirationDate, strike, type }, tsStart, }) => { return await calendarAggregatesDb.get([ symbol, frontExpirationDate, backExpirationDate, strike, type, tsStart, ]); }, insertAggregates: async (aggregates) => { await calendarExistenceDb.batch(() => { for (const aggregate of aggregates) { calendarExistenceDb.put( [ new Date(aggregate.tsStart).toISOString().substring(0, 10), aggregate.key.symbol, aggregate.key.frontExpirationDate, aggregate.key.backExpirationDate, aggregate.key.strike, aggregate.key.type, ], null ); } }); await calendarAggregatesDb.batch(() => { for (const aggregate of aggregates) { calendarAggregatesDb.put( [ aggregate.key.symbol, aggregate.key.frontExpirationDate, aggregate.key.backExpirationDate, aggregate.key.strike, aggregate.key.type, aggregate.tsStart, ], { open: aggregate.open, close: aggregate.close, high: aggregate.high, low: aggregate.low, } ); } }); }, getClosingPrice: async ({ key: { symbol, strike, type, frontExpirationDate, backExpirationDate }, }) => { const startOfExpirationDateUnix = new Date( `${frontExpirationDate}T23:59:59Z` ).valueOf(); const endOfExpirationDateUnix = new Date( `${frontExpirationDate}T00:00:00Z` ).valueOf(); for (const { value } of calendarAggregatesDb.getRange({ start: [ symbol, frontExpirationDate, backExpirationDate, strike, type, startOfExpirationDateUnix, ], end: [ symbol, frontExpirationDate, backExpirationDate, strike, type, endOfExpirationDateUnix, ], reverse: true, })) { if (value.close > 0) { return value.close; } } return 0; }, getTargetPriceByProbability: async ({ symbol, calendarSpan, strikePercentageFromTheMoney, historicalProbabilityOfSuccess, }) => { return 0.24; }, }; return { ...calendarDatabase, getCalendars: calendarDatabase.getKeys, }; } export const calendarDatabase: CalendarDatabase = makeCalendarDatabase();