import { publicProcedure, router } from "./trpc.js"; import { query } from "./lib/clickhouse.js"; import { createHTTPHandler } from "@trpc/server/adapters/standalone"; import cors from "cors"; import { Object as ObjectT, String as StringT, type TSchema, Number as NumberT, } from "@sinclair/typebox"; import { TypeCompiler } from "@sinclair/typebox/compiler"; import { TRPCError } from "@trpc/server"; import { createServer } from "node:http"; import { Env } from "@humanwhocodes/env"; const env = new Env(); const LISTEN_PORT = env.get("LISTEN_PORT", 3005); /** * Generate a TRPC-compatible validator function given a Typebox schema. * This was copied from [https://github.com/sinclairzx81/typebox/blob/6cfcdc02cc813af2f1be57407c771fc4fadfc34a/example/trpc/readme.md]. * @param schema A Typebox schema * @returns A TRPC-compatible validator function */ export function RpcType(schema: T) { const check = TypeCompiler.Compile(schema); return (value: unknown) => { if (check.Check(value)) return value; const { path, message } = check.Errors(value).First(); throw new TRPCError({ message: `${message} for ${path}`, code: "BAD_REQUEST", }); }; } const appRouter = router({ getAvailableUnderlyings: publicProcedure.query(async (opts) => { // return ( // await query<{ symbol: string }>(` // SELECT DISTINCT(symbol) as symbol FROM option_contract_existences WHERE asOfDate = (SELECT max(asOfDate) FROM option_contract_existences) // `) // ).map(({ symbol }) => symbol); return ["AAPL", "AMD", "GOOGL", "MSFT", "NFLX"]; }), getAvailableAsOfDates: publicProcedure .input(RpcType(ObjectT({ underlying: StringT() }))) .query(async (opts) => { const underlying = opts.input.underlying; return ( await query<{ asOfDate: string }>(` SELECT DISTINCT(asOfDate) as asOfDate FROM option_contract_existences WHERE symbol = '${underlying}' ORDER BY asOfDate `) ).map(({ asOfDate }) => asOfDate); }), getExpirationsForUnderlying: publicProcedure .input( RpcType( ObjectT({ underlying: StringT({ maxLength: 5 }), asOfDate: StringT(), }), ), ) .query(async (opts) => { const { underlying, asOfDate } = opts.input; return ( await query<{ expirationDate: string }>(` SELECT DISTINCT(expirationDate) as expirationDate FROM option_contract_existences WHERE symbol = '${underlying}' AND asOfDate = '${asOfDate}' ORDER BY expirationDate `) ).map(({ expirationDate }) => expirationDate); }), getStrikesForUnderlying: publicProcedure .input( RpcType( ObjectT({ underlying: StringT({ maxLength: 5 }), asOfDate: StringT(), expirationDate: StringT(), }), ), ) .query(async (opts) => { const { underlying, asOfDate, expirationDate } = opts.input; return ( await query<{ strike: string }>(` SELECT DISTINCT(strike) as strike FROM option_contract_existences WHERE symbol = '${underlying}' AND asOfDate = '${asOfDate}' AND expirationDate = '${expirationDate}' ORDER BY strike `) ).map(({ strike }) => strike); }), getOpensForUnderlying: publicProcedure .input( RpcType( ObjectT({ underlying: StringT({ maxLength: 5 }), }), ), ) .query(async (opts) => { const { underlying } = opts.input; return await query<{ x: number; y: number }>( ` SELECT toUnixTimestamp(tsStart) as x, open as y FROM stock_aggregates WHERE symbol = '${underlying}' ORDER BY tsStart ASC `, "JSONEachRow", ); }), getOpensForOptionContract: publicProcedure .input( RpcType( ObjectT({ underlying: StringT({ maxLength: 5 }), expirationDate: StringT(), strike: NumberT(), }), ), ) .query(async (opts) => { const { underlying, expirationDate, strike } = opts.input; return await query<{ x: number; y: number }>( ` SELECT toUnixTimestamp(tsStart) as x, open as y FROM option_contract_aggregates WHERE symbol = '${underlying}' AND expirationDate = '${expirationDate}' AND strike = ${strike} AND type = 'call' ORDER BY tsStart ASC `, "JSONEachRow", ); }), getHistoricalCalendarPrices: publicProcedure .input( RpcType( ObjectT({ underlying: StringT({ maxLength: 5 }), daysToFrontExpiration: NumberT(), daysBetweenFrontAndBackExpiration: NumberT(), strikePercentageFromUnderlyingPriceRangeMin: NumberT(), strikePercentageFromUnderlyingPriceRangeMax: NumberT(), }), ), ) .query(async (opts) => { const { underlying, daysToFrontExpiration, daysBetweenFrontAndBackExpiration, strikePercentageFromUnderlyingPriceRangeMin, strikePercentageFromUnderlyingPriceRangeMax, } = opts.input; return ( await query<[number, number]>( ` SELECT toUnixTimestamp(tsStart) as asOfTs, calendarPrice FROM calendar_histories WHERE symbol = '${underlying}' AND daysToFrontExpiration = ${daysToFrontExpiration} AND strikePercentageFromUnderlyingPrice >= ${strikePercentageFromUnderlyingPriceRangeMin} AND strikePercentageFromUnderlyingPrice <= ${strikePercentageFromUnderlyingPriceRangeMax} AND daysBetweenFrontAndBackExpiration = ${daysBetweenFrontAndBackExpiration} `, "JSONCompactEachRow", ) ).reduce( (columns, row) => { columns[0].push(row[0]); columns[1].push(row[1]); return columns; }, [[], []], ); }), getHistoricalStockQuoteChartData: publicProcedure .input( RpcType( ObjectT({ underlying: StringT({ maxLength: 5 }), lookbackPeriodStart: StringT(), lookbackPeriodEnd: StringT(), }), ), ) .query(async (opts) => { const { underlying, lookbackPeriodStart, lookbackPeriodEnd } = opts.input; return await query<[number, number]>( ` SELECT toUnixTimestamp(tsStart) as x, open as y FROM stock_aggregates WHERE symbol = '${underlying}' AND tsStart >= '${lookbackPeriodStart} 00:00:00' AND tsStart <= '${lookbackPeriodEnd} 00:00:00' ORDER BY x ASC `, "JSONEachRow", ); }), getHistoricalCalendarQuoteChartData: publicProcedure .input( RpcType( ObjectT({ underlying: StringT({ maxLength: 5 }), daysToFrontExpiration: NumberT(), daysBetweenFrontAndBackExpiration: NumberT(), strikePercentageFromUnderlyingPriceRangeMin: NumberT(), strikePercentageFromUnderlyingPriceRangeMax: NumberT(), lookbackPeriodStart: StringT(), lookbackPeriodEnd: StringT(), }), ), ) .query(async (opts) => { const { underlying, daysToFrontExpiration, daysBetweenFrontAndBackExpiration, strikePercentageFromUnderlyingPriceRangeMin, strikePercentageFromUnderlyingPriceRangeMax, lookbackPeriodStart, lookbackPeriodEnd, } = opts.input; return await query<[number, number]>( ` SELECT toUnixTimestamp(tsStart) as x, truncate(calendarPrice, 2) as y FROM calendar_histories WHERE symbol = '${underlying}' AND daysToFrontExpiration = ${daysToFrontExpiration} AND strikePercentageFromUnderlyingPrice >= ${strikePercentageFromUnderlyingPriceRangeMin} AND strikePercentageFromUnderlyingPrice <= ${strikePercentageFromUnderlyingPriceRangeMax} AND daysBetweenFrontAndBackExpiration = ${daysBetweenFrontAndBackExpiration} AND tsStart >= '${lookbackPeriodStart} 00:00:00' AND tsStart <= '${lookbackPeriodEnd} 00:00:00' `, "JSONEachRow", ); }), getHistoricalCalendarExitQuoteChartData: publicProcedure .input( RpcType( ObjectT({ underlying: StringT({ maxLength: 5 }), daysToFrontExpiration: NumberT(), daysBetweenFrontAndBackExpiration: NumberT(), lookbackPeriodStart: StringT({ pattern: "[0-9]{4}-[0-9]{2}-[0-9]{2}", }), lookbackPeriodEnd: StringT({ pattern: "[0-9]{4}-[0-9]{2}-[0-9]{2}" }), }), ), ) .query(async (opts) => { const { underlying, daysToFrontExpiration, daysBetweenFrontAndBackExpiration, lookbackPeriodStart, lookbackPeriodEnd, } = opts.input; return await query<[number, number, number]>( ` SELECT FLOOR(strikePercentageFromUnderlyingPrice, 1) as x, FLOOR(calendarPrice, 1) as y, count(*) as n FROM calendar_histories WHERE symbol = '${underlying}' AND daysToFrontExpiration = ${daysToFrontExpiration} AND strikePercentageFromUnderlyingPrice >= -5.0 AND strikePercentageFromUnderlyingPrice <= 5.0 AND daysBetweenFrontAndBackExpiration = ${daysBetweenFrontAndBackExpiration} AND tsStart >= '${lookbackPeriodStart} 00:00:00' AND tsStart <= '${lookbackPeriodEnd} 00:00:00' GROUP BY x, y ORDER BY x ASC, y ASC `, "JSONEachRow", ); }), }); // Export type router type signature, // NOT the router itself. export type AppRouter = typeof appRouter; const handler = createHTTPHandler({ middleware: cors(), router: appRouter, createContext() { return {}; }, }); const server = createServer((req, res) => { if (req.url.startsWith("/healthz")) { res.statusCode = 200; res.end("OK"); } else { handler(req, res); } }); server.listen(Number.parseInt(LISTEN_PORT));