revamp frontend with tailwind

main
Avraham Sakal 11 months ago
parent 5614586b66
commit 6d8b874a82

@ -0,0 +1,41 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color: #222;
background-color: #ffffff;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
body {
margin: 0;
}
/* Input styling: */
:root {
--radius-inputs: 0.25em;
}
select,
textarea,
input[type="text"] {
border-radius: var(--radius-inputs);
}

@ -1,14 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="/index.css" />
<meta name="color-scheme" content="light dark" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Preact</title> <meta name="color-scheme" content="light dark" />
</head> <title>Vite + Preact</title>
<body> </head>
<div id="app"></div> <body>
<script type="module" src="/src/index.tsx"></script> <div id="app"></div>
</body> <script type="module" src="/src/index.tsx"></script>
</body>
</html> </html>

@ -18,6 +18,9 @@
}, },
"devDependencies": { "devDependencies": {
"@preact/preset-vite": "^2.5.0", "@preact/preset-vite": "^2.5.0",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^4.3.2" "vite": "^4.3.2"
} }

File diff suppressed because it is too large Load Diff

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

@ -1,24 +1,27 @@
import { useLocation } from 'preact-iso'; import { useLocation } from "preact-iso";
export function Header() { export function Header() {
const { url } = useLocation(); const { url } = useLocation();
return ( return (
<header> <header class="flex flex-row justify-center">
<nav> <nav class="flex flex-row justify-center text-lg gap-4 max-w-5xl py-2 px-3 rounded-full bg-indigo-200">
<a href="/" class={url == '/' && 'active'}> <a
Home href="/"
</a> class={
<a href="/calendar-optimizer" class={url == '/calendar-optimizer' && 'active'}> (url === "/" || url === "/historical-calendar-prices") &&
Calendar Optimizer "underline font-bold"
</a> }
<a href="/historical-calendar-prices" class={url == '/historical-calendar-prices' && 'active'}> >
Historical Calendar Prices Historical Calendar Prices
</a> </a>
<a href="/404" class={url == '/404' && 'active'}> <a
404 href="/calendar-optimizer"
</a> class={url === "/calendar-optimizer" && "underline font-bold"}
</nav> >
</header> Calendar Optimizer
); </a>
</nav>
</header>
);
} }

@ -1,28 +1,29 @@
import _ from './env'; import _ from "./env";
import { render } from 'preact'; import { render } from "preact";
import { LocationProvider, Router, Route } from 'preact-iso'; import { LocationProvider, Router, Route } from "preact-iso";
import { Header } from './components/Header.jsx'; import { Header } from "./components/Header.jsx";
import { Home } from './pages/Home/index.jsx'; import { CalendarOptimizer } from "./pages/CalendarOptimizer.js";
import { CalendarOptimizer } from './pages/CalendarOptimizer/index.jsx'; import { NotFound } from "./pages/_404.jsx";
import { NotFound } from './pages/_404.jsx'; // import './style.css';
import './style.css'; import { HistoricalCalendarPrices } from "./pages/HistoricalCalendarPrices.js";
import { HistoricalCalendarPrices } from './pages/HistoricalCalendarPrices/HistoricalCalendarPrices.js';
export function App() { export function App() {
return ( return (
<LocationProvider> <LocationProvider>
<Header /> <div class="flex flex-col justify-start gap-4">
<main> <Header />
<Router> <main>
<Route path="/" component={Home} /> <Router>
<Route path="/calendar-optimizer" component={CalendarOptimizer} /> <Route path="/" component={HistoricalCalendarPrices} />
<Route path="/historical-calendar-prices" component={HistoricalCalendarPrices} /> <Route path="/calendar-optimizer" component={CalendarOptimizer} />
<Route default component={NotFound} /> {/* <Route path="/historical-calendar-prices" component={HistoricalCalendarPrices} /> */}
</Router> <Route default component={NotFound} />
</main> </Router>
</LocationProvider> </main>
); </div>
</LocationProvider>
);
} }
render(<App />, document.getElementById('app')); render(<App />, document.getElementById("app"));

