import type { OptionContractDatabase, OptionContractKey, } from "./optiondb.interfaces.js"; import type { Aggregate } from "./interfaces.js"; import { clickhouse, query } from "./lib/clickhouse.js"; function makeOptionContractDatabase(): OptionContractDatabase { const optionContractDatabase: Omit< OptionContractDatabase, "getOptionContracts" > = { getKeys: async ({ key: { symbol }, date }) => { return ( await query>(` SELECT expirationDate, strike, type FROM option_contract_existences WHERE symbol = '${symbol}' AND asOfDate = '${date}' `) ).map((optionContractWithoutKey) => ({ ...optionContractWithoutKey, symbol, })); }, getAggregates: async ({ key: { symbol, expirationDate, strike, type }, date, }) => { return ( await query, "key">>(` SELECT toUnixTimestamp(tsStart) as tsStart, open, close, high, low FROM option_contract_aggregates WHERE symbol = '${symbol}' AND type = '${type}' AND strike = '${strike}' AND expirationDate = '${expirationDate}' AND toDate(tsStart) = '${date}' ORDER BY tsStart ASC `) ).map((aggregate) => ({ ...aggregate, tsStart: aggregate.tsStart * 1000, // unfortunately, `toUnixTimestamp` only returns second-precision })); }, insertAggregates: async (aggregates) => { // stock existence is taken care of by clickhouse materialized view await clickhouse.insert({ table: "option_contract_aggregates", values: aggregates.map( ({ key: { symbol, expirationDate, strike, type }, tsStart, open, close, high, low, }) => ({ symbol, expirationDate, strike, type, tsStart, open, close, high, low, }), ), }); }, getClosingPrice: async ({ key }) => { // no-op: not used since stocks don't have a "closing" price, unlike options. return 0; }, }; return { ...optionContractDatabase, getOptionContracts: optionContractDatabase.getKeys, }; } export const optionContractDatabase: OptionContractDatabase = makeOptionContractDatabase();