You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

336 lines
11 KiB
TypeScript

import { signal } from "@preact/signals";
import { useCallback, useEffect } from "preact/hooks";
import { trpc } from "../trpc.js";
import {
Chart as ChartJS,
LinearScale,
CategoryScale,
PointElement,
Tooltip,
Title,
} from "chart.js";
import { Scatter } from "react-chartjs-2";
import {
Container,
Grid,
Typography,
FormControl,
InputLabel,
Select,
MenuItem,
Paper,
} from "@mui/material";
ChartJS.register(LinearScale, CategoryScale, PointElement, Tooltip, Title);
const availableUnderlyings = signal([]);
const chosenUnderlying = signal(null);
const availableAsOfDates = signal([]);
const chosenAsOfDate = signal(null);
const availableExpirations = signal([]);
const chosenExpiration = signal(null);
const availableStrikes = signal([]);
const chosenStrike = signal(null);
const optionContractUplotData = signal([]);
const underlyingUplotData = signal([]);
function chooseUnderlying(underlying: string) {
chosenUnderlying.value = underlying;
trpc.getAvailableAsOfDates
.query({ underlying: underlying })
.then((getAvailableAsOfDatesResponse) => {
availableAsOfDates.value = getAvailableAsOfDatesResponse;
chooseAsOfDate(getAvailableAsOfDatesResponse[0]);
});
trpc.getOpensForUnderlying
.query({ underlying: underlying })
.then((getOpensForUnderlyingResponse) => {
underlyingUplotData.value = getOpensForUnderlyingResponse;
});
}
function chooseAsOfDate(asOfDate: string) {
chosenAsOfDate.value = asOfDate;
trpc.getExpirationsForUnderlying
.query({
underlying: chosenUnderlying.value,
asOfDate: chosenAsOfDate.value,
})
.then((getExpirationsForUnderlyingResponse) => {
availableExpirations.value = getExpirationsForUnderlyingResponse;
chooseExpiration(getExpirationsForUnderlyingResponse[0]);
});
}
function chooseExpiration(expiration: string) {
chosenExpiration.value = expiration;
trpc.getStrikesForUnderlying
.query({
underlying: chosenUnderlying.value,
asOfDate: chosenAsOfDate.value,
expirationDate: expiration,
})
.then((getStrikesForUnderlyingResponse) => {
availableStrikes.value = getStrikesForUnderlyingResponse;
chooseStrike(getStrikesForUnderlyingResponse[0]);
});
}
function chooseStrike(strike: string) {
chosenStrike.value = strike;
trpc.getOpensForOptionContract
.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, []);
return (
<Container maxWidth="lg">
<Grid container spacing={4}>
<Grid item xs={12}>
<Typography variant="h4" gutterBottom>
Calendar Optimizer
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Paper elevation={3} sx={{ p: 3 }}>
<Grid container spacing={2}>
<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel>Available Underlyings</InputLabel>
<Select
value={chosenUnderlying.value || ""}
onChange={handleUnderlyingChange}
label="Available Underlyings"
>
{availableUnderlyings.value.map((underlying) => (
<MenuItem key={underlying} value={underlying}>
{underlying}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel>Available "As-of" Dates</InputLabel>
<Select
value={chosenAsOfDate.value || ""}
onChange={handleAsOfDateChange}
label='Available "As-of" Dates'
>
{availableAsOfDates.value.map((asOfDate) => (
<MenuItem key={asOfDate} value={asOfDate}>
{asOfDate}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel>Available Expirations</InputLabel>
<Select
value={chosenExpiration.value || ""}
onChange={handleExpirationChange}
label="Available Expirations"
>
{availableExpirations.value.map((expiration) => (
<MenuItem key={expiration} value={expiration}>
{expiration}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel>Available Strikes</InputLabel>
<Select
value={chosenStrike.value || ""}
onChange={handleStrikeChange}
label="Available Strikes"
>
{availableStrikes.value.map((strike) => (
<MenuItem key={strike} value={strike}>
{strike}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid>
</Paper>
</Grid>
<Grid item xs={12} md={6}>
<Paper elevation={3} sx={{ p: 3, height: '100%' }}>
{chosenUnderlying.value !== null && underlyingUplotData.value.length > 0 ? (
<Scatter
data={{
datasets: [
{
label: "Stock Open Price",
data: underlyingUplotData.value,
},
],
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time",
},
ticks: {
callback: function (value, index, ticks) {
return new Date((value as number) * 1000)
.toISOString()
.substring(0, 10);
},
},
},
y: {
beginAtZero: false,
ticks: {
callback: function (value, index, ticks) {
return "$" + value.toString();
},
},
},
},
elements: {
point: {
radius: 1,
borderWidth: 0,
},
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: "Stock Price",
},
},
animation: false,
maintainAspectRatio: false,
}}
/>
) : (
<Typography>Loading Chart...</Typography>
)}
</Paper>
</Grid>
<Grid item xs={12}>
<Paper elevation={3} sx={{ p: 3 }}>
{chosenUnderlying.value !== null &&
chosenAsOfDate.value !== null &&
chosenExpiration.value !== null &&
chosenStrike.value !== null &&
optionContractUplotData.value.length > 0 ? (
<Scatter
data={{
datasets: [
{
label: "Option Contract Open Price",
data: optionContractUplotData.value,
},
],
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time",
},
ticks: {
callback: function (value, index, ticks) {
return new Date((value as number) * 1000)
.toISOString()
.substring(0, 10);
},
},
},
y: {
beginAtZero: false,
ticks: {
callback: function (value, index, ticks) {
return "$" + value.toString();
},
},
},
},
elements: {
point: {
radius: 1,
borderWidth: 0,
},
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: "Option Contract Price",
},
},
animation: false,
maintainAspectRatio: false,
}}
/>
) : (
<Typography>Loading Chart...</Typography>
)}
</Paper>
</Grid>
</Grid>
</Container>
);
}