@ -0,0 +1,345 @@
import { signal } from "@preact/signals";
import { useCallback, useEffect } from "preact/hooks";
import { trpc } from "../trpc.js";
import {
Chart as ChartJS,
LinearScale,
CategoryScale,
PointElement,
Tooltip,
Title,
} from "chart.js";
import { Scatter } from "react-chartjs-2";
// import "./style.css";
ChartJS.register(LinearScale, CategoryScale, PointElement, Tooltip, Title);
const availableUnderlyings = signal([]);
const chosenUnderlying = signal(null);
const availableAsOfDates = signal([]);
const chosenAsOfDate = signal(null);
const availableExpirations = signal([]);
const chosenExpiration = signal(null);
const availableStrikes = signal([]);
const chosenStrike = signal(null);
const optionContractUplotData = signal([]);
const underlyingUplotData = signal([]);
function chooseUnderlying(underlying: string) {
chosenUnderlying.value = underlying;
trpc.getAvailableAsOfDates
.query({ underlying: underlying })
.then((getAvailableAsOfDatesResponse) => {
availableAsOfDates.value = getAvailableAsOfDatesResponse;
chooseAsOfDate(getAvailableAsOfDatesResponse[0]);
});
trpc.getOpensForUnderlying
.query({ underlying: underlying })
.then((getOpensForUnderlyingResponse) => {
underlyingUplotData.value = getOpensForUnderlyingResponse;
});
}
function chooseAsOfDate(asOfDate: string) {
chosenAsOfDate.value = asOfDate;
trpc.getExpirationsForUnderlying
.query({
underlying: chosenUnderlying.value,
asOfDate: chosenAsOfDate.value,
})
.then((getExpirationsForUnderlyingResponse) => {
availableExpirations.value = getExpirationsForUnderlyingResponse;
chooseExpiration(getExpirationsForUnderlyingResponse[0]);
});
}
function chooseExpiration(expiration: string) {
chosenExpiration.value = expiration;
trpc.getStrikesForUnderlying
.query({
underlying: chosenUnderlying.value,
asOfDate: chosenAsOfDate.value,
expirationDate: expiration,
})
.then((getStrikesForUnderlyingResponse) => {
availableStrikes.value = getStrikesForUnderlyingResponse;
chooseStrike(getStrikesForUnderlyingResponse[0]);
});
}
function chooseStrike(strike: string) {
chosenStrike.value = strike;
trpc.getOpensForOptionContract
.query({
underlying: chosenUnderlying.value,
expirationDate: chosenExpiration.value,
strike: parseFloat(strike),
})
.then((getOpensForOptionContractResponse) => {
optionContractUplotData.value = getOpensForOptionContractResponse;
});
}
export function CalendarOptimizer() {
const handleInit = useCallback(() => {
trpc.getAvailableUnderlyings
.query()
.then((availableUnderlyingsResponse) => {
availableUnderlyings.value = availableUnderlyingsResponse;
// load first underlying in list:
chooseUnderlying(availableUnderlyingsResponse[0]);
});
}, []);
const handleUnderlyingChange = useCallback((e) => {
console.log(`Chose Underlying: ${e.target.value}`);
chooseUnderlying(e.target.value);
}, []);
const handleAsOfDateChange = useCallback((e) => {
console.log(`Chose Date: ${e.target.value}`);
chooseAsOfDate(e.target.value);
}, []);
const handleExpirationChange = useCallback((e) => {
console.log(`Chose Expiration: ${e.target.value}`);
chooseExpiration(e.target.value);
}, []);
const handleStrikeChange = useCallback((e) => {
console.log(`Chose Strike: ${e.target.value}`);
chooseStrike(e.target.value);
}, []);
useEffect(handleInit, []);
return (
/* container for centering: */
<div class="flex flex-row justify-center">
<div class="flex flex-col justify-start gap-4">
{/* inputs form container: */}
<div class="flex flex-col justify-start gap-1">
<div class="flex flex-row w-160 gap-3">
<div class="text-right w-1/3">
<label>Available Underlyings</label>
</div>
<div class="my-auto w-2/3">
{availableUnderlyings.value.length === 0 ? (
"Loading..."
) : (
<select
onChange={handleUnderlyingChange}
class="border border-gray-300 focus:border-blue-400"
>
{availableUnderlyings.value.map((availableUnderlying) => (
<option value={availableUnderlying}>
{availableUnderlying}
</option>
))}
</select>
)}
</div>
</div>
<div class="flex flex-row w-160 gap-3">
<div class="text-right w-1/3">
<label>Available "As-of" Dates</label>
</div>
<div class="my-auto w-2/3">
{availableAsOfDates.value.length === 0 ? (
"Loading..."
) : (
<select
onChange={handleAsOfDateChange}
class="border border-gray-300 focus:border-blue-400"
>
{availableAsOfDates.value.map((availableAsOfDate) => (
<option value={availableAsOfDate}>
{availableAsOfDate}
</option>
))}
</select>
)}
</div>
</div>
<div class="flex flex-row w-160 gap-3">
<div class="text-right w-1/3">
<label>Available Expirations</label>
</div>
<div class="my-auto w-2/3">
{availableExpirations.value.length === 0 ? (
"Loading..."
) : (
<select
onChange={handleExpirationChange}
class="border border-gray-300 focus:border-blue-400"
>
{availableExpirations.value.map((availableExpiration) => (
<option value={availableExpiration}>
{availableExpiration}
</option>
))}
</select>
)}
</div>
</div>
<div class="flex flex-row w-160 gap-3">
<div class="text-right w-1/3">
<label>Available Strikes</label>
</div>
<div class="my-auto w-2/3">
{availableStrikes.value.length === 0 ? (
"Loading..."
) : (
<select
onChange={handleStrikeChange}
class="border border-gray-300 focus:border-blue-400"
>
{availableStrikes.value.map((availableStrike) => (
<option value={availableStrike}>{availableStrike}</option>
))}
</select>
)}
</div>
</div>
</div>
<div className="chart-container">
{chosenUnderlying.value !== null &&
underlyingUplotData.value.length > 0 ? (
<div className="chart">
<Scatter
data={{
datasets: [
{
label: "Stock Open Price",
data: underlyingUplotData.value,
},
],
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time",
},
ticks: {
callback: function (value, index, ticks) {
return new Date((value as number) * 1000)
.toISOString()
.substring(0, 10);
},
},
// min: (new Date(chosenLookbackPeriodStart.value)).getTime()/1000,
// max: (new Date(chosenLookbackPeriodEnd.value)).getTime()/1000,
},
y: {
beginAtZero: false,
ticks: {
callback: function (value, index, ticks) {
return "$" + value.toString();
},
},
// min: 0,
// max: maxChartPrice.value,
},
},
elements: {
point: {
radius: 1,
borderWidth: 0,
},
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: "Stock Price",
},
},
animation: false,
maintainAspectRatio: false,
}}
/>
</div>
) : (
<></>
)}
{chosenUnderlying.value !== null &&
chosenAsOfDate.value !== null &&
chosenExpiration.value !== null &&
chosenStrike.value !== null &&
optionContractUplotData.value.length > 0 ? (
<div className="chart">
<Scatter
data={{
datasets: [
{
label: "Option Contract Open Price",
data: optionContractUplotData.value,
},
],
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time",
},
ticks: {
callback: function (value, index, ticks) {
return new Date((value as number) * 1000)
.toISOString()
.substring(0, 10);
},
},
// min: (new Date(chosenLookbackPeriodStart.value)).getTime()/1000,
// max: (new Date(chosenLookbackPeriodEnd.value)).getTime()/1000,
},
y: {
beginAtZero: false,
ticks: {
callback: function (value, index, ticks) {
return "$" + value.toString();
},
},
// min: 0,
// max: maxChartPrice.value,
},
},
elements: {
point: {
radius: 1,
borderWidth: 0,
},
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: "Option Contract Price",
},
},
animation: false,
maintainAspectRatio: false,
}}
/>
</div>
) : (
<></>
)}
</div>
</div>
</div>
);
}

