revamp frontend with tailwind
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>
|
||||||
|
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…
Reference in New Issue