You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
380 lines
13 KiB
TypeScript
380 lines
13 KiB
TypeScript
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>
|
|
);
|
|
} |