@ -1,305 +0,0 @@
import { signal } from "@preact/signals";
import { useCallback, useEffect } from "preact/hooks";
import { trpc } from "../../trpc.js";
import {
Chart as ChartJS,
LinearScale,
CategoryScale,
PointElement,
Tooltip,
Title,
} from "chart.js";
import { Scatter } from "react-chartjs-2";
import "./style.css";
ChartJS.register(LinearScale, CategoryScale, PointElement, Tooltip, Title);
const availableUnderlyings = signal([]);
const chosenUnderlying = signal(null);
const availableAsOfDates = signal([]);
const chosenAsOfDate = signal(null);
const availableExpirations = signal([]);
const chosenExpiration = signal(null);
const availableStrikes = signal([]);
const chosenStrike = signal(null);
const optionContractUplotData = signal([]);
const underlyingUplotData = signal([]);
function chooseUnderlying(underlying: string) {
chosenUnderlying.value = underlying;
trpc.getAvailableAsOfDates
.query({ underlying: underlying })
.then((getAvailableAsOfDatesResponse) => {
availableAsOfDates.value = getAvailableAsOfDatesResponse;
chooseAsOfDate(getAvailableAsOfDatesResponse[0]);
});
trpc.getOpensForUnderlying
.query({ underlying: underlying })
.then((getOpensForUnderlyingResponse) => {
underlyingUplotData.value = getOpensForUnderlyingResponse;
});
}
function chooseAsOfDate(asOfDate: string) {
chosenAsOfDate.value = asOfDate;
trpc.getExpirationsForUnderlying
.query({
underlying: chosenUnderlying.value,
asOfDate: chosenAsOfDate.value,
})
.then((getExpirationsForUnderlyingResponse) => {
availableExpirations.value = getExpirationsForUnderlyingResponse;
chooseExpiration(getExpirationsForUnderlyingResponse[0]);
});
}
function chooseExpiration(expiration: string) {
chosenExpiration.value = expiration;
trpc.getStrikesForUnderlying
.query({
underlying: chosenUnderlying.value,
asOfDate: chosenAsOfDate.value,
expirationDate: expiration,
})
.then((getStrikesForUnderlyingResponse) => {
availableStrikes.value = getStrikesForUnderlyingResponse;
chooseStrike(getStrikesForUnderlyingResponse[0]);
});
}
function chooseStrike(strike: string) {
chosenStrike.value = strike;
trpc.getOpensForOptionContract
.query({
underlying: chosenUnderlying.value,
expirationDate: chosenExpiration.value,
strike: parseFloat(strike),
})
.then((getOpensForOptionContractResponse) => {
optionContractUplotData.value = getOpensForOptionContractResponse;
});
}
export function CalendarOptimizer() {
const handleInit = useCallback(() => {
trpc.getAvailableUnderlyings
.query()
.then((availableUnderlyingsResponse) => {
availableUnderlyings.value = availableUnderlyingsResponse;
// load first underlying in list:
chooseUnderlying(availableUnderlyingsResponse[0]);
});
}, []);
const handleUnderlyingChange = useCallback((e) => {
console.log(`Chose Underlying: ${e.target.value}`);
chooseUnderlying(e.target.value);
}, []);
const handleAsOfDateChange = useCallback((e) => {
console.log(`Chose Date: ${e.target.value}`);
chooseAsOfDate(e.target.value);
}, []);
const handleExpirationChange = useCallback((e) => {
console.log(`Chose Expiration: ${e.target.value}`);
chooseExpiration(e.target.value);
}, []);
const handleStrikeChange = useCallback((e) => {
console.log(`Chose Strike: ${e.target.value}`);
chooseStrike(e.target.value);
}, []);
useEffect(handleInit, []);
return (
<div>
<div>
<label>Available Underlyings</label>
{availableUnderlyings.value.length === 0 ? (
"Loading..."
) : (
<select onChange={handleUnderlyingChange}>
{availableUnderlyings.value.map((availableUnderlying) => (
<option value={availableUnderlying}>{availableUnderlying}</option>
))}
</select>
)}
</div>
<div>
<label>Available "As-of" Dates</label>
{availableAsOfDates.value.length === 0 ? (
"Loading..."
) : (
<select onChange={handleAsOfDateChange}>
{availableAsOfDates.value.map((availableAsOfDate) => (
<option value={availableAsOfDate}>{availableAsOfDate}</option>
))}
</select>
)}
</div>
<div>
<label>Available Expirations</label>
{availableExpirations.value.length === 0 ? (
"Loading..."
) : (
<select onChange={handleExpirationChange}>
{availableExpirations.value.map((availableExpiration) => (
<option value={availableExpiration}>{availableExpiration}</option>
))}
</select>
)}
</div>
<div>
<label>Available Strikes</label>
{availableStrikes.value.length === 0 ? (
"Loading..."
) : (
<select onChange={handleStrikeChange}>
{availableStrikes.value.map((availableStrike) => (
<option value={availableStrike}>{availableStrike}</option>
))}
</select>
)}
</div>
<div className="chart-container">
{chosenUnderlying.value !== null &&
underlyingUplotData.value.length > 0 ? (
<div className="chart">
<Scatter
data={{
datasets: [
{
label: "Stock Open Price",
data: underlyingUplotData.value,
},
],
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time",
},
ticks: {
callback: function (value, index, ticks) {
return new Date((value as number) * 1000)
.toISOString()
.substring(0, 10);
},
},
// min: (new Date(chosenLookbackPeriodStart.value)).getTime()/1000,
// max: (new Date(chosenLookbackPeriodEnd.value)).getTime()/1000,
},
y: {
beginAtZero: false,
ticks: {
callback: function (value, index, ticks) {
return "$" + value.toString();
},
},
// min: 0,
// max: maxChartPrice.value,
},
},
elements: {
point: {
radius: 1,
borderWidth: 0,
},
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: "Stock Price",
},
},
animation: false,
maintainAspectRatio: false,
}}
/>
</div>
) : (
<></>
)}
{chosenUnderlying.value !== null &&
chosenAsOfDate.value !== null &&
chosenExpiration.value !== null &&
chosenStrike.value !== null &&
optionContractUplotData.value.length > 0 ? (
<div className="chart">
<Scatter
data={{
datasets: [
{
label: "Option Contract Open Price",
data: optionContractUplotData.value,
},
],
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time",
},
ticks: {
callback: function (value, index, ticks) {
return new Date((value as number) * 1000)
.toISOString()
.substring(0, 10);
},
},
// min: (new Date(chosenLookbackPeriodStart.value)).getTime()/1000,
// max: (new Date(chosenLookbackPeriodEnd.value)).getTime()/1000,
},
y: {
beginAtZero: false,
ticks: {
callback: function (value, index, ticks) {
return "$" + value.toString();
},
},
// min: 0,
// max: maxChartPrice.value,
},
},
elements: {
point: {
radius: 1,
borderWidth: 0,
},
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: "Option Contract Price",
},
},
animation: false,
maintainAspectRatio: false,
}}
/>
</div>
) : (
<></>
)}
</div>
</div>
);
}

