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