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.

301 lines
10 KiB
TypeScript

import { 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,
Paper,
Popper,
ClickAwayListener,
} from "@mui/material";
import {
availableUnderlyings,
calendarExitPriceChartData,
isPopperOpen,
lookbackPeriodEnd,
lookbackPeriodStart,
maxChartPrice,
maxN,
popperAnchorEl,
popperContent,
similarCalendarPriceChartData,
stockPriceChartData,
underlying,
} from "./HistoricalCalendarPrices/state.js";
import { EditableStrike } from "./HistoricalCalendarPrices/EditableStrike.js";
import {
refreshcalendarExitPriceChartData,
refreshSimilarCalendarPriceChartData,
refreshStockPriceChartData,
} from "./HistoricalCalendarPrices/actions.js";
import { EditableUnderlying } from "./HistoricalCalendarPrices/EditableUnderlying.js";
import { EditableDaysToFrontExpiration } from "./HistoricalCalendarPrices/EditableDaysToFrontExpiration.js";
import { EditableExitToFrontExpiration } from "./HistoricalCalendarPrices/EditableExitToFrontExpiration.js";
import { EditableDaysBetweenFrontAndBackExpiration } from "./HistoricalCalendarPrices/EditableDaysBetweenFrontAndBackExpiration.js";
import { EditableLookbackPeriodStart } from "./HistoricalCalendarPrices/EditableLookbackPeriodStart.js";
import { EditableLookbackPeriodEnd } from "./HistoricalCalendarPrices/EditableLookbackPeriodEnd.js";
ChartJS.register(LinearScale, CategoryScale, PointElement, Tooltip, Title);
const handleInit = () => {
trpc.CalendarCharacteristicsForm.getAvailableUnderlyings
.query()
.then((availableUnderlyingsResponse) => {
availableUnderlyings.value = availableUnderlyingsResponse;
underlying.value = availableUnderlyingsResponse[0];
refreshStockPriceChartData();
refreshSimilarCalendarPriceChartData();
refreshcalendarExitPriceChartData();
});
};
export function HistoricalCalendarPrices() {
useEffect(handleInit, []);
return (
<Container maxWidth="lg">
<Grid container spacing={4}>
<Grid item xs={12}>
<Typography variant="h4" gutterBottom>
<EditableUnderlying /> :
<EditableDaysBetweenFrontAndBackExpiration />
-Day Calendar @ <EditableStrike />
%-from-the-money
</Typography>
<Typography variant="h5" gutterBottom sx={{ pl: 1 }}>
Opening at <EditableDaysToFrontExpiration /> DTE, Closing at{" "}
<EditableExitToFrontExpiration />
DTE
</Typography>
<Typography variant="h5" gutterBottom>
<EditableLookbackPeriodStart />-
<EditableLookbackPeriodEnd />
</Typography>
<ClickAwayListener
onClickAway={() => {
isPopperOpen.value = false;
// refreshSimilarCalendarPriceChartData();
console.log("clicked away");
}}
>
<Popper open={isPopperOpen.value} anchorEl={popperAnchorEl.value}>
<Paper elevation={3} sx={{ p: 3 }}>
{popperContent.value}
</Paper>
</Popper>
</ClickAwayListener>
</Grid>
<Grid item xs={12}>
<Paper elevation={3} sx={{ p: 3, minHeight: "28em", height: "100%" }}>
{underlying.value !== null &&
stockPriceChartData.value.length > 0 ? (
<Scatter
data={{
datasets: [
{
label: "Stock Open Price",
data: stockPriceChartData.value,
},
],
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time",
},
ticks: {
callback: (value, index, ticks) =>
new Date((value as number) * 1000)
.toISOString()
.substring(0, 10),
},
min: new Date(lookbackPeriodStart.value).getTime() / 1000,
max: new Date(lookbackPeriodEnd.value).getTime() / 1000,
},
y: {
beginAtZero: false,
ticks: {
callback: (value, index, ticks) =>
`$${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,
events: [],
}}
/>
) : (
<Typography>Loading Chart...</Typography>
)}
</Paper>
</Grid>
<Grid item xs={12} md={6}>
<Paper elevation={3} sx={{ p: 3, minHeight: "28em" }}>
{underlying.value !== null &&
similarCalendarPriceChartData.value.length > 0 ? (
<Scatter
data={{
datasets: [
{
label: "Calendar Open Price",
data: similarCalendarPriceChartData.value,
},
],
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time",
},
ticks: {
callback: (value, index, ticks) =>
new Date((value as number) * 1000)
.toISOString()
.substring(0, 10),
},
min: new Date(lookbackPeriodStart.value).getTime() / 1000,
max: new Date(lookbackPeriodEnd.value).getTime() / 1000,
},
y: {
beginAtZero: true,
ticks: {
callback: (value, index, ticks) =>
`$${value.toString()}`,
},
min: 0,
max: maxChartPrice.value,
},
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: "Calendar Price (Under Like Conditions)",
},
},
animation: false,
maintainAspectRatio: false,
events: [],
}}
/>
) : (
<Typography>Loading Chart...</Typography>
)}
</Paper>
</Grid>
<Grid item xs={12} md={6}>
<Paper elevation={3} sx={{ p: 3, minHeight: "28em" }}>
{underlying.value !== null &&
similarCalendarPriceChartData.value.length > 0 ? (
<Scatter
data={{
datasets: [
{
label: "Calendar Exit Price",
data: calendarExitPriceChartData.value,
},
],
}}
options={{
scales: {
x: {
type: "linear",
beginAtZero: false,
title: {
display: true,
text: "%-From-the-Money",
},
ticks: {
callback: (value, index, ticks) =>
`${value.toString()}%`,
},
},
y: {
beginAtZero: true,
ticks: {
callback: (value, index, ticks) =>
`$${value.toString()}`,
},
min: 0,
max: maxChartPrice.value,
},
},
elements: {
point: {
borderWidth: 0,
backgroundColor: (context) => {
const n = (
context.raw as { x: number; y: number; n: number }
).n;
const alpha = n / maxN.value;
return `rgba(0, 0, 0, ${alpha})`;
},
},
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: [
"Calendar Prices at Exit",
"by %-age from-the-money",
],
},
},
animation: false,
maintainAspectRatio: false,
events: [],
}}
/>
) : (
<Typography>Loading Chart...</Typography>
)}
</Paper>
</Grid>
</Grid>
</Container>
);
}