@ -1,11 +0,0 @@
.chart-container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-around;
width: 1800px;
height: 480px;
}
.chart-container > .chart {
width: 880px;
}

@ -0,0 +1,500 @@
import { signal, computed } from "@preact/signals";
import { useEffect } from "preact/hooks";
import { trpc } from "../trpc.js";
import {
Chart as ChartJS,
LinearScale,
CategoryScale,
PointElement,
Tooltip,
Title,
} from "chart.js";
import { Scatter } from "react-chartjs-2";
// import './style.css';
ChartJS.register(LinearScale, CategoryScale, PointElement, Tooltip, Title);
const availableUnderlyings = signal([]);
const chosenUnderlying = signal(null);
const chosenDaysToFrontExpiration = signal(14);
const chosenDaysBetweenFrontAndBackExpiration = signal(14);
const chosenStrikePercentageFromUnderlyingPrice = signal(1.4);
const chosenStrikePercentageFromUnderlyingPriceRadius = signal(0.05);
const chosenExitToFrontExpiration = signal(2);
const historicalStockQuoteChartData = signal([]);
const historicalCalendarQuoteChartData = signal([]);
const historicalCalendarExitQuoteChartData = signal([]);
const chosenLookbackPeriodStart = signal("2022-01-01");
const chosenLookbackPeriodEnd = signal("2024-01-01");
const maxChartPrice = computed(() =>
Math.max(
Math.max.apply(
null,
historicalCalendarQuoteChartData.value.map((d) => d.y)
),
Math.max.apply(
null,
historicalCalendarExitQuoteChartData.value.map((d) => d.y)
)
)
);
const refreshHistoricalStockQuoteChartData = () => {
trpc.getHistoricalStockQuoteChartData
.query({
underlying: chosenUnderlying.value,
lookbackPeriodStart: chosenLookbackPeriodStart.value,
lookbackPeriodEnd: chosenLookbackPeriodEnd.value,
})
.then((getHistoricalStockQuoteChartDataResponse) => {
historicalStockQuoteChartData.value =
getHistoricalStockQuoteChartDataResponse;
});
};
const refreshHistoricalCalendarQuoteChartData = () => {
trpc.getHistoricalCalendarQuoteChartData
.query({
underlying: chosenUnderlying.value,
daysToFrontExpiration: chosenDaysToFrontExpiration.value,
daysBetweenFrontAndBackExpiration:
chosenDaysBetweenFrontAndBackExpiration.value,
strikePercentageFromUnderlyingPriceRangeMin:
chosenStrikePercentageFromUnderlyingPrice.value -
chosenStrikePercentageFromUnderlyingPriceRadius.value,
strikePercentageFromUnderlyingPriceRangeMax:
chosenStrikePercentageFromUnderlyingPrice.value +
chosenStrikePercentageFromUnderlyingPriceRadius.value,
lookbackPeriodStart: chosenLookbackPeriodStart.value,
lookbackPeriodEnd: chosenLookbackPeriodEnd.value,
})
.then((getHistoricalCalendarQuoteChartDataResponse) => {
historicalCalendarQuoteChartData.value =
getHistoricalCalendarQuoteChartDataResponse;
});
};
const refreshHistoricalCalendarExitQuoteChartData = () => {
trpc.getHistoricalCalendarExitQuoteChartData
.query({
underlying: chosenUnderlying.value,
daysToFrontExpiration: chosenExitToFrontExpiration.value,
daysBetweenFrontAndBackExpiration:
chosenDaysBetweenFrontAndBackExpiration.value,
lookbackPeriodStart: chosenLookbackPeriodStart.value,
lookbackPeriodEnd: chosenLookbackPeriodEnd.value,
})
.then((getHistoricalCalendarExitQuoteChartDataResponse) => {
historicalCalendarExitQuoteChartData.value =
getHistoricalCalendarExitQuoteChartDataResponse;
});
};
const handleInit = () => {
trpc.getAvailableUnderlyings.query().then((availableUnderlyingsResponse) => {
availableUnderlyings.value = availableUnderlyingsResponse;
chosenUnderlying.value = availableUnderlyingsResponse[0];
refreshHistoricalStockQuoteChartData();
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
});
};
const handleUnderlyingChange = (e) => {
if (chosenUnderlying.value !== e.target.value) {
chosenUnderlying.value = e.target.value;
refreshHistoricalStockQuoteChartData();
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
}
};
const handleDaysToFrontExpirationChange = (e) => {
if (chosenDaysToFrontExpiration.value !== parseInt(e.target.value)) {
chosenDaysToFrontExpiration.value = parseInt(e.target.value);
refreshHistoricalCalendarQuoteChartData();
}
};
const handleDaysBetweenFrontAndBackExpirationChange = (e) => {
if (
chosenDaysBetweenFrontAndBackExpiration.value !== parseInt(e.target.value)
) {
chosenDaysBetweenFrontAndBackExpiration.value = parseInt(e.target.value);
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
}
};
const handleStrikePercentageFromUnderlyingPriceChange = (e) => {
if (
chosenStrikePercentageFromUnderlyingPrice.value !==
parseFloat(e.target.value)
) {
chosenStrikePercentageFromUnderlyingPrice.value = parseFloat(
e.target.value
);
refreshHistoricalCalendarQuoteChartData();
}
};
const handleStrikePercentageFromUnderlyingPriceRadiusChange = (e) => {
if (
chosenStrikePercentageFromUnderlyingPriceRadius.value !==
parseFloat(e.target.value)
) {
chosenStrikePercentageFromUnderlyingPriceRadius.value = parseFloat(
e.target.value
);
refreshHistoricalCalendarQuoteChartData();
}
};
const handleExitToFrontExpirationChange = (e) => {
if (chosenExitToFrontExpiration.value !== parseInt(e.target.value)) {
chosenExitToFrontExpiration.value = parseInt(e.target.value);
refreshHistoricalCalendarExitQuoteChartData();
}
};
const handleLookbackPeriodStartChange = (e) => {
if (chosenLookbackPeriodStart.value !== e.target.value) {
chosenLookbackPeriodStart.value = e.target.value;
refreshHistoricalStockQuoteChartData();
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
}
};
const handleLookbackPeriodEndChange = (e) => {
if (chosenLookbackPeriodEnd.value !== e.target.value) {
chosenLookbackPeriodEnd.value = e.target.value;
refreshHistoricalStockQuoteChartData();
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
}
};
export function HistoricalCalendarPrices() {
useEffect(handleInit, []);
return (
/* container for centering: */
<div class="flex flex-row justify-center">
<div class="flex flex-col justify-start gap-4">
{/* inputs form container: */}
<div class="flex flex-col justify-start gap-1 divide-y">
<div class="flex flex-row w-160 gap-3">
<div class="text-right w-1/3">
<label>Available Underlyings</label>
</div>
<div class="my-auto w-2/3">
{availableUnderlyings.value.length === 0 ? (
"Loading..."
) : (
<select
onChange={handleUnderlyingChange}
class="border border-gray-300 focus:border-blue-400"
>
{availableUnderlyings.value.map((availableUnderlying) => (
<option value={availableUnderlying}>
{availableUnderlying}
</option>
))}
</select>
)}
</div>
</div>
<div class="flex flex-row w-160 gap-3">
<div class="text-right w-1/3">
<label>Now-to-Front-Month "Days to Expiration"</label>
</div>
<div class="my-auto w-2/3">
<input
type="text"
onBlur={handleDaysToFrontExpirationChange}
value={chosenDaysToFrontExpiration.value}
class="border border-gray-300 focus:border-blue-400"
/>
Days
</div>
</div>
<div class="flex flex-row w-160 gap-3">
<div class="text-right w-1/3">
<label>Front-to-Back-Month "Days to Expiration" Difference</label>
</div>
<div class="my-auto w-2/3">
<input
type="text"
onBlur={handleDaysBetweenFrontAndBackExpirationChange}
value={chosenDaysBetweenFrontAndBackExpiration.value}
class="border border-gray-300 focus:border-blue-400"
/>
Days Difference
</div>
</div>
<div class="flex flex-row w-160 gap-3">
<div class="text-right w-1/3">
<label>"Strike Percentage From Underlying Price" Range</label>
</div>
<div class="my-auto w-2/3">
<input
type="text"
onBlur={handleStrikePercentageFromUnderlyingPriceChange}
value={chosenStrikePercentageFromUnderlyingPrice.value}
class="border border-gray-300 focus:border-blue-400"
/>
% +/-
<input
type="text"
onBlur={handleStrikePercentageFromUnderlyingPriceRadiusChange}
value={chosenStrikePercentageFromUnderlyingPriceRadius.value}
class="border border-gray-300 focus:border-blue-400"
/>
% from ATM
</div>
</div>
<div class="flex flex-row w-160 gap-3">
<div class="text-right w-1/3">
<label>Exit-to-Front-Month "Days to Expiration"</label>
</div>
<div class="my-auto w-2/3">
<input
type="text"
onBlur={handleExitToFrontExpirationChange}
value={chosenExitToFrontExpiration.value}
class="border border-gray-300 focus:border-blue-400"
/>
Days
</div>
</div>
<div class="flex flex-row w-160 gap-3">
<div class="text-right w-1/3">
<label>Lookback Period</label>
</div>
<div class="my-auto w-2/3">
<input
type="text"
onBlur={handleLookbackPeriodStartChange}
value={chosenLookbackPeriodStart.value}
class="border border-gray-300 focus:border-blue-400"
/>
-
<input
type="text"
onBlur={handleLookbackPeriodEndChange}
value={chosenLookbackPeriodEnd.value}
class="border border-gray-300 focus:border-blue-400"
/>
</div>
</div>
</div>
{/* charts container: */}
<div>
<div className="chart-container">
{chosenUnderlying.value !== null &&
historicalStockQuoteChartData.value.length > 0 ? (
<div className="chart">
<Scatter
data={{
datasets: [
{
label: "Stock Open Price",
data: historicalStockQuoteChartData.value,
},
],
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time",
},
ticks: {
callback: function (value, index, ticks) {
return new Date((value as number) * 1000)
.toISOString()
.substring(0, 10);
},
},
min:
new Date(chosenLookbackPeriodStart.value).getTime() /
1000,
max:
new Date(chosenLookbackPeriodEnd.value).getTime() /
1000,
},
y: {
beginAtZero: false,
ticks: {
callback: function (value, index, ticks) {
return "$" + 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,
}}
/>
</div>
) : (
<div>Loading Chart...</div>
)}
</div>
<div className="chart-container">
{chosenUnderlying.value !== null &&
historicalCalendarQuoteChartData.value.length > 0 ? (
<div className="chart">
<Scatter
data={{
datasets: [
{
label: "Calendar Open Price",
data: historicalCalendarQuoteChartData.value,
},
],
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time",
},
ticks: {
callback: function (value, index, ticks) {
return new Date((value as number) * 1000)
.toISOString()
.substring(0, 10);
},
},
min:
new Date(chosenLookbackPeriodStart.value).getTime() /
1000,
max:
new Date(chosenLookbackPeriodEnd.value).getTime() /
1000,
},
y: {
beginAtZero: true,
ticks: {
callback: function (value, index, ticks) {
return "$" + value.toString();
},
},
min: 0,
max: maxChartPrice.value,
},
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: "Calendar Price (Under Like Conditions)",
},
},
animation: false,
maintainAspectRatio: false,
}}
/>
</div>
) : (
<div>Loading Chart...</div>
)}
{chosenUnderlying.value !== null &&
historicalCalendarQuoteChartData.value.length > 0 ? (
<div className="chart">
<Scatter
data={{
datasets: [
{
label: "Calendar Exit Price",
data: historicalCalendarExitQuoteChartData.value,
},
],
}}
options={{
scales: {
x: {
type: "linear",
beginAtZero: false,
title: {
display: true,
text: "%-From-the-Money",
},
ticks: {
callback: function (value, index, ticks) {
return value.toString() + "%";
},
},
},
y: {
beginAtZero: true,
ticks: {
callback: function (value, index, ticks) {
return "$" + value.toString();
},
},
min: 0,
max: maxChartPrice.value,
},
},
elements: {
point: {
borderWidth: 0,
},
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: [
"Calendar Prices at Exit",
"by %-age from-the-money",
],
},
},
animation: false,
maintainAspectRatio: false,
}}
/>
</div>
) : (
<div>Loading Chart...</div>
)}
</div>
</div>
</div>
</div>
);
}

