Compare commits

...

2 Commits

Author SHA1 Message Date
Avraham Sakal ead2e5bd1b refactor calendar optimizer page 2024-03-11 22:19:22 -04:00
Avraham Sakal 6d8ac852ab temporarily limit available underlyings 2024-03-11 21:59:40 -04:00
2 changed files with 330 additions and 194 deletions
+91 -59
View File
@@ -1,6 +1,6 @@
import { signal } from "@preact/signals";
import { useCallback, useEffect } from "preact/hooks";
import {trpc} from '../../trpc.js';
import { trpc } from "../../trpc.js";
const availableUnderlyings = signal([]);
const chosenUnderlying = signal(null);
@@ -17,55 +17,87 @@ const chosenStrike = signal(null);
const optionContractUplotData = signal([]);
const underlyingUplotData = signal([]);
export function CalendarOptimizer(){
const handleInit = useCallback(()=>{
trpc.getAvailableUnderlyings
.query()
.then((availableUnderlyingsResponse)=>{
availableUnderlyings.value = availableUnderlyingsResponse;
});
},[]);
const handleUnderlyingChange = useCallback((e)=>{
console.log(`Chose Underlying: ${e.target.value}`);
chosenUnderlying.value = e.target.value;
function chooseUnderlying(underlying: string) {
chosenUnderlying.value = underlying;
trpc.getAvailableAsOfDates
.query({underlying:e.target.value})
.then((getAvailableAsOfDatesResponse)=>{
.query({ underlying: underlying })
.then((getAvailableAsOfDatesResponse) => {
availableAsOfDates.value = getAvailableAsOfDatesResponse;
chooseAsOfDate(getAvailableAsOfDatesResponse[0]);
});
trpc.getOpensForUnderlying
.query({underlying:e.target.value})
.then((getOpensForUnderlyingResponse)=>{
.query({ underlying: underlying })
.then((getOpensForUnderlyingResponse) => {
underlyingUplotData.value = getOpensForUnderlyingResponse;
});
},[]);
const handleAsOfDateChange = useCallback((e)=>{
console.log(`Chose Date: ${e.target.value}`);
chosenAsOfDate.value = e.target.value;
}
function chooseAsOfDate(asOfDate: string) {
chosenAsOfDate.value = asOfDate;
trpc.getExpirationsForUnderlying
.query({underlying:chosenUnderlying.value, asOfDate:chosenAsOfDate.value})
.then((getExpirationsForUnderlyingResponse)=>{
.query({
underlying: chosenUnderlying.value,
asOfDate: chosenAsOfDate.value,
})
.then((getExpirationsForUnderlyingResponse) => {
availableExpirations.value = getExpirationsForUnderlyingResponse;
chooseExpiration(getExpirationsForUnderlyingResponse[0]);
});
},[]);
const handleExpirationChange = useCallback((e)=>{
console.log(`Chose Expiration: ${e.target.value}`);
chosenExpiration.value = e.target.value;
}
function chooseExpiration(expiration: string) {
chosenExpiration.value = expiration;
trpc.getStrikesForUnderlying
.query({underlying:chosenUnderlying.value, asOfDate:chosenAsOfDate.value, expirationDate: e.target.value})
.then((getStrikesForUnderlyingResponse)=>{
.query({
underlying: chosenUnderlying.value,
asOfDate: chosenAsOfDate.value,
expirationDate: expiration,
})
.then((getStrikesForUnderlyingResponse) => {
availableStrikes.value = getStrikesForUnderlyingResponse;
chooseStrike(getStrikesForUnderlyingResponse[0]);
});
},[]);
const handleStrikeChange = useCallback((e)=>{
console.log(`Chose Strike: ${e.target.value}`);
chosenStrike.value = e.target.value;
}
function chooseStrike(strike: string) {
chosenStrike.value = strike;
trpc.getOpensForOptionContract
.query({underlying:chosenUnderlying.value, expirationDate:chosenExpiration.value, strike:parseFloat(e.target.value)})
.then((getOpensForOptionContractResponse)=>{
.query({
underlying: chosenUnderlying.value,
expirationDate: chosenExpiration.value,
strike: parseFloat(strike),
})
.then((getOpensForOptionContractResponse) => {
optionContractUplotData.value = getOpensForOptionContractResponse;
});
},[]);
}
export function CalendarOptimizer() {
const handleInit = useCallback(() => {
trpc.getAvailableUnderlyings
.query()
.then((availableUnderlyingsResponse) => {
availableUnderlyings.value = availableUnderlyingsResponse;
// load first underlying in list:
chooseUnderlying(availableUnderlyingsResponse[0]);
});
}, []);
const handleUnderlyingChange = useCallback((e) => {
console.log(`Chose Underlying: ${e.target.value}`);
chooseUnderlying(e.target.value);
}, []);
const handleAsOfDateChange = useCallback((e) => {
console.log(`Chose Date: ${e.target.value}`);
chooseAsOfDate(e.target.value);
}, []);
const handleExpirationChange = useCallback((e) => {
console.log(`Chose Expiration: ${e.target.value}`);
chooseExpiration(e.target.value);
}, []);
const handleStrikeChange = useCallback((e) => {
console.log(`Chose Strike: ${e.target.value}`);
chooseStrike(e.target.value);
}, []);
useEffect(handleInit, []);
@@ -73,51 +105,51 @@ export function CalendarOptimizer(){
<div>
<div>
<label>Available Underlyings</label>
{
availableUnderlyings.value.length === 0
? "Loading..."
: <select onChange={handleUnderlyingChange}>
{availableUnderlyings.value.map((availableUnderlying)=>(
{availableUnderlyings.value.length === 0 ? (
"Loading..."
) : (
<select onChange={handleUnderlyingChange}>
{availableUnderlyings.value.map((availableUnderlying) => (
<option value={availableUnderlying}>{availableUnderlying}</option>
))}
</select>
}
)}
</div>
<div>
<label>Available "As-of" Dates</label>
{
availableAsOfDates.value.length === 0
? "Loading..."
: <select onChange={handleAsOfDateChange}>
{availableAsOfDates.value.map((availableAsOfDate)=>(
{availableAsOfDates.value.length === 0 ? (
"Loading..."
) : (
<select onChange={handleAsOfDateChange}>
{availableAsOfDates.value.map((availableAsOfDate) => (
<option value={availableAsOfDate}>{availableAsOfDate}</option>
))}
</select>
}
)}
</div>
<div>
<label>Available Expirations</label>
{
availableExpirations.value.length === 0
? "Loading..."
: <select onChange={handleExpirationChange}>
{availableExpirations.value.map((availableExpiration)=>(
{availableExpirations.value.length === 0 ? (
"Loading..."
) : (
<select onChange={handleExpirationChange}>
{availableExpirations.value.map((availableExpiration) => (
<option value={availableExpiration}>{availableExpiration}</option>
))}
</select>
}
)}
</div>
<div>
<label>Available Strikes</label>
{
availableStrikes.value.length === 0
? "Loading..."
: <select onChange={handleStrikeChange}>
{availableStrikes.value.map((availableStrike)=>(
{availableStrikes.value.length === 0 ? (
"Loading..."
) : (
<select onChange={handleStrikeChange}>
{availableStrikes.value.map((availableStrike) => (
<option value={availableStrike}>{availableStrike}</option>
))}
</select>
}
)}
</div>
{/* {chosenUnderlying.value!==null && underlyingUplotData.value.length>0
? <UPlot data={underlyingUplotData.value} title="Underlying" opts={uplotOpts}/>
+216 -112
View File
@@ -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<T extends TSchema>(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
@@ -110,22 +149,43 @@ const appRouter = router({
WHERE symbol = '${underlying}'
AND expirationDate = '${expirationDate}'
AND strike = ${strike}
AND optionType = 'call'
AND type = '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,12 +328,11 @@ 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);
}
});