Compare commits
12 Commits
694bb38536
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c83f2ce0a0 | |||
| 4d989f10eb | |||
| 906e63eb91 | |||
| 3732680d52 | |||
| 3c841f488c | |||
| b6cc25b769 | |||
| 14dff05785 | |||
| f05831b5f1 | |||
| b7f9d60715 | |||
| b2169e1da7 | |||
| aeac0e1042 | |||
| 904d2da84a |
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
FROM node:20-slim AS build
|
FROM node:20-slim AS build
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
RUN corepack enable
|
RUN npm install -g corepack@latest && corepack enable
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
# copy what's necessary to install dependencies:
|
# copy what's necessary to install dependencies:
|
||||||
COPY package.json pnpm-lock.yaml /app/
|
COPY package.json pnpm-lock.yaml /app/
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
LinearScale,
|
LinearScale,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
PointElement,
|
PointElement,
|
||||||
|
LineElement,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Title,
|
Title,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
@@ -16,6 +17,7 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
Popper,
|
Popper,
|
||||||
ClickAwayListener,
|
ClickAwayListener,
|
||||||
|
Stack,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import {
|
import {
|
||||||
availableUnderlyings,
|
availableUnderlyings,
|
||||||
@@ -38,13 +40,19 @@ import {
|
|||||||
refreshStockPriceChartData,
|
refreshStockPriceChartData,
|
||||||
} from "./HistoricalCalendarPrices/actions.js";
|
} from "./HistoricalCalendarPrices/actions.js";
|
||||||
import { EditableUnderlying } from "./HistoricalCalendarPrices/EditableUnderlying.js";
|
import { EditableUnderlying } from "./HistoricalCalendarPrices/EditableUnderlying.js";
|
||||||
import { EditableDaysToFrontExpiration } from "./HistoricalCalendarPrices/EditableDaysToFrontExpiration.js";
|
import { EditableOpenDTE } from "./HistoricalCalendarPrices/EditableOpenDTE.js";
|
||||||
import { EditableExitToFrontExpiration } from "./HistoricalCalendarPrices/EditableExitToFrontExpiration.js";
|
import { EditableExitDTE } from "./HistoricalCalendarPrices/EditableExitDTE.js";
|
||||||
import { EditableDaysBetweenFrontAndBackExpiration } from "./HistoricalCalendarPrices/EditableDaysBetweenFrontAndBackExpiration.js";
|
import { EditableSpan } from "./HistoricalCalendarPrices/EditableSpan.js";
|
||||||
import { EditableLookbackPeriodStart } from "./HistoricalCalendarPrices/EditableLookbackPeriodStart.js";
|
import { EditableLookbackPeriod } from "./HistoricalCalendarPrices/EditableLookbackPeriod.js";
|
||||||
import { EditableLookbackPeriodEnd } from "./HistoricalCalendarPrices/EditableLookbackPeriodEnd.js";
|
|
||||||
|
|
||||||
ChartJS.register(LinearScale, CategoryScale, PointElement, Tooltip, Title);
|
ChartJS.register(
|
||||||
|
LinearScale,
|
||||||
|
CategoryScale,
|
||||||
|
PointElement,
|
||||||
|
LineElement,
|
||||||
|
Tooltip,
|
||||||
|
Title
|
||||||
|
);
|
||||||
|
|
||||||
const handleInit = () => {
|
const handleInit = () => {
|
||||||
trpc.CalendarCharacteristicsForm.getAvailableUnderlyings
|
trpc.CalendarCharacteristicsForm.getAvailableUnderlyings
|
||||||
@@ -64,16 +72,16 @@ export function HistoricalCalendarPrices() {
|
|||||||
return (
|
return (
|
||||||
<Container maxWidth="lg">
|
<Container maxWidth="lg">
|
||||||
<Grid2 container spacing={4} columns={12}>
|
<Grid2 container spacing={4} columns={12}>
|
||||||
<Grid2 size={{ xs: 12 }}>
|
{/* <Grid2 size={{ xs: 12 }}>
|
||||||
|
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h4" gutterBottom>
|
||||||
<EditableUnderlying /> :
|
<EditableUnderlying /> :
|
||||||
<EditableDaysBetweenFrontAndBackExpiration />
|
<EditableSpan />
|
||||||
-Day Calendar @ <EditableStrike />
|
-Day Calendar @ <EditableStrike />
|
||||||
%-from-the-money
|
%-from-the-money
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h5" gutterBottom sx={{ pl: 1 }}>
|
<Typography variant="h5" gutterBottom sx={{ pl: 1 }}>
|
||||||
Opening at <EditableDaysToFrontExpiration /> DTE, Closing at{" "}
|
Opening at <EditableOpenDTE /> DTE, Closing at <EditableExitDTE />
|
||||||
<EditableExitToFrontExpiration />
|
|
||||||
DTE
|
DTE
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h5" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
@@ -93,72 +101,54 @@ export function HistoricalCalendarPrices() {
|
|||||||
</Paper>
|
</Paper>
|
||||||
</Popper>
|
</Popper>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>
|
||||||
</Grid2>
|
</Grid2> */}
|
||||||
|
|
||||||
<Grid2 size={{ xs: 12 }}>
|
<Grid2 size={{ xs: 12 }}>
|
||||||
<Paper elevation={3} sx={{ p: 3, minHeight: "28em", height: "100%" }}>
|
<Stack direction="row" spacing={2}>
|
||||||
{underlying.value !== null &&
|
<Typography gutterBottom minWidth={"8em"}>
|
||||||
stockPriceChartData.value.length > 0 ? (
|
Underlying
|
||||||
<Scatter
|
</Typography>
|
||||||
data={{
|
<EditableUnderlying />
|
||||||
datasets: [
|
</Stack>
|
||||||
{
|
<Stack direction="row" spacing={2}>
|
||||||
label: "Stock Open Price",
|
<Typography gutterBottom minWidth={"8em"}>
|
||||||
data: stockPriceChartData.value,
|
Open DTE
|
||||||
},
|
</Typography>
|
||||||
],
|
<EditableOpenDTE />
|
||||||
|
</Stack>
|
||||||
|
<Stack direction="row" spacing={2}>
|
||||||
|
<Typography gutterBottom minWidth={"8em"}>
|
||||||
|
Exit DTE
|
||||||
|
</Typography>
|
||||||
|
<EditableExitDTE />
|
||||||
|
</Stack>
|
||||||
|
<Stack direction="row" spacing={2}>
|
||||||
|
<Typography gutterBottom minWidth={"8em"}>
|
||||||
|
Span
|
||||||
|
</Typography>
|
||||||
|
<EditableSpan />
|
||||||
|
</Stack>
|
||||||
|
<Stack direction="row" spacing={2}>
|
||||||
|
<Typography gutterBottom minWidth={"8em"}>
|
||||||
|
Lookback Period
|
||||||
|
</Typography>
|
||||||
|
<EditableLookbackPeriod />
|
||||||
|
</Stack>
|
||||||
|
<ClickAwayListener
|
||||||
|
onClickAway={() => {
|
||||||
|
isPopperOpen.value = false;
|
||||||
|
// refreshSimilarCalendarPriceChartData();
|
||||||
|
console.log("clicked away");
|
||||||
}}
|
}}
|
||||||
options={{
|
>
|
||||||
scales: {
|
<Popper open={isPopperOpen.value} anchorEl={popperAnchorEl.value}>
|
||||||
x: {
|
<Paper elevation={3} sx={{ p: 3 }}>
|
||||||
title: {
|
{popperContent.value}
|
||||||
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>
|
</Paper>
|
||||||
|
</Popper>
|
||||||
|
</ClickAwayListener>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
<Grid2 size={{ xs: 12, md: 6 }}>
|
<Grid2 size={{ xs: 12, md: 6 }}>
|
||||||
<Paper elevation={3} sx={{ p: 3, minHeight: "28em" }}>
|
<Paper elevation={3} sx={{ p: 3, minHeight: "28em" }}>
|
||||||
{underlying.value !== null &&
|
{underlying.value !== null &&
|
||||||
@@ -223,7 +213,7 @@ export function HistoricalCalendarPrices() {
|
|||||||
<Grid2 size={{ xs: 12, md: 6 }}>
|
<Grid2 size={{ xs: 12, md: 6 }}>
|
||||||
<Paper elevation={3} sx={{ p: 3, minHeight: "28em" }}>
|
<Paper elevation={3} sx={{ p: 3, minHeight: "28em" }}>
|
||||||
{underlying.value !== null &&
|
{underlying.value !== null &&
|
||||||
similarCalendarPriceChartData.value.length > 0 ? (
|
calendarExitPriceChartData.value.length > 0 ? (
|
||||||
<Scatter
|
<Scatter
|
||||||
data={{
|
data={{
|
||||||
datasets: [
|
datasets: [
|
||||||
@@ -294,6 +284,76 @@ export function HistoricalCalendarPrices() {
|
|||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
<Grid2 size={{ 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={{
|
||||||
|
showLine: true,
|
||||||
|
normalized: true,
|
||||||
|
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: 2,
|
||||||
|
borderWidth: 0,
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: "Stock Price",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: false,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
events: [],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Typography>Loading Chart...</Typography>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|||||||
-38
@@ -1,38 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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,28 @@
|
|||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import { EditableValue } from "./EditableValue";
|
||||||
|
import { exitDTE } from "./state";
|
||||||
|
import { refreshcalendarExitPriceChartData } from "./actions";
|
||||||
|
import Slider from "@mui/material/Slider";
|
||||||
|
|
||||||
|
const handleExitDTEChange = (e) => {
|
||||||
|
if (exitDTE.value !== Number.parseInt(e.target.value)) {
|
||||||
|
exitDTE.value = Number.parseInt(e.target.value);
|
||||||
|
refreshcalendarExitPriceChartData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function EditableExitDTE() {
|
||||||
|
return (
|
||||||
|
<Slider
|
||||||
|
value={exitDTE.value}
|
||||||
|
onChange={handleExitDTEChange}
|
||||||
|
min={0}
|
||||||
|
max={5}
|
||||||
|
step={1}
|
||||||
|
valueLabelDisplay="on"
|
||||||
|
/>
|
||||||
|
// <EditableValue text={exitDTE.value}>
|
||||||
|
// <ExitToFrontExpirationChooser />
|
||||||
|
// </EditableValue>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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,66 @@
|
|||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import {
|
||||||
|
refreshcalendarExitPriceChartData,
|
||||||
|
refreshSimilarCalendarPriceChartData,
|
||||||
|
refreshStockPriceChartData,
|
||||||
|
} from "./actions";
|
||||||
|
import { lookbackPeriodEnd, lookbackPeriodStart } from "./state";
|
||||||
|
import Slider from "@mui/material/Slider";
|
||||||
|
|
||||||
|
const handleLookbackPeriodChange = (
|
||||||
|
e,
|
||||||
|
[newLookbackPeriodStart, newLookbackPeriodEnd]: [number, number]
|
||||||
|
) => {
|
||||||
|
const [lookbackPeriodStartUnixTime, lookbackPeriodEndUnixTime] = [
|
||||||
|
new Date(lookbackPeriodStart.value).getTime(),
|
||||||
|
new Date(lookbackPeriodEnd.value).getTime(),
|
||||||
|
];
|
||||||
|
if (lookbackPeriodStartUnixTime !== newLookbackPeriodStart) {
|
||||||
|
lookbackPeriodStart.value = new Date(newLookbackPeriodStart)
|
||||||
|
.toISOString()
|
||||||
|
.substring(0, 10);
|
||||||
|
refreshStockPriceChartData();
|
||||||
|
refreshSimilarCalendarPriceChartData();
|
||||||
|
refreshcalendarExitPriceChartData();
|
||||||
|
}
|
||||||
|
if (lookbackPeriodEndUnixTime !== newLookbackPeriodEnd) {
|
||||||
|
lookbackPeriodEnd.value = new Date(newLookbackPeriodEnd)
|
||||||
|
.toISOString()
|
||||||
|
.substring(0, 10);
|
||||||
|
refreshStockPriceChartData();
|
||||||
|
refreshSimilarCalendarPriceChartData();
|
||||||
|
refreshcalendarExitPriceChartData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const earliestDate = new Date("2022-03-07");
|
||||||
|
const DAY = 1000 * 60 * 60 * 24;
|
||||||
|
function addDays(date, days) {
|
||||||
|
const result = new Date(date);
|
||||||
|
result.setDate(result.getDate() + days);
|
||||||
|
return result.toISOString().substring(0, 10);
|
||||||
|
}
|
||||||
|
function daysBetween(date1, date2) {
|
||||||
|
return Math.round(Math.abs((date2.getTime() - date1.getTime()) / DAY));
|
||||||
|
}
|
||||||
|
export function EditableLookbackPeriod() {
|
||||||
|
return (
|
||||||
|
<Slider
|
||||||
|
value={[
|
||||||
|
new Date(lookbackPeriodStart.value).getTime(),
|
||||||
|
new Date(lookbackPeriodEnd.value).getTime(),
|
||||||
|
]}
|
||||||
|
onChange={handleLookbackPeriodChange}
|
||||||
|
valueLabelFormat={(unixTimeMs) =>
|
||||||
|
new Date(unixTimeMs).toISOString().substring(0, 10)
|
||||||
|
}
|
||||||
|
getAriaValueText={(unixTimeMs) =>
|
||||||
|
new Date(unixTimeMs).toISOString().substring(0, 10)
|
||||||
|
}
|
||||||
|
min={earliestDate.getTime()}
|
||||||
|
max={earliestDate.getTime() + 250 * DAY}
|
||||||
|
step={DAY}
|
||||||
|
valueLabelDisplay="on"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { refreshSimilarCalendarPriceChartData } from "./actions";
|
||||||
|
import { openDTE } from "./state";
|
||||||
|
import Slider from "@mui/material/Slider";
|
||||||
|
|
||||||
|
const handleOpenDTEChange = (e) => {
|
||||||
|
if (openDTE.value !== Number.parseInt(e.target.value)) {
|
||||||
|
openDTE.value = Number.parseInt(e.target.value);
|
||||||
|
refreshSimilarCalendarPriceChartData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function EditableOpenDTE() {
|
||||||
|
return (
|
||||||
|
<Slider
|
||||||
|
value={openDTE.value}
|
||||||
|
onChange={handleOpenDTEChange}
|
||||||
|
min={0}
|
||||||
|
max={5}
|
||||||
|
step={1}
|
||||||
|
valueLabelDisplay="on"
|
||||||
|
/>
|
||||||
|
// <EditableValue text={openDTE.value}>
|
||||||
|
// <DaysToFrontExpirationChooser />
|
||||||
|
// </EditableValue>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import {
|
||||||
|
refreshcalendarExitPriceChartData,
|
||||||
|
refreshSimilarCalendarPriceChartData,
|
||||||
|
} from "./actions";
|
||||||
|
import { span } from "./state";
|
||||||
|
import { EditableValue } from "./EditableValue";
|
||||||
|
import Slider from "@mui/material/Slider";
|
||||||
|
|
||||||
|
const handleSpanChange = (e) => {
|
||||||
|
if (span.value !== Number.parseInt(e.target.value)) {
|
||||||
|
span.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={span.value}
|
||||||
|
onChange={handleSpanChange}
|
||||||
|
InputProps={{ endAdornment: "Days Difference" }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EditableSpan() {
|
||||||
|
return (
|
||||||
|
<Slider
|
||||||
|
value={span.value}
|
||||||
|
onChange={handleSpanChange}
|
||||||
|
min={3}
|
||||||
|
max={45}
|
||||||
|
step={1}
|
||||||
|
valueLabelDisplay="on"
|
||||||
|
/>
|
||||||
|
// <EditableValue text={span.value}>
|
||||||
|
// <DaysBetweenFrontAndBackExpirationChooser />
|
||||||
|
// </EditableValue>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,24 +1,21 @@
|
|||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import { EditableValue } from "./EditableValue";
|
import { EditableValue } from "./EditableValue";
|
||||||
import {
|
import { moniness, moninessRadius } from "./state";
|
||||||
strikePercentageFromUnderlyingPrice,
|
|
||||||
strikePercentageFromUnderlyingPriceRadius,
|
|
||||||
} from "./state";
|
|
||||||
import Slider from "@mui/material/Slider";
|
import Slider from "@mui/material/Slider";
|
||||||
import { refreshSimilarCalendarPriceChartData } from "./actions";
|
import { refreshSimilarCalendarPriceChartData } from "./actions";
|
||||||
|
|
||||||
function StrikePercentageFromUnderlyingPriceChooser() {
|
function MoninessChooser() {
|
||||||
return (
|
return (
|
||||||
<Slider
|
<Slider
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Strike % From Underlying Price"
|
label="Strike % From Underlying Price"
|
||||||
value={strikePercentageFromUnderlyingPrice.value}
|
value={moniness.value}
|
||||||
valueLabelDisplay="on"
|
valueLabelDisplay="on"
|
||||||
min={0}
|
min={0}
|
||||||
max={10}
|
max={10}
|
||||||
step={0.1}
|
step={1}
|
||||||
onChange={(e, value) => {
|
onChange={(e, value) => {
|
||||||
strikePercentageFromUnderlyingPrice.value = value as number;
|
moniness.value = value as number;
|
||||||
}}
|
}}
|
||||||
onChangeCommitted={(e, value) => {
|
onChangeCommitted={(e, value) => {
|
||||||
refreshSimilarCalendarPriceChartData();
|
refreshSimilarCalendarPriceChartData();
|
||||||
@@ -28,18 +25,18 @@ function StrikePercentageFromUnderlyingPriceChooser() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function StrikePercentageFromUnderlyingPriceRadiusChooser() {
|
function MoninessRadiusChooser() {
|
||||||
return (
|
return (
|
||||||
<Slider
|
<Slider
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Strike % Radius"
|
label="Strike % Radius"
|
||||||
value={strikePercentageFromUnderlyingPriceRadius.value}
|
value={moninessRadius.value}
|
||||||
valueLabelDisplay="on"
|
valueLabelDisplay="on"
|
||||||
min={0}
|
min={0}
|
||||||
max={0.5}
|
max={10}
|
||||||
step={0.05}
|
step={1}
|
||||||
onChange={(e, value) => {
|
onChange={(e, value) => {
|
||||||
strikePercentageFromUnderlyingPriceRadius.value = value as number;
|
moninessRadius.value = value as number;
|
||||||
}}
|
}}
|
||||||
onChangeCommitted={(e, value) => {
|
onChangeCommitted={(e, value) => {
|
||||||
refreshSimilarCalendarPriceChartData();
|
refreshSimilarCalendarPriceChartData();
|
||||||
@@ -57,13 +54,11 @@ function StrikePercentageFromUnderlyingPriceRadiusChooser() {
|
|||||||
export function EditableStrike() {
|
export function EditableStrike() {
|
||||||
return (
|
return (
|
||||||
<EditableValue
|
<EditableValue
|
||||||
text={`${strikePercentageFromUnderlyingPrice.value.toFixed(
|
text={`${moniness.value.toFixed(1)}±${moninessRadius.value.toFixed(2)}`}
|
||||||
1
|
|
||||||
)}±${strikePercentageFromUnderlyingPriceRadius.value.toFixed(2)}`}
|
|
||||||
>
|
>
|
||||||
<Box sx={{ minWidth: "20em" }}>
|
<Box sx={{ minWidth: "20em" }}>
|
||||||
<StrikePercentageFromUnderlyingPriceChooser />
|
<MoninessChooser />
|
||||||
<StrikePercentageFromUnderlyingPriceRadiusChooser />
|
<MoninessRadiusChooser />
|
||||||
</Box>
|
</Box>
|
||||||
</EditableValue>
|
</EditableValue>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,19 +1,41 @@
|
|||||||
import { trpc } from "../../trpc";
|
import { trpc } from "../../trpc";
|
||||||
import {
|
import {
|
||||||
calendarExitPriceChartData,
|
calendarExitPriceChartData,
|
||||||
daysBetweenFrontAndBackExpiration,
|
span,
|
||||||
daysToFrontExpiration,
|
openDTE,
|
||||||
exitToFrontExpiration,
|
exitDTE,
|
||||||
lookbackPeriodEnd,
|
lookbackPeriodEnd,
|
||||||
lookbackPeriodStart,
|
lookbackPeriodStart,
|
||||||
similarCalendarPriceChartData,
|
similarCalendarPriceChartData,
|
||||||
stockPriceChartData,
|
stockPriceChartData,
|
||||||
strikePercentageFromUnderlyingPrice,
|
moniness,
|
||||||
strikePercentageFromUnderlyingPriceRadius,
|
moninessRadius,
|
||||||
underlying,
|
underlying,
|
||||||
} from "./state";
|
} from "./state";
|
||||||
|
|
||||||
export const refreshStockPriceChartData = () => {
|
function debounce(func, wait) {
|
||||||
|
let timeout;
|
||||||
|
return function () {
|
||||||
|
const context = this;
|
||||||
|
const args = arguments;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => func.apply(context, args), wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function throttle(func, limit) {
|
||||||
|
let inThrottle;
|
||||||
|
return function () {
|
||||||
|
const context = this;
|
||||||
|
const args = arguments;
|
||||||
|
if (!inThrottle) {
|
||||||
|
func.apply(context, args);
|
||||||
|
inThrottle = true;
|
||||||
|
setTimeout(() => (inThrottle = false), limit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const refreshStockPriceChartData = throttle(() => {
|
||||||
stockPriceChartData.value = [];
|
stockPriceChartData.value = [];
|
||||||
trpc.StockPriceChart.getChartData
|
trpc.StockPriceChart.getChartData
|
||||||
.query({
|
.query({
|
||||||
@@ -24,40 +46,36 @@ export const refreshStockPriceChartData = () => {
|
|||||||
.then((getChartDataResponse) => {
|
.then((getChartDataResponse) => {
|
||||||
stockPriceChartData.value = getChartDataResponse;
|
stockPriceChartData.value = getChartDataResponse;
|
||||||
});
|
});
|
||||||
};
|
}, 400);
|
||||||
export const refreshSimilarCalendarPriceChartData = () => {
|
export const refreshSimilarCalendarPriceChartData = throttle(() => {
|
||||||
similarCalendarPriceChartData.value = [];
|
similarCalendarPriceChartData.value = [];
|
||||||
trpc.SimilarCalendarPriceChart.getChartData
|
trpc.SimilarCalendarPriceChart.getChartData
|
||||||
.query({
|
.query({
|
||||||
underlying: underlying.value,
|
underlying: underlying.value,
|
||||||
daysToFrontExpiration: daysToFrontExpiration.value,
|
daysToFrontExpiration: openDTE.value,
|
||||||
daysBetweenFrontAndBackExpiration:
|
daysBetweenFrontAndBackExpiration: span.value,
|
||||||
daysBetweenFrontAndBackExpiration.value,
|
|
||||||
strikePercentageFromUnderlyingPriceRangeMin:
|
strikePercentageFromUnderlyingPriceRangeMin:
|
||||||
strikePercentageFromUnderlyingPrice.value -
|
(moniness.value - moninessRadius.value) / 100,
|
||||||
strikePercentageFromUnderlyingPriceRadius.value,
|
|
||||||
strikePercentageFromUnderlyingPriceRangeMax:
|
strikePercentageFromUnderlyingPriceRangeMax:
|
||||||
strikePercentageFromUnderlyingPrice.value +
|
(moniness.value + moninessRadius.value) / 100,
|
||||||
strikePercentageFromUnderlyingPriceRadius.value,
|
|
||||||
lookbackPeriodStart: lookbackPeriodStart.value,
|
lookbackPeriodStart: lookbackPeriodStart.value,
|
||||||
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
||||||
})
|
})
|
||||||
.then((getChartDataResponse) => {
|
.then((getChartDataResponse) => {
|
||||||
similarCalendarPriceChartData.value = getChartDataResponse;
|
similarCalendarPriceChartData.value = getChartDataResponse;
|
||||||
});
|
});
|
||||||
};
|
}, 400);
|
||||||
export const refreshcalendarExitPriceChartData = () => {
|
export const refreshcalendarExitPriceChartData = throttle(() => {
|
||||||
calendarExitPriceChartData.value = [];
|
calendarExitPriceChartData.value = [];
|
||||||
trpc.CalendarExitPriceChart.getChartData
|
trpc.CalendarExitPriceChart.getChartData
|
||||||
.query({
|
.query({
|
||||||
underlying: underlying.value,
|
underlying: underlying.value,
|
||||||
daysToFrontExpiration: exitToFrontExpiration.value,
|
daysToFrontExpiration: exitDTE.value,
|
||||||
daysBetweenFrontAndBackExpiration:
|
daysBetweenFrontAndBackExpiration: span.value,
|
||||||
daysBetweenFrontAndBackExpiration.value,
|
|
||||||
lookbackPeriodStart: lookbackPeriodStart.value,
|
lookbackPeriodStart: lookbackPeriodStart.value,
|
||||||
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
||||||
})
|
})
|
||||||
.then((getChartDataResponse) => {
|
.then((getChartDataResponse) => {
|
||||||
calendarExitPriceChartData.value = getChartDataResponse;
|
calendarExitPriceChartData.value = getChartDataResponse;
|
||||||
});
|
});
|
||||||
};
|
}, 400);
|
||||||
|
|||||||
@@ -7,33 +7,33 @@ export const popperContent = signal(null);
|
|||||||
export const availableUnderlyings = signal([]);
|
export const availableUnderlyings = signal([]);
|
||||||
export const underlying = signal(null);
|
export const underlying = signal(null);
|
||||||
|
|
||||||
export const daysToFrontExpiration = signal(14);
|
export const openDTE = signal(14);
|
||||||
|
|
||||||
export const daysBetweenFrontAndBackExpiration = signal(14);
|
export const span = signal(14);
|
||||||
|
|
||||||
export const strikePercentageFromUnderlyingPrice = signal(1.4);
|
export const moniness = signal(1);
|
||||||
export const strikePercentageFromUnderlyingPriceRadius = signal(0.05);
|
export const moninessRadius = signal(1);
|
||||||
|
|
||||||
export const exitToFrontExpiration = signal(2);
|
export const exitDTE = signal(2);
|
||||||
|
|
||||||
export const stockPriceChartData = signal([]);
|
export const stockPriceChartData = signal<Array<[number, number]>>([]);
|
||||||
|
|
||||||
export const similarCalendarPriceChartData = signal([]);
|
export const similarCalendarPriceChartData = signal([]);
|
||||||
|
|
||||||
export const calendarExitPriceChartData = signal([]);
|
export const calendarExitPriceChartData = signal([]);
|
||||||
|
|
||||||
export const lookbackPeriodStart = signal("2022-01-01");
|
export const lookbackPeriodStart = signal("2022-03-01");
|
||||||
export const lookbackPeriodEnd = signal("2024-01-01");
|
export const lookbackPeriodEnd = signal("2022-04-01");
|
||||||
|
|
||||||
export const maxChartPrice = computed(() =>
|
export const maxChartPrice = computed(() =>
|
||||||
Math.max(
|
Math.max(
|
||||||
Math.max.apply(
|
Math.max.apply(
|
||||||
null,
|
null,
|
||||||
similarCalendarPriceChartData.value.map((d) => d.y)
|
similarCalendarPriceChartData.value.map((d) => d.y).slice(0, -2)
|
||||||
),
|
),
|
||||||
Math.max.apply(
|
Math.max.apply(
|
||||||
null,
|
null,
|
||||||
calendarExitPriceChartData.value.map((d) => d.y)
|
calendarExitPriceChartData.value.map((d) => d.y).slice(0, -2)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,3 +27,4 @@ dist-ssr
|
|||||||
.env
|
.env
|
||||||
*.db
|
*.db
|
||||||
*.db-lck
|
*.db-lck
|
||||||
|
Calendar tRPC
|
||||||
@@ -31,7 +31,7 @@ export const getChartData = publicProcedure
|
|||||||
return await query<[number, number, number]>(
|
return await query<[number, number, number]>(
|
||||||
`
|
`
|
||||||
SELECT
|
SELECT
|
||||||
moniness as x,
|
moniness*100 as x,
|
||||||
FLOOR(price, 1) as y,
|
FLOOR(price, 1) as y,
|
||||||
sum(number_of_quotes) as n
|
sum(number_of_quotes) as n
|
||||||
FROM calendar_stats
|
FROM calendar_stats
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ export const getChartData = publicProcedure
|
|||||||
GROUP BY x
|
GROUP BY x
|
||||||
ORDER BY x ASC
|
ORDER BY x ASC
|
||||||
`,
|
`,
|
||||||
"JSONEachRow"
|
"JSONCompactEachRow"
|
||||||
|
// "JSONEachRow"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import { retry } from "./utils/retry.js";
|
|||||||
const env = new Env();
|
const env = new Env();
|
||||||
|
|
||||||
const { CLICKHOUSE_USER, CLICKHOUSE_PASS } = env.required;
|
const { CLICKHOUSE_USER, CLICKHOUSE_PASS } = env.required;
|
||||||
const CLICKHOUSE_HOST = env.get("CLICKHOUSE_HOST", "http://localhost:8123");
|
const CLICKHOUSE_URL = env.get("CLICKHOUSE_URL", "http://localhost:8123");
|
||||||
|
|
||||||
export const clickhouse = createClickhouseClient({
|
export const clickhouse = createClickhouseClient({
|
||||||
url: CLICKHOUSE_HOST,
|
url: CLICKHOUSE_URL,
|
||||||
username: CLICKHOUSE_USER,
|
username: CLICKHOUSE_USER,
|
||||||
password: CLICKHOUSE_PASS,
|
password: CLICKHOUSE_PASS,
|
||||||
keep_alive: {
|
keep_alive: {
|
||||||
|
|||||||
Reference in New Issue
Block a user