@ -1,380 +0,0 @@
import { signal, computed } from "@preact/signals";
import { useEffect } from "preact/hooks";
import {trpc} from '../../trpc.js';
import {
Chart as ChartJS,
LinearScale,
CategoryScale,
PointElement,
Tooltip,
Title,
} from 'chart.js';
import { Scatter } from 'react-chartjs-2';
import './style.css';
ChartJS.register(LinearScale, CategoryScale, PointElement, Tooltip, Title,);
const availableUnderlyings = signal([]);
const chosenUnderlying = signal(null);
const chosenDaysToFrontExpiration = signal(14);
const chosenDaysBetweenFrontAndBackExpiration = signal(14);
const chosenStrikePercentageFromUnderlyingPrice = signal(1.4);
const chosenStrikePercentageFromUnderlyingPriceRadius = signal(0.05);
const chosenExitToFrontExpiration = signal(2);
const historicalStockQuoteChartData = signal([]);
const historicalCalendarQuoteChartData = signal([]);
const historicalCalendarExitQuoteChartData = signal([]);
const chosenLookbackPeriodStart = signal("2022-01-01");
const chosenLookbackPeriodEnd = signal("2024-01-01");
const maxChartPrice = computed(()=>
Math.max(
Math.max.apply(null, historicalCalendarQuoteChartData.value.map(d=>d.y)),
Math.max.apply(null, historicalCalendarExitQuoteChartData.value.map(d=>d.y)),
)
);
const refreshHistoricalStockQuoteChartData = ()=>{
trpc.getHistoricalStockQuoteChartData
.query({
underlying:chosenUnderlying.value,
lookbackPeriodStart:chosenLookbackPeriodStart.value,
lookbackPeriodEnd:chosenLookbackPeriodEnd.value,
})
.then((getHistoricalStockQuoteChartDataResponse)=>{
historicalStockQuoteChartData.value = getHistoricalStockQuoteChartDataResponse;
})
};
const refreshHistoricalCalendarQuoteChartData = ()=>{
trpc.getHistoricalCalendarQuoteChartData
.query({
underlying:chosenUnderlying.value,
daysToFrontExpiration:chosenDaysToFrontExpiration.value,
daysBetweenFrontAndBackExpiration:chosenDaysBetweenFrontAndBackExpiration.value,
strikePercentageFromUnderlyingPriceRangeMin:chosenStrikePercentageFromUnderlyingPrice.value - chosenStrikePercentageFromUnderlyingPriceRadius.value,
strikePercentageFromUnderlyingPriceRangeMax:chosenStrikePercentageFromUnderlyingPrice.value + chosenStrikePercentageFromUnderlyingPriceRadius.value,
lookbackPeriodStart:chosenLookbackPeriodStart.value,
lookbackPeriodEnd:chosenLookbackPeriodEnd.value,
})
.then((getHistoricalCalendarQuoteChartDataResponse)=>{
historicalCalendarQuoteChartData.value = getHistoricalCalendarQuoteChartDataResponse;
})
};
const refreshHistoricalCalendarExitQuoteChartData = ()=>{
trpc.getHistoricalCalendarExitQuoteChartData
.query({
underlying:chosenUnderlying.value,
daysToFrontExpiration:chosenExitToFrontExpiration.value,
daysBetweenFrontAndBackExpiration:chosenDaysBetweenFrontAndBackExpiration.value,
lookbackPeriodStart:chosenLookbackPeriodStart.value,
lookbackPeriodEnd:chosenLookbackPeriodEnd.value,
})
.then((getHistoricalCalendarExitQuoteChartDataResponse)=>{
historicalCalendarExitQuoteChartData.value = getHistoricalCalendarExitQuoteChartDataResponse;
})
};
const handleInit = ()=>{
trpc.getAvailableUnderlyings
.query()
.then((availableUnderlyingsResponse)=>{
availableUnderlyings.value = availableUnderlyingsResponse;
chosenUnderlying.value = availableUnderlyingsResponse[0];
refreshHistoricalStockQuoteChartData();
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
});
};
const handleUnderlyingChange = (e)=>{
if(chosenUnderlying.value !== e.target.value){
chosenUnderlying.value = e.target.value;
refreshHistoricalStockQuoteChartData();
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
}
};
const handleDaysToFrontExpirationChange = (e)=>{
if(chosenDaysToFrontExpiration.value !== parseInt(e.target.value)){
chosenDaysToFrontExpiration.value = parseInt(e.target.value);
refreshHistoricalCalendarQuoteChartData();
}
};
const handleDaysBetweenFrontAndBackExpirationChange = (e)=>{
if(chosenDaysBetweenFrontAndBackExpiration.value !== parseInt(e.target.value)){
chosenDaysBetweenFrontAndBackExpiration.value = parseInt(e.target.value);
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
}
};
const handleStrikePercentageFromUnderlyingPriceChange = (e)=>{
if(chosenStrikePercentageFromUnderlyingPrice.value !== parseFloat(e.target.value)){
chosenStrikePercentageFromUnderlyingPrice.value = parseFloat(e.target.value);
refreshHistoricalCalendarQuoteChartData();
}
};
const handleStrikePercentageFromUnderlyingPriceRadiusChange = (e)=>{
if(chosenStrikePercentageFromUnderlyingPriceRadius.value !== parseFloat(e.target.value)){
chosenStrikePercentageFromUnderlyingPriceRadius.value = parseFloat(e.target.value);
refreshHistoricalCalendarQuoteChartData();
}
};
const handleExitToFrontExpirationChange = (e)=>{
if(chosenExitToFrontExpiration.value !== parseInt(e.target.value)){
chosenExitToFrontExpiration.value = parseInt(e.target.value);
refreshHistoricalCalendarExitQuoteChartData();
}
};
const handleLookbackPeriodStartChange = (e)=>{
if(chosenLookbackPeriodStart.value !== e.target.value){
chosenLookbackPeriodStart.value = e.target.value;
refreshHistoricalStockQuoteChartData();
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
}
};
const handleLookbackPeriodEndChange = (e)=>{
if(chosenLookbackPeriodEnd.value !== e.target.value){
chosenLookbackPeriodEnd.value = e.target.value;
refreshHistoricalStockQuoteChartData();
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
}
};
export function HistoricalCalendarPrices(){
useEffect(handleInit, []);
return (
<div>
<div>
<label>Available Underlyings</label>
{
availableUnderlyings.value.length === 0
? "Loading..."
: <select onChange={handleUnderlyingChange}>
{availableUnderlyings.value.map((availableUnderlying)=>(
<option value={availableUnderlying}>{availableUnderlying}</option>
))}
</select>
}
</div>
<div>
<label>Now-to-Front-Month "Days to Expiration"</label>
<input type="text" onBlur={handleDaysToFrontExpirationChange} value={chosenDaysToFrontExpiration.value} />
Days
</div>
<div>
<label>Front-to-Back-Month "Days to Expiration" Difference</label>
<input type="text" onBlur={handleDaysBetweenFrontAndBackExpirationChange} value={chosenDaysBetweenFrontAndBackExpiration.value} />
Days Difference
</div>
<div>
<label>"Strike Percentage From Underlying Price" Range</label>
<input type="text" onBlur={handleStrikePercentageFromUnderlyingPriceChange} value={chosenStrikePercentageFromUnderlyingPrice.value} />
%
+/-
<input type="text" onBlur={handleStrikePercentageFromUnderlyingPriceRadiusChange} value={chosenStrikePercentageFromUnderlyingPriceRadius.value} />
% from ATM
</div>
<div>
<label>Exit-to-Front-Month "Days to Expiration"</label>
<input type="text" onBlur={handleExitToFrontExpirationChange} value={chosenExitToFrontExpiration.value} />
Days
</div>
<div>
<label>Lookback Period</label>
<input type="text" onBlur={handleLookbackPeriodStartChange} value={chosenLookbackPeriodStart.value} />
-
<input type="text" onBlur={handleLookbackPeriodEndChange} value={chosenLookbackPeriodEnd.value} />
</div>
<div className="chart-container">
{chosenUnderlying.value!==null && historicalStockQuoteChartData.value.length>0
? <div className="chart">
<Scatter
data={{
datasets: [
{
label: "Stock Open Price",
data: historicalStockQuoteChartData.value
}
]
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time"
},
ticks: {
callback: function(value, index, ticks) {
return (new Date(value as number*1000)).toISOString().substring(0,10);
}
},
min: (new Date(chosenLookbackPeriodStart.value)).getTime()/1000,
max: (new Date(chosenLookbackPeriodEnd.value)).getTime()/1000,
},
y: {
beginAtZero: false,
ticks: {
callback: function(value, index, ticks) {
return "$"+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,
}}
/>
</div>
: <div>Loading Chart...</div>}
</div>
<div className="chart-container">
{chosenUnderlying.value!==null && historicalCalendarQuoteChartData.value.length>0
? <div className="chart">
<Scatter
data={{
datasets: [
{
label: "Calendar Open Price",
data: historicalCalendarQuoteChartData.value
}
]
}}
options={{
scales: {
x: {
title: {
display: true,
text: "Time"
},
ticks: {
callback: function(value, index, ticks) {
return (new Date(value as number*1000)).toISOString().substring(0,10);
}
},
min: (new Date(chosenLookbackPeriodStart.value)).getTime()/1000,
max: (new Date(chosenLookbackPeriodEnd.value)).getTime()/1000,
},
y: {
beginAtZero: true,
ticks: {
callback: function(value, index, ticks) {
return "$"+value.toString();
}
},
min: 0,
max: maxChartPrice.value,
}
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: "Calendar Price (Under Like Conditions)"
},
},
animation: false,
maintainAspectRatio: false,
}}
/>
</div>
: <div>Loading Chart...</div>}
{chosenUnderlying.value!==null && historicalCalendarQuoteChartData.value.length>0
? <div className="chart">
<Scatter
data={{
datasets: [
{
label: "Calendar Exit Price",
data: historicalCalendarExitQuoteChartData.value
}
]
}}
options={{
scales: {
x: {
type: 'linear',
beginAtZero: false,
title: {
display: true,
text: "%-From-the-Money"
},
ticks: {
callback: function(value, index, ticks) {
return value.toString()+"%";
}
},
},
y: {
beginAtZero: true,
ticks: {
callback: function(value, index, ticks) {
return "$"+value.toString();
}
},
min: 0,
max: maxChartPrice.value,
},
},
elements: {
point: {
borderWidth: 0,
},
},
plugins: {
tooltip: {
enabled: false,
},
legend: {
display: false,
},
title: {
display: true,
text: ["Calendar Prices at Exit","by %-age from-the-money"]
},
},
animation: false,
maintainAspectRatio: false,
}}
/>
</div>
: <div>Loading Chart...</div>}
</div>
</div>
);
}

