revamp frontend with tailwind
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user