fully adopt @mui/material; refactor
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { signal, computed } from "@preact/signals";
|
||||
import { useEffect } from "preact/hooks";
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
import { trpc } from "../trpc.js";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
@@ -17,102 +17,42 @@ import {
|
||||
TextField,
|
||||
Select,
|
||||
MenuItem,
|
||||
InputLabel,
|
||||
FormControl,
|
||||
Paper,
|
||||
Button,
|
||||
Popper,
|
||||
ClickAwayListener,
|
||||
Slider,
|
||||
Box,
|
||||
} 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 availableUnderlyings = signal([]);
|
||||
const underlying = signal(null);
|
||||
|
||||
const daysToFrontExpiration = signal(14);
|
||||
|
||||
const daysBetweenFrontAndBackExpiration = signal(14);
|
||||
|
||||
const strikePercentageFromUnderlyingPrice = signal(1.4);
|
||||
const strikePercentageFromUnderlyingPriceRadius = signal(0.05);
|
||||
|
||||
const exitToFrontExpiration = signal(2);
|
||||
|
||||
const stockPriceChartData = signal([]);
|
||||
|
||||
const similarCalendarPriceChartData = signal([]);
|
||||
|
||||
const calendarExitPriceChartData = signal([]);
|
||||
|
||||
const lookbackPeriodStart = signal("2022-01-01");
|
||||
const lookbackPeriodEnd = signal("2024-01-01");
|
||||
|
||||
const maxChartPrice = computed(() =>
|
||||
Math.max(
|
||||
Math.max.apply(
|
||||
null,
|
||||
similarCalendarPriceChartData.value.map((d) => d.y)
|
||||
),
|
||||
Math.max.apply(
|
||||
null,
|
||||
calendarExitPriceChartData.value.map((d) => d.y)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const maxN = computed(() =>
|
||||
Math.max.apply(
|
||||
null,
|
||||
calendarExitPriceChartData.value.map((d) => d.n)
|
||||
)
|
||||
);
|
||||
|
||||
const refreshStockPriceChartData = () => {
|
||||
stockPriceChartData.value = [];
|
||||
trpc.StockPriceChart.getChartData
|
||||
.query({
|
||||
underlying: underlying.value,
|
||||
lookbackPeriodStart: lookbackPeriodStart.value,
|
||||
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
||||
})
|
||||
.then((getChartDataResponse) => {
|
||||
stockPriceChartData.value = getChartDataResponse;
|
||||
});
|
||||
};
|
||||
const refreshSimilarCalendarPriceChartData = () => {
|
||||
similarCalendarPriceChartData.value = [];
|
||||
trpc.SimilarCalendarPriceChart.getChartData
|
||||
.query({
|
||||
underlying: underlying.value,
|
||||
daysToFrontExpiration: daysToFrontExpiration.value,
|
||||
daysBetweenFrontAndBackExpiration:
|
||||
daysBetweenFrontAndBackExpiration.value,
|
||||
strikePercentageFromUnderlyingPriceRangeMin:
|
||||
strikePercentageFromUnderlyingPrice.value -
|
||||
strikePercentageFromUnderlyingPriceRadius.value,
|
||||
strikePercentageFromUnderlyingPriceRangeMax:
|
||||
strikePercentageFromUnderlyingPrice.value +
|
||||
strikePercentageFromUnderlyingPriceRadius.value,
|
||||
lookbackPeriodStart: lookbackPeriodStart.value,
|
||||
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
||||
})
|
||||
.then((getChartDataResponse) => {
|
||||
similarCalendarPriceChartData.value = getChartDataResponse;
|
||||
});
|
||||
};
|
||||
const refreshcalendarExitPriceChartData = () => {
|
||||
calendarExitPriceChartData.value = [];
|
||||
trpc.CalendarExitPriceChart.getChartData
|
||||
.query({
|
||||
underlying: underlying.value,
|
||||
daysToFrontExpiration: exitToFrontExpiration.value,
|
||||
daysBetweenFrontAndBackExpiration:
|
||||
daysBetweenFrontAndBackExpiration.value,
|
||||
lookbackPeriodStart: lookbackPeriodStart.value,
|
||||
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
||||
})
|
||||
.then((getChartDataResponse) => {
|
||||
calendarExitPriceChartData.value = getChartDataResponse;
|
||||
});
|
||||
};
|
||||
const handleInit = () => {
|
||||
trpc.CalendarCharacteristicsForm.getAvailableUnderlyings
|
||||
.query()
|
||||
@@ -124,74 +64,6 @@ const handleInit = () => {
|
||||
refreshcalendarExitPriceChartData();
|
||||
});
|
||||
};
|
||||
const handleUnderlyingChange = (e) => {
|
||||
if (underlying.value !== e.target.value) {
|
||||
underlying.value = e.target.value;
|
||||
refreshStockPriceChartData();
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
refreshcalendarExitPriceChartData();
|
||||
}
|
||||
};
|
||||
const handleDaysToFrontExpirationChange = (e) => {
|
||||
if (daysToFrontExpiration.value !== Number.parseInt(e.target.value)) {
|
||||
daysToFrontExpiration.value = Number.parseInt(e.target.value);
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
}
|
||||
};
|
||||
const handleDaysBetweenFrontAndBackExpirationChange = (e) => {
|
||||
if (
|
||||
daysBetweenFrontAndBackExpiration.value !== Number.parseInt(e.target.value)
|
||||
) {
|
||||
daysBetweenFrontAndBackExpiration.value = Number.parseInt(e.target.value);
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
refreshcalendarExitPriceChartData();
|
||||
}
|
||||
};
|
||||
const handleStrikePercentageFromUnderlyingPriceChange = (e) => {
|
||||
if (
|
||||
strikePercentageFromUnderlyingPrice.value !==
|
||||
Number.parseFloat(e.target.value)
|
||||
) {
|
||||
strikePercentageFromUnderlyingPrice.value = Number.parseFloat(
|
||||
e.target.value
|
||||
);
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
}
|
||||
};
|
||||
const handleStrikePercentageFromUnderlyingPriceRadiusChange = (e) => {
|
||||
if (
|
||||
strikePercentageFromUnderlyingPriceRadius.value !==
|
||||
Number.parseFloat(e.target.value)
|
||||
) {
|
||||
strikePercentageFromUnderlyingPriceRadius.value = Number.parseFloat(
|
||||
e.target.value
|
||||
);
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
}
|
||||
};
|
||||
const handleExitToFrontExpirationChange = (e) => {
|
||||
if (exitToFrontExpiration.value !== Number.parseInt(e.target.value)) {
|
||||
exitToFrontExpiration.value = Number.parseInt(e.target.value);
|
||||
refreshcalendarExitPriceChartData();
|
||||
}
|
||||
};
|
||||
|
||||
const handleLookbackPeriodStartChange = (e) => {
|
||||
if (lookbackPeriodStart.value !== e.target.value) {
|
||||
lookbackPeriodStart.value = e.target.value;
|
||||
refreshStockPriceChartData();
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
refreshcalendarExitPriceChartData();
|
||||
}
|
||||
};
|
||||
const handleLookbackPeriodEndChange = (e) => {
|
||||
if (lookbackPeriodEnd.value !== e.target.value) {
|
||||
lookbackPeriodEnd.value = e.target.value;
|
||||
refreshStockPriceChartData();
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
refreshcalendarExitPriceChartData();
|
||||
}
|
||||
};
|
||||
|
||||
export function HistoricalCalendarPrices() {
|
||||
useEffect(handleInit, []);
|
||||
@@ -201,113 +73,36 @@ export function HistoricalCalendarPrices() {
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Historical Calendar Prices
|
||||
<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} md={6}>
|
||||
<Paper elevation={3} sx={{ p: 3 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Available Underlyings</InputLabel>
|
||||
<Select
|
||||
value={underlying.value || ""}
|
||||
onChange={handleUnderlyingChange}
|
||||
label="Available Underlyings"
|
||||
>
|
||||
{availableUnderlyings.value.map((underlying) => (
|
||||
<MenuItem key={underlying} value={underlying}>
|
||||
{underlying}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Now-to-Front-Month Days to Expiration"
|
||||
type="number"
|
||||
value={daysToFrontExpiration.value}
|
||||
onChange={handleDaysToFrontExpirationChange}
|
||||
InputProps={{ endAdornment: "Days" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Front-to-Back-Month Days to Expiration Difference"
|
||||
type="number"
|
||||
value={daysBetweenFrontAndBackExpiration.value}
|
||||
onChange={handleDaysBetweenFrontAndBackExpirationChange}
|
||||
InputProps={{ endAdornment: "Days Difference" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Strike % From Underlying Price"
|
||||
type="number"
|
||||
value={strikePercentageFromUnderlyingPrice.value}
|
||||
onChange={handleStrikePercentageFromUnderlyingPriceChange}
|
||||
InputProps={{ endAdornment: "%" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Strike % Radius"
|
||||
type="number"
|
||||
value={strikePercentageFromUnderlyingPriceRadius.value}
|
||||
onChange={
|
||||
handleStrikePercentageFromUnderlyingPriceRadiusChange
|
||||
}
|
||||
InputProps={{ endAdornment: "%" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Exit-to-Front-Month Days to Expiration"
|
||||
type="number"
|
||||
value={exitToFrontExpiration.value}
|
||||
onChange={handleExitToFrontExpirationChange}
|
||||
InputProps={{ endAdornment: "Days" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Lookback Period Start"
|
||||
type="date"
|
||||
value={lookbackPeriodStart.value}
|
||||
onChange={(e) =>
|
||||
handleLookbackPeriodStartChange({
|
||||
target: { value: e.target.value },
|
||||
})
|
||||
}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Lookback Period End"
|
||||
type="date"
|
||||
value={lookbackPeriodEnd.value}
|
||||
onChange={(e) =>
|
||||
handleLookbackPeriodEndChange({
|
||||
target: { value: e.target.value },
|
||||
})
|
||||
}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper elevation={3} sx={{ p: 3, height: "100%" }}>
|
||||
<Grid item xs={12}>
|
||||
<Paper elevation={3} sx={{ p: 3, minHeight: "28em", height: "100%" }}>
|
||||
{underlying.value !== null &&
|
||||
stockPriceChartData.value.length > 0 ? (
|
||||
<Scatter
|
||||
@@ -371,8 +166,8 @@ export function HistoricalCalendarPrices() {
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper elevation={3} sx={{ p: 3 }}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper elevation={3} sx={{ p: 3, minHeight: "28em" }}>
|
||||
{underlying.value !== null &&
|
||||
similarCalendarPriceChartData.value.length > 0 ? (
|
||||
<Scatter
|
||||
@@ -432,8 +227,8 @@ export function HistoricalCalendarPrices() {
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper elevation={3} sx={{ p: 3 }}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper elevation={3} sx={{ p: 3, minHeight: "28em" }}>
|
||||
{underlying.value !== null &&
|
||||
similarCalendarPriceChartData.value.length > 0 ? (
|
||||
<Scatter
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import TextField from "@mui/material/TextField";
|
||||
import {
|
||||
refreshcalendarExitPriceChartData,
|
||||
refreshSimilarCalendarPriceChartData,
|
||||
} from "./actions";
|
||||
import { daysBetweenFrontAndBackExpiration } from "./state";
|
||||
import { EditableValue } from "./EditableValue";
|
||||
|
||||
const handleDaysBetweenFrontAndBackExpirationChange = (e) => {
|
||||
if (
|
||||
daysBetweenFrontAndBackExpiration.value !== Number.parseInt(e.target.value)
|
||||
) {
|
||||
daysBetweenFrontAndBackExpiration.value = Number.parseInt(e.target.value);
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
refreshcalendarExitPriceChartData();
|
||||
}
|
||||
};
|
||||
|
||||
function DaysBetweenFrontAndBackExpirationChooser() {
|
||||
return (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Front-to-Back-Month Days to Expiration Difference"
|
||||
type="number"
|
||||
value={daysBetweenFrontAndBackExpiration.value}
|
||||
onChange={handleDaysBetweenFrontAndBackExpirationChange}
|
||||
InputProps={{ endAdornment: "Days Difference" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditableDaysBetweenFrontAndBackExpiration() {
|
||||
return (
|
||||
<EditableValue text={daysBetweenFrontAndBackExpiration.value}>
|
||||
<DaysBetweenFrontAndBackExpirationChooser />
|
||||
</EditableValue>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { refreshSimilarCalendarPriceChartData } from "./actions";
|
||||
import { EditableValue } from "./EditableValue";
|
||||
import { daysToFrontExpiration } from "./state";
|
||||
|
||||
const handleDaysToFrontExpirationChange = (e) => {
|
||||
if (daysToFrontExpiration.value !== Number.parseInt(e.target.value)) {
|
||||
daysToFrontExpiration.value = Number.parseInt(e.target.value);
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
}
|
||||
};
|
||||
|
||||
function DaysToFrontExpirationChooser() {
|
||||
return (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Now-to-Front-Month Days to Expiration"
|
||||
type="number"
|
||||
value={daysToFrontExpiration.value}
|
||||
onChange={handleDaysToFrontExpirationChange}
|
||||
InputProps={{ endAdornment: "Days" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditableDaysToFrontExpiration() {
|
||||
return (
|
||||
<EditableValue text={daysToFrontExpiration.value}>
|
||||
<DaysToFrontExpirationChooser />
|
||||
</EditableValue>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { EditableValue } from "./EditableValue";
|
||||
import { exitToFrontExpiration } from "./state";
|
||||
import { refreshcalendarExitPriceChartData } from "./actions";
|
||||
|
||||
const handleExitToFrontExpirationChange = (e) => {
|
||||
if (exitToFrontExpiration.value !== Number.parseInt(e.target.value)) {
|
||||
exitToFrontExpiration.value = Number.parseInt(e.target.value);
|
||||
refreshcalendarExitPriceChartData();
|
||||
}
|
||||
};
|
||||
|
||||
function ExitToFrontExpirationChooser() {
|
||||
return (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Exit-to-Front-Month Days to Expiration"
|
||||
type="number"
|
||||
value={exitToFrontExpiration.value}
|
||||
onChange={handleExitToFrontExpirationChange}
|
||||
InputProps={{ endAdornment: "Days" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditableExitToFrontExpiration() {
|
||||
return (
|
||||
<EditableValue text={exitToFrontExpiration.value}>
|
||||
<ExitToFrontExpirationChooser />
|
||||
</EditableValue>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import TextField from "@mui/material/TextField";
|
||||
import {
|
||||
refreshcalendarExitPriceChartData,
|
||||
refreshSimilarCalendarPriceChartData,
|
||||
refreshStockPriceChartData,
|
||||
} from "./actions";
|
||||
import { lookbackPeriodEnd } from "./state";
|
||||
import { EditableValue } from "./EditableValue";
|
||||
|
||||
const handleLookbackPeriodEndChange = (e) => {
|
||||
if (lookbackPeriodEnd.value !== e.target.value) {
|
||||
lookbackPeriodEnd.value = e.target.value;
|
||||
refreshStockPriceChartData();
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
refreshcalendarExitPriceChartData();
|
||||
}
|
||||
};
|
||||
|
||||
function LookbackPeriodEndChooser() {
|
||||
return (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Lookback Period End"
|
||||
type="date"
|
||||
value={lookbackPeriodEnd.value}
|
||||
onChange={handleLookbackPeriodEndChange}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditableLookbackPeriodEnd() {
|
||||
return (
|
||||
<EditableValue text={lookbackPeriodEnd.value}>
|
||||
<LookbackPeriodEndChooser />
|
||||
</EditableValue>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import TextField from "@mui/material/TextField";
|
||||
import {
|
||||
refreshcalendarExitPriceChartData,
|
||||
refreshSimilarCalendarPriceChartData,
|
||||
refreshStockPriceChartData,
|
||||
} from "./actions";
|
||||
import { lookbackPeriodStart } from "./state";
|
||||
import { EditableValue } from "./EditableValue";
|
||||
|
||||
const handleLookbackPeriodStartChange = (e) => {
|
||||
if (lookbackPeriodStart.value !== e.target.value) {
|
||||
lookbackPeriodStart.value = e.target.value;
|
||||
refreshStockPriceChartData();
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
refreshcalendarExitPriceChartData();
|
||||
}
|
||||
};
|
||||
|
||||
function LookbackPeriodStartChooser() {
|
||||
return (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Lookback Period Start"
|
||||
type="date"
|
||||
value={lookbackPeriodStart.value}
|
||||
onChange={handleLookbackPeriodStartChange}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditableLookbackPeriodStart() {
|
||||
return (
|
||||
<EditableValue text={lookbackPeriodStart.value}>
|
||||
<LookbackPeriodStartChooser />
|
||||
</EditableValue>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import { EditableValue } from "./EditableValue";
|
||||
import {
|
||||
strikePercentageFromUnderlyingPrice,
|
||||
strikePercentageFromUnderlyingPriceRadius,
|
||||
} from "./state";
|
||||
import Slider from "@mui/material/Slider";
|
||||
import { refreshSimilarCalendarPriceChartData } from "./actions";
|
||||
|
||||
function StrikePercentageFromUnderlyingPriceChooser() {
|
||||
return (
|
||||
<Slider
|
||||
fullWidth
|
||||
label="Strike % From Underlying Price"
|
||||
value={strikePercentageFromUnderlyingPrice.value}
|
||||
valueLabelDisplay="on"
|
||||
min={0}
|
||||
max={10}
|
||||
step={0.1}
|
||||
onChange={(e, value) => {
|
||||
strikePercentageFromUnderlyingPrice.value = value as number;
|
||||
}}
|
||||
onChangeCommitted={(e, value) => {
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
}}
|
||||
InputProps={{ endAdornment: "%" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function StrikePercentageFromUnderlyingPriceRadiusChooser() {
|
||||
return (
|
||||
<Slider
|
||||
fullWidth
|
||||
label="Strike % Radius"
|
||||
value={strikePercentageFromUnderlyingPriceRadius.value}
|
||||
valueLabelDisplay="on"
|
||||
min={0}
|
||||
max={0.5}
|
||||
step={0.05}
|
||||
onChange={(e, value) => {
|
||||
strikePercentageFromUnderlyingPriceRadius.value = value as number;
|
||||
}}
|
||||
onChangeCommitted={(e, value) => {
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
}}
|
||||
InputProps={{ endAdornment: "%" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/** This is its own component so that sliding the slider with the mouse is
|
||||
* smoother. Preact detects reads from the "slider" signal values, and
|
||||
* associates them with the component that read them and redraws that component.
|
||||
* If this was not its own component, it would redraw the entire UI. It was very
|
||||
* slow. */
|
||||
export function EditableStrike() {
|
||||
return (
|
||||
<EditableValue
|
||||
text={`${strikePercentageFromUnderlyingPrice.value.toFixed(
|
||||
1
|
||||
)}±${strikePercentageFromUnderlyingPriceRadius.value.toFixed(2)}`}
|
||||
>
|
||||
<Box sx={{ minWidth: "20em" }}>
|
||||
<StrikePercentageFromUnderlyingPriceChooser />
|
||||
<StrikePercentageFromUnderlyingPriceRadiusChooser />
|
||||
</Box>
|
||||
</EditableValue>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import Select from "@mui/material/Select";
|
||||
import { EditableValue } from "./EditableValue";
|
||||
import { availableUnderlyings, underlying } from "./state";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import {
|
||||
refreshcalendarExitPriceChartData,
|
||||
refreshSimilarCalendarPriceChartData,
|
||||
refreshStockPriceChartData,
|
||||
} from "./actions";
|
||||
|
||||
const handleUnderlyingChange = (e) => {
|
||||
if (underlying.value !== e.target.value) {
|
||||
underlying.value = e.target.value;
|
||||
refreshStockPriceChartData();
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
refreshcalendarExitPriceChartData();
|
||||
}
|
||||
};
|
||||
|
||||
function UnderlyingChooser() {
|
||||
return (
|
||||
<Select
|
||||
value={underlying.value || ""}
|
||||
onChange={handleUnderlyingChange}
|
||||
label="Available Underlyings"
|
||||
>
|
||||
{availableUnderlyings.value.map((underlying) => (
|
||||
<MenuItem key={underlying} value={underlying}>
|
||||
{underlying}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditableUnderlying() {
|
||||
return (
|
||||
<EditableValue text={underlying.value}>
|
||||
<UnderlyingChooser />
|
||||
</EditableValue>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import Button from "@mui/material/Button";
|
||||
import { isPopperOpen, popperAnchorEl, popperContent } from "./state";
|
||||
|
||||
export function EditableValue({ text, children }) {
|
||||
return (
|
||||
<Button
|
||||
variant="text"
|
||||
size="large"
|
||||
sx={{
|
||||
textDecoration: "underline",
|
||||
textUnderlineOffset: "3px",
|
||||
fontSize: "1.0em",
|
||||
}}
|
||||
onClick={(e) => {
|
||||
// stop propagation so it's not caught by the ClickAwayListener:
|
||||
e.stopPropagation();
|
||||
if (isPopperOpen.value === false) {
|
||||
isPopperOpen.value = true;
|
||||
popperAnchorEl.value = e.currentTarget;
|
||||
popperContent.value = children;
|
||||
} else {
|
||||
isPopperOpen.value = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { trpc } from "../../trpc";
|
||||
import {
|
||||
calendarExitPriceChartData,
|
||||
daysBetweenFrontAndBackExpiration,
|
||||
daysToFrontExpiration,
|
||||
exitToFrontExpiration,
|
||||
lookbackPeriodEnd,
|
||||
lookbackPeriodStart,
|
||||
similarCalendarPriceChartData,
|
||||
stockPriceChartData,
|
||||
strikePercentageFromUnderlyingPrice,
|
||||
strikePercentageFromUnderlyingPriceRadius,
|
||||
underlying,
|
||||
} from "./state";
|
||||
|
||||
export const refreshStockPriceChartData = () => {
|
||||
stockPriceChartData.value = [];
|
||||
trpc.StockPriceChart.getChartData
|
||||
.query({
|
||||
underlying: underlying.value,
|
||||
lookbackPeriodStart: lookbackPeriodStart.value,
|
||||
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
||||
})
|
||||
.then((getChartDataResponse) => {
|
||||
stockPriceChartData.value = getChartDataResponse;
|
||||
});
|
||||
};
|
||||
export const refreshSimilarCalendarPriceChartData = () => {
|
||||
similarCalendarPriceChartData.value = [];
|
||||
trpc.SimilarCalendarPriceChart.getChartData
|
||||
.query({
|
||||
underlying: underlying.value,
|
||||
daysToFrontExpiration: daysToFrontExpiration.value,
|
||||
daysBetweenFrontAndBackExpiration:
|
||||
daysBetweenFrontAndBackExpiration.value,
|
||||
strikePercentageFromUnderlyingPriceRangeMin:
|
||||
strikePercentageFromUnderlyingPrice.value -
|
||||
strikePercentageFromUnderlyingPriceRadius.value,
|
||||
strikePercentageFromUnderlyingPriceRangeMax:
|
||||
strikePercentageFromUnderlyingPrice.value +
|
||||
strikePercentageFromUnderlyingPriceRadius.value,
|
||||
lookbackPeriodStart: lookbackPeriodStart.value,
|
||||
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
||||
})
|
||||
.then((getChartDataResponse) => {
|
||||
similarCalendarPriceChartData.value = getChartDataResponse;
|
||||
});
|
||||
};
|
||||
export const refreshcalendarExitPriceChartData = () => {
|
||||
calendarExitPriceChartData.value = [];
|
||||
trpc.CalendarExitPriceChart.getChartData
|
||||
.query({
|
||||
underlying: underlying.value,
|
||||
daysToFrontExpiration: exitToFrontExpiration.value,
|
||||
daysBetweenFrontAndBackExpiration:
|
||||
daysBetweenFrontAndBackExpiration.value,
|
||||
lookbackPeriodStart: lookbackPeriodStart.value,
|
||||
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
||||
})
|
||||
.then((getChartDataResponse) => {
|
||||
calendarExitPriceChartData.value = getChartDataResponse;
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { computed, signal } from "@preact/signals";
|
||||
|
||||
export const isPopperOpen = signal(false);
|
||||
export const popperAnchorEl = signal(null);
|
||||
export const popperContent = signal(null);
|
||||
|
||||
export const availableUnderlyings = signal([]);
|
||||
export const underlying = signal(null);
|
||||
|
||||
export const daysToFrontExpiration = signal(14);
|
||||
|
||||
export const daysBetweenFrontAndBackExpiration = signal(14);
|
||||
|
||||
export const strikePercentageFromUnderlyingPrice = signal(1.4);
|
||||
export const strikePercentageFromUnderlyingPriceRadius = signal(0.05);
|
||||
|
||||
export const exitToFrontExpiration = signal(2);
|
||||
|
||||
export const stockPriceChartData = signal([]);
|
||||
|
||||
export const similarCalendarPriceChartData = signal([]);
|
||||
|
||||
export const calendarExitPriceChartData = signal([]);
|
||||
|
||||
export const lookbackPeriodStart = signal("2022-01-01");
|
||||
export const lookbackPeriodEnd = signal("2024-01-01");
|
||||
|
||||
export const maxChartPrice = computed(() =>
|
||||
Math.max(
|
||||
Math.max.apply(
|
||||
null,
|
||||
similarCalendarPriceChartData.value.map((d) => d.y)
|
||||
),
|
||||
Math.max.apply(
|
||||
null,
|
||||
calendarExitPriceChartData.value.map((d) => d.y)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
export const maxN = computed(() =>
|
||||
Math.max.apply(
|
||||
null,
|
||||
calendarExitPriceChartData.value.map((d) => d.n)
|
||||
)
|
||||
);
|
||||
Reference in New Issue
Block a user