diff --git a/server/src/index.ts b/server/src/index.ts index 75a30ad..f368a1a 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,12 +1,17 @@ -import _ from './env'; -import { publicProcedure, router } from './trpc.js'; -import { query } from './clickhouse.js'; -import { createHTTPHandler } from '@trpc/server/adapters/standalone'; -import cors from 'cors'; -import { Object as ObjectT, String as StringT, TSchema, Number as NumberT } from '@sinclair/typebox'; -import { TypeCompiler } from '@sinclair/typebox/compiler'; -import { TRPCError } from '@trpc/server'; -import { createServer } from 'http'; +import _ from "./env"; +import { publicProcedure, router } from "./trpc.js"; +import { query } from "./clickhouse.js"; +import { createHTTPHandler } from "@trpc/server/adapters/standalone"; +import cors from "cors"; +import { + Object as ObjectT, + String as StringT, + TSchema, + Number as NumberT, +} from "@sinclair/typebox"; +import { TypeCompiler } from "@sinclair/typebox/compiler"; +import { TRPCError } from "@trpc/server"; +import { createServer } from "http"; /** * Generate a TRPC-compatible validator function given a Typebox schema. @@ -15,94 +20,128 @@ import { createServer } from 'http'; * @returns A TRPC-compatible validator function */ export function RpcType(schema: T) { - const check = TypeCompiler.Compile(schema) + 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' }) - } + 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_contracts - `)) - .map(({symbol})=>symbol); - }), + getAvailableUnderlyings: publicProcedure.query(async (opts) => { + // return (await query<{symbol:string}>(` + // SELECT DISTINCT(symbol) as symbol FROM option_contracts + // `)) + // .map(({symbol})=>symbol); + return ["GOOGL", "AAPL", "NFLX", "AMD", "MSFT"]; + }), getAvailableAsOfDates: publicProcedure - .input(RpcType(ObjectT({underlying:StringT()}))) + .input(RpcType(ObjectT({ underlying: StringT() }))) .query(async (opts) => { const underlying = opts.input.underlying; - return (await query<{asOfDate:string}>(` + return ( + await query<{ asOfDate: string }>(` SELECT DISTINCT(asOfDate) as asOfDate FROM option_contracts WHERE symbol = '${underlying}' - `)) - .map(({asOfDate})=>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}>(` + .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) FROM option_contracts WHERE symbol = '${underlying}' AND asOfDate = '${asOfDate}' - `)) - .map(({expirationDate})=>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}>(` + .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) FROM option_contracts WHERE symbol = '${underlying}' AND asOfDate = '${asOfDate}' AND expirationDate = '${expirationDate}' - `)) - .map(({strike})=>strike); + `) + ).map(({ strike }) => strike); }), getOpensForUnderlying: publicProcedure - .input(RpcType(ObjectT({ - underlying:StringT({maxLength:5}) - }))) - .query(async (opts)=>{ - const {underlying} = opts.input; - return (await query<[number,number]>(` + .input( + RpcType( + ObjectT({ + underlying: StringT({ maxLength: 5 }), + }) + ) + ) + .query(async (opts) => { + const { underlying } = opts.input; + return ( + await query<[number, number]>( + ` SELECT toUnixTimestamp(tsStart), open FROM stock_aggregates WHERE symbol = '${underlying}' ORDER BY tsStart ASC - `,'JSONCompactEachRow')) - .reduce((columns, row)=>{ columns[0].push(row[0]); columns[1].push(row[1]); return columns; },[[],[]]); + `, + "JSONCompactEachRow" + ) + ).reduce( + (columns, row) => { + columns[0].push(row[0]); + columns[1].push(row[1]); + return columns; + }, + [[], []] + ); }), 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<[number,number]>(` + .input( + RpcType( + ObjectT({ + underlying: StringT({ maxLength: 5 }), + expirationDate: StringT(), + strike: NumberT(), + }) + ) + ) + .query(async (opts) => { + const { underlying, expirationDate, strike } = opts.input; + return ( + await query<[number, number]>( + ` SELECT toUnixTimestamp(tsStart), open @@ -112,20 +151,41 @@ const appRouter = router({ AND strike = ${strike} AND optionType = 'call' ORDER BY tsStart ASC - `,'JSONCompactEachRow')) - .reduce((columns, row)=>{ columns[0].push(row[0]); columns[1].push(row[1]); return columns; },[[],[]]); + `, + "JSONCompactEachRow" + ) + ).reduce( + (columns, row) => { + columns[0].push(row[0]); + columns[1].push(row[1]); + return columns; + }, + [[], []] + ); }), 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]>(` + .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 @@ -135,18 +195,32 @@ const appRouter = router({ 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; },[[],[]]); + `, + "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)=>{ + .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]>(` + return await query<[number, number]>( + ` SELECT toUnixTimestamp(tsStart) as x, open as y @@ -155,21 +229,36 @@ const appRouter = router({ AND tsStart >= '${lookbackPeriodStart} 00:00:00' AND tsStart <= '${lookbackPeriodEnd} 00:00:00' ORDER BY x ASC - `,'JSONEachRow')); + `, + "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]>(` + .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, calendarPrice as y @@ -181,19 +270,34 @@ const appRouter = router({ AND daysBetweenFrontAndBackExpiration = ${daysBetweenFrontAndBackExpiration} AND tsStart >= '${lookbackPeriodStart} 00:00:00' AND tsStart <= '${lookbackPeriodEnd} 00:00:00' - `,'JSONEachRow')); + `, + "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]>(` + .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]>( + ` SELECT FLOOR(strikePercentageFromUnderlyingPrice, 1) as x, calendarPrice as y @@ -206,7 +310,9 @@ const appRouter = router({ AND tsStart >= '${lookbackPeriodStart} 00:00:00' AND tsStart <= '${lookbackPeriodEnd} 00:00:00' ORDER BY x ASC - `,'JSONEachRow')); + `, + "JSONEachRow" + ); }), }); @@ -214,7 +320,6 @@ const appRouter = router({ // NOT the router itself. export type AppRouter = typeof appRouter; - const handler = createHTTPHandler({ middleware: cors(), router: appRouter, @@ -223,14 +328,13 @@ const handler = createHTTPHandler({ }, }); -const server = createServer((req, res)=>{ - if(req.url.startsWith("/healthz")){ +const server = createServer((req, res) => { + if (req.url.startsWith("/healthz")) { res.statusCode = 200; res.end("OK"); - } - else{ + } else { handler(req, res); } }); - -server.listen(parseInt(process.env.LISTEN_PORT) || 3005); \ No newline at end of file + +server.listen(parseInt(process.env.LISTEN_PORT) || 3005);