import type { CalendarDatabase } from "./calendardb.interfaces.js"; import { open } from "lmdbx"; const calendarAggregatesDb = open({ path: "/tmp/calendar-aggregates.db", // any options go here, we can turn on compression like this: compression: true, }); const calendarExistenceDb = open({ path: "/tmp/calendar-existence.db", // any options go here, we can turn on compression like this: 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; }, 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 startOfLastHourUnix = new Date( `${frontExpirationDate}T00:00:00Z`, ).valueOf(); const endOfLastHourUnix = startOfLastHourUnix + 3600 * 1000; let minPrice = 0; for (const { value } of calendarAggregatesDb.getRange({ start: [ symbol, frontExpirationDate, backExpirationDate, strike, type, startOfLastHourUnix, ], end: [ symbol, frontExpirationDate, backExpirationDate, strike, type, endOfLastHourUnix, ], })) { if (value.close < minPrice || minPrice === 0) { minPrice = value.close; } } return minPrice; }, getTargetPriceByProbability: async ({ symbol, calendarSpan, strikePercentageFromTheMoney, historicalProbabilityOfSuccess, }) => { return 0.24; }, }; return { ...calendarDatabase, getCalendars: calendarDatabase.getKeys, }; } export const calendarDatabase: CalendarDatabase = makeCalendarDatabase();