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
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>
|
|
);
|
|
} |