This commit is contained in:
Avraham Sakal
2024-02-04 15:36:28 -05:00
commit dc63f31647
33 changed files with 3423 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="27.68" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 296"><path fill="#673AB8" d="m128 0l128 73.9v147.8l-128 73.9L0 221.7V73.9z"></path><path fill="#FFF" d="M34.865 220.478c17.016 21.78 71.095 5.185 122.15-34.704c51.055-39.888 80.24-88.345 63.224-110.126c-17.017-21.78-71.095-5.184-122.15 34.704c-51.055 39.89-80.24 88.346-63.224 110.126Zm7.27-5.68c-5.644-7.222-3.178-21.402 7.573-39.253c11.322-18.797 30.541-39.548 54.06-57.923c23.52-18.375 48.303-32.004 69.281-38.442c19.922-6.113 34.277-5.075 39.92 2.148c5.644 7.223 3.178 21.403-7.573 39.254c-11.322 18.797-30.541 39.547-54.06 57.923c-23.52 18.375-48.304 32.004-69.281 38.441c-19.922 6.114-34.277 5.076-39.92-2.147Z"></path><path fill="#FFF" d="M220.239 220.478c17.017-21.78-12.169-70.237-63.224-110.126C105.96 70.464 51.88 53.868 34.865 75.648c-17.017 21.78 12.169 70.238 63.224 110.126c51.055 39.889 105.133 56.485 122.15 34.704Zm-7.27-5.68c-5.643 7.224-19.998 8.262-39.92 2.148c-20.978-6.437-45.761-20.066-69.28-38.441c-23.52-18.376-42.74-39.126-54.06-57.923c-10.752-17.851-13.218-32.03-7.575-39.254c5.644-7.223 19.999-8.261 39.92-2.148c20.978 6.438 45.762 20.067 69.281 38.442c23.52 18.375 42.739 39.126 54.06 57.923c10.752 17.85 13.218 32.03 7.574 39.254Z"></path><path fill="#FFF" d="M127.552 167.667c10.827 0 19.603-8.777 19.603-19.604c0-10.826-8.776-19.603-19.603-19.603c-10.827 0-19.604 8.777-19.604 19.603c0 10.827 8.777 19.604 19.604 19.604Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+24
View File
@@ -0,0 +1,24 @@
import { useLocation } from 'preact-iso';
export function Header() {
const { url } = useLocation();
return (
<header>
<nav>
<a href="/" class={url == '/' && 'active'}>
Home
</a>
<a href="/calendar-optimizer" class={url == '/calendar-optimizer' && 'active'}>
Calendar Optimizer
</a>
<a href="/historical-calendar-prices" class={url == '/historical-calendar-prices' && 'active'}>
Historical Calendar Prices
</a>
<a href="/404" class={url == '/404' && 'active'}>
404
</a>
</nav>
</header>
);
}
+27
View File
@@ -0,0 +1,27 @@
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';
export function App() {
return (
<LocationProvider>
<Header />
<main>
<Router>
<Route path="/" component={Home} />
<Route path="/calendar-optimizer" component={CalendarOptimizer} />
<Route path="/historical-calendar-prices" component={HistoricalCalendarPrices} />
<Route default component={NotFound} />
</Router>
</main>
</LocationProvider>
);
}
render(<App />, document.getElementById('app'));
@@ -0,0 +1,130 @@
import { signal } from "@preact/signals";
import { useCallback, useEffect } from "preact/hooks";
import {trpc} from '../../trpc.js';
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([]);
export function CalendarOptimizer(){
const handleInit = useCallback(()=>{
trpc.getAvailableUnderlyings
.query()
.then((availableUnderlyingsResponse)=>{
availableUnderlyings.value = availableUnderlyingsResponse;
});
},[]);
const handleUnderlyingChange = useCallback((e)=>{
console.log(`Chose Underlying: ${e.target.value}`);
chosenUnderlying.value = e.target.value;
trpc.getAvailableAsOfDates
.query({underlying:e.target.value})
.then((getAvailableAsOfDatesResponse)=>{
availableAsOfDates.value = getAvailableAsOfDatesResponse;
});
trpc.getOpensForUnderlying
.query({underlying:e.target.value})
.then((getOpensForUnderlyingResponse)=>{
underlyingUplotData.value = getOpensForUnderlyingResponse;
});
},[]);
const handleAsOfDateChange = useCallback((e)=>{
console.log(`Chose Date: ${e.target.value}`);
chosenAsOfDate.value = e.target.value;
trpc.getExpirationsForUnderlying
.query({underlying:chosenUnderlying.value, asOfDate:chosenAsOfDate.value})
.then((getExpirationsForUnderlyingResponse)=>{
availableExpirations.value = getExpirationsForUnderlyingResponse;
});
},[]);
const handleExpirationChange = useCallback((e)=>{
console.log(`Chose Expiration: ${e.target.value}`);
chosenExpiration.value = e.target.value;
trpc.getStrikesForUnderlying
.query({underlying:chosenUnderlying.value, asOfDate:chosenAsOfDate.value, expirationDate: e.target.value})
.then((getStrikesForUnderlyingResponse)=>{
availableStrikes.value = getStrikesForUnderlyingResponse;
});
},[]);
const handleStrikeChange = useCallback((e)=>{
console.log(`Chose Strike: ${e.target.value}`);
chosenStrike.value = e.target.value;
trpc.getOpensForOptionContract
.query({underlying:chosenUnderlying.value, expirationDate:chosenExpiration.value, strike:parseFloat(e.target.value)})
.then((getOpensForOptionContractResponse)=>{
optionContractUplotData.value = getOpensForOptionContractResponse;
});
},[]);
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>
{/* {chosenUnderlying.value!==null && underlyingUplotData.value.length>0
? <UPlot data={underlyingUplotData.value} title="Underlying" opts={uplotOpts}/>
: <></>}
{chosenUnderlying.value!==null && chosenAsOfDate.value!==null && chosenExpiration.value!==null && chosenStrike.value!==null && optionContractUplotData.value.length>0
? <UPlot data={optionContractUplotData.value} title="Option Contract" opts={uplotOpts}/>
: <></>} */}
</div>
);
}
@@ -0,0 +1,321 @@
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 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([]);
export function HistoricalCalendarPrices(){
const refreshHistoricalStockQuoteChartData = useCallback(()=>{
trpc.getHistoricalStockQuoteChartData
.query({
underlying:chosenUnderlying.value,
})
.then((getHistoricalStockQuoteChartDataResponse)=>{
historicalStockQuoteChartData.value = getHistoricalStockQuoteChartDataResponse;
})
},[]);
const refreshHistoricalCalendarQuoteChartData = useCallback(()=>{
trpc.getHistoricalCalendarQuoteChartData
.query({
underlying:chosenUnderlying.value,
daysToFrontExpiration:chosenDaysToFrontExpiration.value,
daysBetweenFrontAndBackExpiration:chosenDaysBetweenFrontAndBackExpiration.value,
strikePercentageFromUnderlyingPriceRangeMin:chosenStrikePercentageFromUnderlyingPrice.value - chosenStrikePercentageFromUnderlyingPriceRadius.value,
strikePercentageFromUnderlyingPriceRangeMax:chosenStrikePercentageFromUnderlyingPrice.value + chosenStrikePercentageFromUnderlyingPriceRadius.value,
})
.then((getHistoricalCalendarQuoteChartDataResponse)=>{
historicalCalendarQuoteChartData.value = getHistoricalCalendarQuoteChartDataResponse;
})
},[]);
const refreshHistoricalCalendarExitQuoteChartData = useCallback(()=>{
trpc.getHistoricalCalendarExitQuoteChartData
.query({
underlying:chosenUnderlying.value,
daysToFrontExpiration:chosenExitToFrontExpiration.value,
daysBetweenFrontAndBackExpiration:chosenDaysBetweenFrontAndBackExpiration.value,
})
.then((getHistoricalCalendarExitQuoteChartDataResponse)=>{
historicalCalendarExitQuoteChartData.value = getHistoricalCalendarExitQuoteChartDataResponse;
})
},[]);
const handleInit = useCallback(()=>{
trpc.getAvailableUnderlyings
.query()
.then((availableUnderlyingsResponse)=>{
availableUnderlyings.value = availableUnderlyingsResponse;
chosenUnderlying.value = availableUnderlyingsResponse[0];
refreshHistoricalStockQuoteChartData();
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
});
},[]);
const handleUnderlyingChange = useCallback((e)=>{
chosenUnderlying.value = e.target.value;
refreshHistoricalStockQuoteChartData();
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
},[]);
const handleDaysToFrontExpirationChange = useCallback((e)=>{
chosenDaysToFrontExpiration.value = parseInt(e.target.value);
refreshHistoricalCalendarQuoteChartData();
},[]);
const handleDaysBetweenFrontAndBackExpirationChange = useCallback((e)=>{
chosenDaysBetweenFrontAndBackExpiration.value = parseInt(e.target.value);
refreshHistoricalCalendarQuoteChartData();
refreshHistoricalCalendarExitQuoteChartData();
},[]);
const handleStrikePercentageFromUnderlyingPriceChange = useCallback((e)=>{
chosenStrikePercentageFromUnderlyingPrice.value = parseFloat(e.target.value);
refreshHistoricalCalendarQuoteChartData();
},[]);
const handleStrikePercentageFromUnderlyingPriceRadiusChange = useCallback((e)=>{
chosenStrikePercentageFromUnderlyingPriceRadius.value = parseFloat(e.target.value);
refreshHistoricalCalendarQuoteChartData();
},[]);
const handleExitToFrontExpirationChange = useCallback((e)=>{
chosenExitToFrontExpiration.value = parseInt(e.target.value);
refreshHistoricalCalendarExitQuoteChartData();
},[]);
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" onChange={handleDaysToFrontExpirationChange} value={chosenDaysToFrontExpiration.value} />
Days
</div>
<div>
<label>Front-to-Back-Month "Days to Expiration" Difference</label>
<input type="text" onChange={handleDaysBetweenFrontAndBackExpirationChange} value={chosenDaysBetweenFrontAndBackExpiration.value} />
Days Difference
</div>
<div>
<label>"Strike Percentage From Underlying Price" Range</label>
<input type="text" onChange={handleStrikePercentageFromUnderlyingPriceChange} value={chosenStrikePercentageFromUnderlyingPrice.value} />
%
+/-
<input type="text" onChange={handleStrikePercentageFromUnderlyingPriceRadiusChange} value={chosenStrikePercentageFromUnderlyingPriceRadius.value} />
% from ATM
</div>
<div>
<label>Exit-to-Front-Month "Days to Expiration"</label>
<input type="text" onChange={handleExitToFrontExpirationChange} value={chosenExitToFrontExpiration.value} />
Days
</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);
}
}
},
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);
}
}
},
y: {
beginAtZero: true,
ticks: {
callback: function(value, index, ticks) {
return "$"+value.toString();
}
}
}
},
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();
}
}
},
},
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>
);
}
@@ -0,0 +1,11 @@
.chart-container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-around;
width: 1800px;
height: 480px;
}
.chart-container > .chart {
width: 880px;
}
+47
View File
@@ -0,0 +1,47 @@
import preactLogo from '../../assets/preact.svg';
import './style.css';
export function Logo(){
return (
<a href="https://preactjs.com" target="_blank">
<img src={preactLogo} alt="Preact logo" height="160" width="160" />
</a>
);
}
export function Home() {
return (
<div class="home">
<Logo/>
<h1>Get Started with the Calendar Optimizer</h1>
<a href="/calendar-optimizer" target="_blank">Calendar Optimizer</a>
<a href="/historical-calendar-prices" target="_blank">Historical Calendar Prices</a>
<section>
<Resource
title="Learn Preact"
description="If you're new to Preact, try the interactive tutorial to learn important concepts"
href="https://preactjs.com/tutorial"
/>
<Resource
title="Differences to React"
description="If you're coming from React, you may want to check out our docs to see where Preact differs"
href="https://preactjs.com/guide/v10/differences-to-react"
/>
<Resource
title="Learn Vite"
description="To learn more about Vite and how you can customize it to fit your needs, take a look at their excellent documentation"
href="https://vitejs.dev"
/>
</section>
</div>
);
}
function Resource(props) {
return (
<a href={props.href} target="_blank" class="resource">
<h2>{props.title}</h2>
<p>{props.description}</p>
</a>
);
}
+47
View File
@@ -0,0 +1,47 @@
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;
}
}
+8
View File
@@ -0,0 +1,8 @@
export function NotFound() {
return (
<section>
<h1>404: Not Found</h1>
<p>It's gone :(</p>
</section>
);
}
+72
View File
@@ -0,0 +1,72 @@
: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;
}
}
+15
View File
@@ -0,0 +1,15 @@
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../../server/src/index';
export const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
// `import.meta.env` is what Vite uses to expose envvars:
url: import.meta.env.VITE_SERVER_BASE_URL || 'https://calendar-optimizer-server.sakal.us',
}),
],
transformer: {
serialize: (x)=>x,
deserialize: (x)=>x,
}
});