@ -1,11 +0,0 @@
.chart-container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-around;
width: 1800px;
height: 480px;
}
.chart-container > .chart {
width: 880px;
}

@ -1,47 +0,0 @@
img {
margin-bottom: 1.5rem;
}
img:hover {
filter: drop-shadow(0 0 2em #673ab8aa);
}
.home section {
margin-top: 5rem;
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 1.5rem;
}
.resource {
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
text-align: left;
text-decoration: none;
color: #222;
background-color: #f1f1f1;
border: 1px solid transparent;
}
.resource:hover {
border: 1px solid #000;
box-shadow: 0 25px 50px -12px #673ab888;
}
@media (max-width: 639px) {
.home section {
margin-top: 5rem;
grid-template-columns: 1fr;
row-gap: 1rem;
}
}
@media (prefers-color-scheme: dark) {
.resource {
color: #ccc;
background-color: #161616;
}
.resource:hover {
border: 1px solid #bbb;
}
}

@ -1,72 +0,0 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color: #222;
background-color: #ffffff;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
}
#app {
display: flex;
flex-direction: column;
min-height: 100vh;
justify-content: start;
}
header {
display: flex;
justify-content: flex-end;
background-color: #673ab8;
}
header nav {
display: flex;
}
header a {
color: #fff;
padding: 0.75rem;
text-decoration: none;
}
header a.active {
background-color: #0005;
}
header a:hover {
background-color: #0008;
}
main {
flex: auto;
display: flex;
flex-direction: column;
align-items: start;
max-width: 1280px;
justify-content: center;
text-align: center;
}
@media (max-width: 639px) {
main {
margin: 2rem;
}
}
@media (prefers-color-scheme: dark) {
:root {
color: #ccc;
background-color: #1a1a1a;
}
}

@ -0,0 +1,13 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
width: {
128: "32rem",
160: "40rem",
},
},
},
plugins: [],
};
Loading…
Cancel
Save