Compare commits
16 Commits
add21288be
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c83f2ce0a0 | |||
| 4d989f10eb | |||
| 906e63eb91 | |||
| 3732680d52 | |||
| 3c841f488c | |||
| b6cc25b769 | |||
| 14dff05785 | |||
| f05831b5f1 | |||
| b7f9d60715 | |||
| b2169e1da7 | |||
| aeac0e1042 | |||
| 904d2da84a | |||
| 694bb38536 | |||
| 64a5172ea8 | |||
| ac9554ba21 | |||
| 2597c0f6ac |
+1
-1
@@ -2,7 +2,7 @@
|
||||
FROM node:20-slim AS build
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
RUN npm install -g corepack@latest && corepack enable
|
||||
WORKDIR /app
|
||||
# copy what's necessary to install dependencies:
|
||||
COPY package.json pnpm-lock.yaml /app/
|
||||
|
||||
@@ -5,17 +5,19 @@ import {
|
||||
LinearScale,
|
||||
CategoryScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Tooltip,
|
||||
Title,
|
||||
} from "chart.js";
|
||||
import { Scatter } from "react-chartjs-2";
|
||||
import {
|
||||
Container,
|
||||
Grid,
|
||||
Grid2,
|
||||
Typography,
|
||||
Paper,
|
||||
Popper,
|
||||
ClickAwayListener,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
availableUnderlyings,
|
||||
@@ -38,13 +40,19 @@ import {
|
||||
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";
|
||||
import { EditableOpenDTE } from "./HistoricalCalendarPrices/EditableOpenDTE.js";
|
||||
import { EditableExitDTE } from "./HistoricalCalendarPrices/EditableExitDTE.js";
|
||||
import { EditableSpan } from "./HistoricalCalendarPrices/EditableSpan.js";
|
||||
import { EditableLookbackPeriod } from "./HistoricalCalendarPrices/EditableLookbackPeriod.js";
|
||||
|
||||
ChartJS.register(LinearScale, CategoryScale, PointElement, Tooltip, Title);
|
||||
ChartJS.register(
|
||||
LinearScale,
|
||||
CategoryScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Tooltip,
|
||||
Title
|
||||
);
|
||||
|
||||
const handleInit = () => {
|
||||
trpc.CalendarCharacteristicsForm.getAvailableUnderlyings
|
||||
@@ -63,17 +71,17 @@ export function HistoricalCalendarPrices() {
|
||||
|
||||
return (
|
||||
<Container maxWidth="lg">
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12}>
|
||||
<Grid2 container spacing={4} columns={12}>
|
||||
{/* <Grid2 size={{ xs: 12 }}>
|
||||
|
||||
<Typography variant="h4" gutterBottom>
|
||||
<EditableUnderlying /> :
|
||||
<EditableDaysBetweenFrontAndBackExpiration />
|
||||
<EditableSpan />
|
||||
-Day Calendar @ <EditableStrike />
|
||||
%-from-the-money
|
||||
</Typography>
|
||||
<Typography variant="h5" gutterBottom sx={{ pl: 1 }}>
|
||||
Opening at <EditableDaysToFrontExpiration /> DTE, Closing at{" "}
|
||||
<EditableExitToFrontExpiration />
|
||||
Opening at <EditableOpenDTE /> DTE, Closing at <EditableExitDTE />
|
||||
DTE
|
||||
</Typography>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
@@ -93,73 +101,55 @@ export function HistoricalCalendarPrices() {
|
||||
</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}>
|
||||
</Grid2> */}
|
||||
|
||||
<Grid2 size={{ xs: 12 }}>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Typography gutterBottom minWidth={"8em"}>
|
||||
Underlying
|
||||
</Typography>
|
||||
<EditableUnderlying />
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Typography gutterBottom minWidth={"8em"}>
|
||||
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");
|
||||
}}
|
||||
>
|
||||
<Popper open={isPopperOpen.value} anchorEl={popperAnchorEl.value}>
|
||||
<Paper elevation={3} sx={{ p: 3 }}>
|
||||
{popperContent.value}
|
||||
</Paper>
|
||||
</Popper>
|
||||
</ClickAwayListener>
|
||||
</Grid2>
|
||||
|
||||
<Grid2 size={{ xs: 12, md: 6 }}>
|
||||
<Paper elevation={3} sx={{ p: 3, minHeight: "28em" }}>
|
||||
{underlying.value !== null &&
|
||||
similarCalendarPriceChartData.value.length > 0 ? (
|
||||
@@ -219,11 +209,11 @@ export function HistoricalCalendarPrices() {
|
||||
<Typography>Loading Chart...</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
</Grid2>
|
||||
<Grid2 size={{ xs: 12, md: 6 }}>
|
||||
<Paper elevation={3} sx={{ p: 3, minHeight: "28em" }}>
|
||||
{underlying.value !== null &&
|
||||
similarCalendarPriceChartData.value.length > 0 ? (
|
||||
calendarExitPriceChartData.value.length > 0 ? (
|
||||
<Scatter
|
||||
data={{
|
||||
datasets: [
|
||||
@@ -293,8 +283,78 @@ export function HistoricalCalendarPrices() {
|
||||
<Typography>Loading Chart...</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</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>
|
||||
</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 { EditableValue } from "./EditableValue";
|
||||
import {
|
||||
strikePercentageFromUnderlyingPrice,
|
||||
strikePercentageFromUnderlyingPriceRadius,
|
||||
} from "./state";
|
||||
import { moniness, moninessRadius } from "./state";
|
||||
import Slider from "@mui/material/Slider";
|
||||
import { refreshSimilarCalendarPriceChartData } from "./actions";
|
||||
|
||||
function StrikePercentageFromUnderlyingPriceChooser() {
|
||||
function MoninessChooser() {
|
||||
return (
|
||||
<Slider
|
||||
fullWidth
|
||||
label="Strike % From Underlying Price"
|
||||
value={strikePercentageFromUnderlyingPrice.value}
|
||||
value={moniness.value}
|
||||
valueLabelDisplay="on"
|
||||
min={0}
|
||||
max={10}
|
||||
step={0.1}
|
||||
step={1}
|
||||
onChange={(e, value) => {
|
||||
strikePercentageFromUnderlyingPrice.value = value as number;
|
||||
moniness.value = value as number;
|
||||
}}
|
||||
onChangeCommitted={(e, value) => {
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
@@ -28,18 +25,18 @@ function StrikePercentageFromUnderlyingPriceChooser() {
|
||||
);
|
||||
}
|
||||
|
||||
function StrikePercentageFromUnderlyingPriceRadiusChooser() {
|
||||
function MoninessRadiusChooser() {
|
||||
return (
|
||||
<Slider
|
||||
fullWidth
|
||||
label="Strike % Radius"
|
||||
value={strikePercentageFromUnderlyingPriceRadius.value}
|
||||
value={moninessRadius.value}
|
||||
valueLabelDisplay="on"
|
||||
min={0}
|
||||
max={0.5}
|
||||
step={0.05}
|
||||
max={10}
|
||||
step={1}
|
||||
onChange={(e, value) => {
|
||||
strikePercentageFromUnderlyingPriceRadius.value = value as number;
|
||||
moninessRadius.value = value as number;
|
||||
}}
|
||||
onChangeCommitted={(e, value) => {
|
||||
refreshSimilarCalendarPriceChartData();
|
||||
@@ -57,13 +54,11 @@ function StrikePercentageFromUnderlyingPriceRadiusChooser() {
|
||||
export function EditableStrike() {
|
||||
return (
|
||||
<EditableValue
|
||||
text={`${strikePercentageFromUnderlyingPrice.value.toFixed(
|
||||
1
|
||||
)}±${strikePercentageFromUnderlyingPriceRadius.value.toFixed(2)}`}
|
||||
text={`${moniness.value.toFixed(1)}±${moninessRadius.value.toFixed(2)}`}
|
||||
>
|
||||
<Box sx={{ minWidth: "20em" }}>
|
||||
<StrikePercentageFromUnderlyingPriceChooser />
|
||||
<StrikePercentageFromUnderlyingPriceRadiusChooser />
|
||||
<MoninessChooser />
|
||||
<MoninessRadiusChooser />
|
||||
</Box>
|
||||
</EditableValue>
|
||||
);
|
||||
|
||||
@@ -1,19 +1,41 @@
|
||||
import { trpc } from "../../trpc";
|
||||
import {
|
||||
calendarExitPriceChartData,
|
||||
daysBetweenFrontAndBackExpiration,
|
||||
daysToFrontExpiration,
|
||||
exitToFrontExpiration,
|
||||
span,
|
||||
openDTE,
|
||||
exitDTE,
|
||||
lookbackPeriodEnd,
|
||||
lookbackPeriodStart,
|
||||
similarCalendarPriceChartData,
|
||||
stockPriceChartData,
|
||||
strikePercentageFromUnderlyingPrice,
|
||||
strikePercentageFromUnderlyingPriceRadius,
|
||||
moniness,
|
||||
moninessRadius,
|
||||
underlying,
|
||||
} 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 = [];
|
||||
trpc.StockPriceChart.getChartData
|
||||
.query({
|
||||
@@ -24,40 +46,36 @@ export const refreshStockPriceChartData = () => {
|
||||
.then((getChartDataResponse) => {
|
||||
stockPriceChartData.value = getChartDataResponse;
|
||||
});
|
||||
};
|
||||
export const refreshSimilarCalendarPriceChartData = () => {
|
||||
}, 400);
|
||||
export const refreshSimilarCalendarPriceChartData = throttle(() => {
|
||||
similarCalendarPriceChartData.value = [];
|
||||
trpc.SimilarCalendarPriceChart.getChartData
|
||||
.query({
|
||||
underlying: underlying.value,
|
||||
daysToFrontExpiration: daysToFrontExpiration.value,
|
||||
daysBetweenFrontAndBackExpiration:
|
||||
daysBetweenFrontAndBackExpiration.value,
|
||||
daysToFrontExpiration: openDTE.value,
|
||||
daysBetweenFrontAndBackExpiration: span.value,
|
||||
strikePercentageFromUnderlyingPriceRangeMin:
|
||||
strikePercentageFromUnderlyingPrice.value -
|
||||
strikePercentageFromUnderlyingPriceRadius.value,
|
||||
(moniness.value - moninessRadius.value) / 100,
|
||||
strikePercentageFromUnderlyingPriceRangeMax:
|
||||
strikePercentageFromUnderlyingPrice.value +
|
||||
strikePercentageFromUnderlyingPriceRadius.value,
|
||||
(moniness.value + moninessRadius.value) / 100,
|
||||
lookbackPeriodStart: lookbackPeriodStart.value,
|
||||
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
||||
})
|
||||
.then((getChartDataResponse) => {
|
||||
similarCalendarPriceChartData.value = getChartDataResponse;
|
||||
});
|
||||
};
|
||||
export const refreshcalendarExitPriceChartData = () => {
|
||||
}, 400);
|
||||
export const refreshcalendarExitPriceChartData = throttle(() => {
|
||||
calendarExitPriceChartData.value = [];
|
||||
trpc.CalendarExitPriceChart.getChartData
|
||||
.query({
|
||||
underlying: underlying.value,
|
||||
daysToFrontExpiration: exitToFrontExpiration.value,
|
||||
daysBetweenFrontAndBackExpiration:
|
||||
daysBetweenFrontAndBackExpiration.value,
|
||||
daysToFrontExpiration: exitDTE.value,
|
||||
daysBetweenFrontAndBackExpiration: span.value,
|
||||
lookbackPeriodStart: lookbackPeriodStart.value,
|
||||
lookbackPeriodEnd: lookbackPeriodEnd.value,
|
||||
})
|
||||
.then((getChartDataResponse) => {
|
||||
calendarExitPriceChartData.value = getChartDataResponse;
|
||||
});
|
||||
};
|
||||
}, 400);
|
||||
|
||||
@@ -7,33 +7,33 @@ export const popperContent = signal(null);
|
||||
export const availableUnderlyings = signal([]);
|
||||
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 strikePercentageFromUnderlyingPriceRadius = signal(0.05);
|
||||
export const moniness = signal(1);
|
||||
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 calendarExitPriceChartData = signal([]);
|
||||
|
||||
export const lookbackPeriodStart = signal("2022-01-01");
|
||||
export const lookbackPeriodEnd = signal("2024-01-01");
|
||||
export const lookbackPeriodStart = signal("2022-03-01");
|
||||
export const lookbackPeriodEnd = signal("2022-04-01");
|
||||
|
||||
export const maxChartPrice = computed(() =>
|
||||
Math.max(
|
||||
Math.max.apply(
|
||||
null,
|
||||
similarCalendarPriceChartData.value.map((d) => d.y)
|
||||
similarCalendarPriceChartData.value.map((d) => d.y).slice(0, -2)
|
||||
),
|
||||
Math.max.apply(
|
||||
null,
|
||||
calendarExitPriceChartData.value.map((d) => d.y)
|
||||
calendarExitPriceChartData.value.map((d) => d.y).slice(0, -2)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -27,3 +27,4 @@ dist-ssr
|
||||
.env
|
||||
*.db
|
||||
*.db-lck
|
||||
Calendar tRPC
|
||||
+17
-12
@@ -2,22 +2,27 @@
|
||||
FROM node:20-slim AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
RUN npm install -g corepack@latest && corepack enable
|
||||
COPY package.json pnpm-lock.yaml /app/
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS prod-deps
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
|
||||
# install dev dependencies which are needed for building, such as typescript:
|
||||
FROM base AS build
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
COPY tsconfig.json /app/
|
||||
COPY src /app/src
|
||||
RUN pnpm run build
|
||||
CMD [ "pnpm", "tsx", "src/index.ts" ]
|
||||
|
||||
FROM base
|
||||
COPY --from=prod-deps /app/node_modules /app/node_modules
|
||||
COPY --from=build /app/dist /app/dist
|
||||
WORKDIR /app/dist
|
||||
CMD [ "node", "index.js" ]
|
||||
# FROM base AS prod-deps
|
||||
# RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
|
||||
# # install dev dependencies which are needed for building, such as typescript:
|
||||
# FROM base AS build
|
||||
# RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
# COPY tsconfig.json /app/
|
||||
# COPY src /app/src
|
||||
# RUN pnpm run build
|
||||
|
||||
# FROM base
|
||||
# COPY --from=prod-deps /app/node_modules /app/node_modules
|
||||
# COPY --from=build /app/dist /app/dist
|
||||
# WORKDIR /app/dist
|
||||
# CMD [ "node", "index.js" ]
|
||||
+2
-1
@@ -9,11 +9,12 @@
|
||||
"cli": "tsx src/cli.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clickhouse/client": "^0.2.7",
|
||||
"@clickhouse/client": "^1.4.1",
|
||||
"@humanwhocodes/env": "^3.0.5",
|
||||
"@sinclair/typebox": "^0.32.5",
|
||||
"@trpc/server": "^10.45.0",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "^3.6.0",
|
||||
"execa": "^9.3.0",
|
||||
"ink": "^4.1.0",
|
||||
"ink-text-input": "^5.0.1",
|
||||
|
||||
Generated
+17
-9
@@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@clickhouse/client':
|
||||
specifier: ^0.2.7
|
||||
version: 0.2.7
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1
|
||||
'@humanwhocodes/env':
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
@@ -23,6 +23,9 @@ importers:
|
||||
cors:
|
||||
specifier: ^2.8.5
|
||||
version: 2.8.5
|
||||
date-fns:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0
|
||||
execa:
|
||||
specifier: ^9.3.0
|
||||
version: 9.3.0
|
||||
@@ -88,11 +91,11 @@ packages:
|
||||
resolution: {integrity: sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==}
|
||||
engines: {node: '>=14.13.1'}
|
||||
|
||||
'@clickhouse/client-common@0.2.7':
|
||||
resolution: {integrity: sha512-vgZm+8c5Cu1toIx1/xplF5dEHlCPw+7pJDOOEtLv2CIUVZ0Bl6nGVZ43EWxRdHeah9ivTfoRWhN1zI1PxjH0xQ==}
|
||||
'@clickhouse/client-common@1.4.1':
|
||||
resolution: {integrity: sha512-f5eoTrUSDplrMoi3ddeZ0MzGTn0iGMByEQ8j63eVMoBSOI2+F6jEIPcW2tWofT79Rvnn3RRlveYcShiaIiCJyw==}
|
||||
|
||||
'@clickhouse/client@0.2.7':
|
||||
resolution: {integrity: sha512-ZiyarrGngHc+f5AjZSA7mkQfvnE/71jgXk304B0ps8V+aBpE2CsFB6AQmE/Mk2YkP5j+8r/JfG+m0AZWmE27ig==}
|
||||
'@clickhouse/client@1.4.1':
|
||||
resolution: {integrity: sha512-12iV+MeykxdQySRFHwaVU+hKUv3JP6kdwOI+z3zzyfPVYHynTlV8emJjjGZR0+VfRaj3PCMuQfryfsJ82nh9WQ==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
'@esbuild/aix-ppc64@0.19.11':
|
||||
@@ -610,6 +613,9 @@ packages:
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
date-fns@3.6.0:
|
||||
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
|
||||
|
||||
debug@4.3.5:
|
||||
resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -1626,11 +1632,11 @@ snapshots:
|
||||
ansi-styles: 6.2.1
|
||||
is-fullwidth-code-point: 4.0.0
|
||||
|
||||
'@clickhouse/client-common@0.2.7': {}
|
||||
'@clickhouse/client-common@1.4.1': {}
|
||||
|
||||
'@clickhouse/client@0.2.7':
|
||||
'@clickhouse/client@1.4.1':
|
||||
dependencies:
|
||||
'@clickhouse/client-common': 0.2.7
|
||||
'@clickhouse/client-common': 1.4.1
|
||||
|
||||
'@esbuild/aix-ppc64@0.19.11':
|
||||
optional: true
|
||||
@@ -2021,6 +2027,8 @@ snapshots:
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
date-fns@3.6.0: {}
|
||||
|
||||
debug@4.3.5:
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
@@ -13,7 +13,7 @@ export const getAvailableUnderlyings = publicProcedure.query(async (opts) => {
|
||||
// SELECT DISTINCT(symbol) as symbol FROM option_contract_existences WHERE asOfDate = (SELECT max(asOfDate) FROM option_contract_existences)
|
||||
// `)
|
||||
// ).map(({ symbol }) => symbol);
|
||||
return ["AAPL", "AMD", "GOOGL", "MSFT", "NFLX"];
|
||||
return ["SPY"];
|
||||
});
|
||||
|
||||
export const getAvailableAsOfDates = publicProcedure
|
||||
|
||||
@@ -30,18 +30,17 @@ export const getChartData = publicProcedure
|
||||
} = opts.input;
|
||||
return await query<[number, number, number]>(
|
||||
`
|
||||
SELECT
|
||||
FLOOR(strikePercentageFromUnderlyingPrice, 1) as x,
|
||||
FLOOR(calendarPrice, 1) as y,
|
||||
count(*) as n
|
||||
FROM calendar_histories
|
||||
WHERE symbol = '${underlying}'
|
||||
AND daysToFrontExpiration = ${daysToFrontExpiration}
|
||||
AND strikePercentageFromUnderlyingPrice >= -5.0
|
||||
AND strikePercentageFromUnderlyingPrice <= 5.0
|
||||
AND daysBetweenFrontAndBackExpiration = ${daysBetweenFrontAndBackExpiration}
|
||||
AND tsStart >= '${lookbackPeriodStart} 00:00:00'
|
||||
AND tsStart <= '${lookbackPeriodEnd} 00:00:00'
|
||||
SELECT
|
||||
moniness*100 as x,
|
||||
FLOOR(price, 1) as y,
|
||||
sum(number_of_quotes) as n
|
||||
FROM calendar_stats
|
||||
WHERE dte = ${daysToFrontExpiration}
|
||||
AND moniness >= -0.05
|
||||
AND moniness <= 0.05
|
||||
AND span = ${daysBetweenFrontAndBackExpiration}
|
||||
AND date >= '${lookbackPeriodStart}'
|
||||
AND date <= '${lookbackPeriodEnd}'
|
||||
GROUP BY x, y
|
||||
ORDER BY x ASC, y ASC
|
||||
`,
|
||||
|
||||
@@ -34,17 +34,16 @@ export const getChartData = publicProcedure
|
||||
} = opts.input;
|
||||
return await query<[number, number]>(
|
||||
`
|
||||
SELECT
|
||||
toUnixTimestamp(tsStart) as x,
|
||||
truncate(calendarPrice, 2) as y
|
||||
FROM calendar_histories
|
||||
WHERE symbol = '${underlying}'
|
||||
AND daysToFrontExpiration = ${daysToFrontExpiration}
|
||||
AND strikePercentageFromUnderlyingPrice >= ${strikePercentageFromUnderlyingPriceRangeMin}
|
||||
AND strikePercentageFromUnderlyingPrice <= ${strikePercentageFromUnderlyingPriceRangeMax}
|
||||
AND daysBetweenFrontAndBackExpiration = ${daysBetweenFrontAndBackExpiration}
|
||||
AND tsStart >= '${lookbackPeriodStart} 00:00:00'
|
||||
AND tsStart <= '${lookbackPeriodEnd} 00:00:00'
|
||||
SELECT
|
||||
toUnixTimestamp(date) as x,
|
||||
price as y
|
||||
FROM calendar_stats
|
||||
WHERE dte = ${daysToFrontExpiration}
|
||||
AND moniness >= ${strikePercentageFromUnderlyingPriceRangeMin}
|
||||
AND moniness <= ${strikePercentageFromUnderlyingPriceRangeMax}
|
||||
AND span = ${daysBetweenFrontAndBackExpiration}
|
||||
AND date >= '${lookbackPeriodStart}'
|
||||
AND date <= '${lookbackPeriodEnd}'
|
||||
`,
|
||||
"JSONEachRow"
|
||||
);
|
||||
|
||||
@@ -17,15 +17,17 @@ export const getChartData = publicProcedure
|
||||
return await query<[number, number]>(
|
||||
`
|
||||
SELECT
|
||||
toUnixTimestamp(tsStart) as x,
|
||||
open as y
|
||||
FROM stock_aggregates
|
||||
toUnixTimestamp(toStartOfHour(ts)) as x,
|
||||
avg(price) as y
|
||||
FROM stock_aggregates_filled
|
||||
WHERE symbol = '${underlying}'
|
||||
AND tsStart >= '${lookbackPeriodStart} 00:00:00'
|
||||
AND tsStart <= '${lookbackPeriodEnd} 00:00:00'
|
||||
AND ts >= '${lookbackPeriodStart} 00:00:00'
|
||||
AND ts <= '${lookbackPeriodEnd} 00:00:00'
|
||||
GROUP BY x
|
||||
ORDER BY x ASC
|
||||
`,
|
||||
"JSONEachRow"
|
||||
"JSONCompactEachRow"
|
||||
// "JSONEachRow"
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,21 +6,21 @@ import { retry } from "./utils/retry.js";
|
||||
const env = new Env();
|
||||
|
||||
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({
|
||||
host: CLICKHOUSE_HOST,
|
||||
url: CLICKHOUSE_URL,
|
||||
username: CLICKHOUSE_USER,
|
||||
password: CLICKHOUSE_PASS,
|
||||
keep_alive: {
|
||||
enabled: true,
|
||||
socket_ttl: 2500,
|
||||
// socket_ttl: 2500,
|
||||
},
|
||||
});
|
||||
|
||||
export async function query<T>(
|
||||
queryString: string,
|
||||
format: DataFormat = "JSONEachRow",
|
||||
format: DataFormat = "JSONEachRow"
|
||||
): Promise<Array<T>> {
|
||||
return await retry(
|
||||
async () => {
|
||||
@@ -33,6 +33,6 @@ export async function query<T>(
|
||||
});
|
||||
return await result.json();
|
||||
},
|
||||
{ maxRetries: 5 },
|
||||
{ maxRetries: 5 }
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user