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);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
@ -1,28 +1,29 @@
|
||||
import _ from './env';
|
||||
import { render } from 'preact';
|
||||
import { LocationProvider, Router, Route } from 'preact-iso';
|
||||
import _ from "./env";
|
||||
import { render } from "preact";
|
||||
import { LocationProvider, Router, Route } from "preact-iso";
|
||||
|
||||
import { Header } from './components/Header.jsx';
|
||||
import { Home } from './pages/Home/index.jsx';
|
||||
import { CalendarOptimizer } from './pages/CalendarOptimizer/index.jsx';
|
||||
import { NotFound } from './pages/_404.jsx';
|
||||
import './style.css';
|
||||
import { HistoricalCalendarPrices } from './pages/HistoricalCalendarPrices/HistoricalCalendarPrices.js';
|
||||
import { Header } from "./components/Header.jsx";
|
||||
import { CalendarOptimizer } from "./pages/CalendarOptimizer.js";
|
||||
import { NotFound } from "./pages/_404.jsx";
|
||||
// import './style.css';
|
||||
import { HistoricalCalendarPrices } from "./pages/HistoricalCalendarPrices.js";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<LocationProvider>
|
||||
<div class="flex flex-col justify-start gap-4">
|
||||
<Header />
|
||||
<main>
|
||||
<Router>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/" component={HistoricalCalendarPrices} />
|
||||
<Route path="/calendar-optimizer" component={CalendarOptimizer} />
|
||||
<Route path="/historical-calendar-prices" component={HistoricalCalendarPrices} />
|
||||
{/* <Route path="/historical-calendar-prices" component={HistoricalCalendarPrices} /> */}
|
||||
<Route default component={NotFound} />
|
||||
</Router>
|
||||
</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