adjust css; develop atom pattern

master
Avraham Sakal 2 years ago
parent 582d868568
commit 9dec57681f

@ -0,0 +1,8 @@
Choose Underlying
Choose Strike
For each front-mont-back-month combination:
Lookup the cost to open the position
At the front month's expiration, the back-month will have a certain DTE. Determine what IV the back-month will need in order to offset this cost.
Determine the 30-day lo-hi range for IV, normalized for distance-from-the-money and time-to-expiry.
In other words, a naive 30-day-lo-hi isn't informative, because maybe it was low due to long time-to-expiry, or high due to distance-from-the-money
Normalize the IV that was determined to yield a profit, and see if it's higher than the bottom of the lo-hi range. If it is, it's safe; the only way to lose is for it to end-off having a lower IV than the 30-day record.

@ -3,7 +3,18 @@
flex-direction: column;
display: flex;
}
.App-module__app_gPMrEW__001 > .App-module__picker_gPMrEW__001 {
.App-module__app_gPMrEW__001 > .App-module__form_gPMrEW__001 {
flex-direction: column;
max-width: 30em;
display: flex;
}
.App-module__app_gPMrEW__001 > .App-module__form_gPMrEW__001 > .App-module__underlyingPrice_gPMrEW__001 {
flex-direction: row;
justify-content: flex-start;
gap: .6em;
display: flex;
}
.App-module__app_gPMrEW__001 > .App-module__form_gPMrEW__001 > .App-module__picker_gPMrEW__001 {
flex-direction: row;
justify-content: space-between;
display: flex;

@ -1,7 +1,9 @@
// src/App.module.css
var App_module_default = {
"app": "App-module__app_gPMrEW__001",
"picker": "App-module__picker_gPMrEW__001"
"form": "App-module__form_gPMrEW__001",
"picker": "App-module__picker_gPMrEW__001",
"underlyingPrice": "App-module__underlyingPrice_gPMrEW__001"
};
export {
App_module_default as default

13
dist/index.css vendored

@ -3,7 +3,18 @@
flex-direction: column;
display: flex;
}
.App-module__app_gPMrEW__001 > .App-module__picker_gPMrEW__001 {
.App-module__app_gPMrEW__001 > .App-module__form_gPMrEW__001 {
flex-direction: column;
max-width: 30em;
display: flex;
}
.App-module__app_gPMrEW__001 > .App-module__form_gPMrEW__001 > .App-module__underlyingPrice_gPMrEW__001 {
flex-direction: row;
justify-content: flex-start;
gap: .6em;
display: flex;
}
.App-module__app_gPMrEW__001 > .App-module__form_gPMrEW__001 > .App-module__picker_gPMrEW__001 {
flex-direction: row;
justify-content: space-between;
display: flex;

152
dist/index.js vendored

@ -1100,7 +1100,7 @@ var require_react_development = __commonJS({
var dispatcher = resolveDispatcher();
return dispatcher.useRef(initialValue);
}
function useEffect5(create, deps) {
function useEffect6(create, deps) {
var dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
@ -1116,7 +1116,7 @@ var require_react_development = __commonJS({
var dispatcher = resolveDispatcher();
return dispatcher.useCallback(callback2, deps);
}
function useMemo2(create, deps) {
function useMemo3(create, deps) {
var dispatcher = resolveDispatcher();
return dispatcher.useMemo(create, deps);
}
@ -1882,12 +1882,12 @@ var require_react_development = __commonJS({
exports.useContext = useContext2;
exports.useDebugValue = useDebugValue2;
exports.useDeferredValue = useDeferredValue;
exports.useEffect = useEffect5;
exports.useEffect = useEffect6;
exports.useId = useId;
exports.useImperativeHandle = useImperativeHandle;
exports.useInsertionEffect = useInsertionEffect;
exports.useLayoutEffect = useLayoutEffect;
exports.useMemo = useMemo2;
exports.useMemo = useMemo3;
exports.useReducer = useReducer2;
exports.useRef = useRef3;
exports.useState = useState;
@ -24379,10 +24379,10 @@ var require_react_jsx_runtime_development = __commonJS({
return jsxWithValidation(type, props, key, false);
}
}
var jsx6 = jsxWithValidationDynamic;
var jsx7 = jsxWithValidationDynamic;
var jsxs4 = jsxWithValidationStatic;
exports.Fragment = REACT_FRAGMENT_TYPE;
exports.jsx = jsx6;
exports.jsx = jsx7;
exports.jsxs = jsxs4;
})();
}
@ -24417,7 +24417,7 @@ function Header() {
var Header_default = Header;
// src/Picker.tsx
var import_react2 = __toESM(require_react(), 1);
var import_react3 = __toESM(require_react(), 1);
// node_modules/jotai/esm/vanilla.mjs
var keyCount = 0;
@ -25064,31 +25064,40 @@ function useAtom(atom2, options) {
];
}
// src/util.ts
var import_react2 = __toESM(require_react(), 1);
function useLocalAtom(initialValue, deps) {
return (0, import_react2.useMemo)(() => atom(initialValue), deps);
}
function useCommand(fn, deps) {
return useSetAtom((0, import_react2.useMemo)(() => atom(null, fn), deps));
}
// src/Picker.tsx
var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
function Picker({
$url,
$options = (0, import_react2.useMemo)(() => atom([]), []),
$isLoading = (0, import_react2.useMemo)(() => atom(true), []),
$isEnabled = (0, import_react2.useMemo)(() => atom(true), []),
$selectedOption = (0, import_react2.useMemo)(() => atom(""), [])
$options = (0, import_react3.useMemo)(() => atom([]), []),
$isLoading = (0, import_react3.useMemo)(() => atom(true), []),
$isEnabled = (0, import_react3.useMemo)(() => atom(true), []),
$selectedOptionValue = (0, import_react3.useMemo)(() => atom(""), [])
}) {
const url = useAtomValue($url);
const options = useAtomValue($options);
const isLoading = useAtomValue($isLoading);
const [selectedOption, setSelectedOption] = useAtom($selectedOption);
const [selectedOptionValue, setSelectedOptionValue] = useAtom($selectedOptionValue);
const isEnabled = useAtomValue($isEnabled);
const optionsFetched = useSetAtom((0, import_react2.useMemo)(() => atom(null, (get, set2, options2) => {
const handleFetchedOptions = useCommand((get, set2, options2) => {
set2($options, options2);
set2($isLoading, false);
}), [$options, $isLoading]));
(0, import_react2.useEffect)(() => {
}, [$options, $isLoading]);
(0, import_react3.useEffect)(() => {
if (isEnabled) {
fetch(url).then((x) => x.json()).catch((err) => ["AAPL", "MSFT", "GOOG"]).then(optionsFetched);
fetch(url).then((x) => x.json()).catch((err) => ["AAPL", "MSFT", "GOOG"]).then(handleFetchedOptions);
}
}, [url, isEnabled]);
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Loading..." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: selectedOption, onChange: (e) => {
setSelectedOption(e.target.value);
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Loading..." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: selectedOptionValue, onChange: (e) => {
setSelectedOptionValue(e.target.value);
}, children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "" }, ""),
options.map(
@ -25100,7 +25109,9 @@ function Picker({
// src/App.module.css
var App_module_default = {
"app": "App-module__app_gPMrEW__001",
"picker": "App-module__picker_gPMrEW__001"
"form": "App-module__form_gPMrEW__001",
"picker": "App-module__picker_gPMrEW__001",
"underlyingPrice": "App-module__underlyingPrice_gPMrEW__001"
};
// node_modules/@kurkle/color/dist/color.esm.js
@ -36540,7 +36551,7 @@ __publicField(TimeSeriesScale, "id", "timeseries");
__publicField(TimeSeriesScale, "defaults", TimeScale.defaults);
// node_modules/react-chartjs-2/dist/index.js
var import_react3 = __toESM(require_react(), 1);
var import_react4 = __toESM(require_react(), 1);
var defaultDatasetIdKey = "label";
function reforwardRef(ref, value) {
if (typeof ref === "function") {
@ -36585,8 +36596,8 @@ function cloneData(data) {
}
function ChartComponent(props, ref) {
const { height = 150, width = 300, redraw = false, datasetIdKey, type, data, options, plugins = [], fallbackContent, updateMode, ...canvasProps } = props;
const canvasRef = (0, import_react3.useRef)(null);
const chartRef = (0, import_react3.useRef)();
const canvasRef = (0, import_react4.useRef)(null);
const chartRef = (0, import_react4.useRef)();
const renderChart = () => {
if (!canvasRef.current)
return;
@ -36607,7 +36618,7 @@ function ChartComponent(props, ref) {
chartRef.current = null;
}
};
(0, import_react3.useEffect)(() => {
(0, import_react4.useEffect)(() => {
if (!redraw && chartRef.current && options) {
setOptions(chartRef.current, options);
}
@ -36615,7 +36626,7 @@ function ChartComponent(props, ref) {
redraw,
options
]);
(0, import_react3.useEffect)(() => {
(0, import_react4.useEffect)(() => {
if (!redraw && chartRef.current) {
setLabels(chartRef.current.config.data, data.labels);
}
@ -36623,7 +36634,7 @@ function ChartComponent(props, ref) {
redraw,
data.labels
]);
(0, import_react3.useEffect)(() => {
(0, import_react4.useEffect)(() => {
if (!redraw && chartRef.current && data.datasets) {
setDatasets(chartRef.current.config.data, data.datasets, datasetIdKey);
}
@ -36631,7 +36642,7 @@ function ChartComponent(props, ref) {
redraw,
data.datasets
]);
(0, import_react3.useEffect)(() => {
(0, import_react4.useEffect)(() => {
if (!chartRef.current)
return;
if (redraw) {
@ -36647,7 +36658,7 @@ function ChartComponent(props, ref) {
data.datasets,
updateMode
]);
(0, import_react3.useEffect)(() => {
(0, import_react4.useEffect)(() => {
if (!chartRef.current)
return;
destroyChart();
@ -36655,21 +36666,21 @@ function ChartComponent(props, ref) {
}, [
type
]);
(0, import_react3.useEffect)(() => {
(0, import_react4.useEffect)(() => {
renderChart();
return () => destroyChart();
}, []);
return /* @__PURE__ */ import_react3.default.createElement("canvas", Object.assign({
return /* @__PURE__ */ import_react4.default.createElement("canvas", Object.assign({
ref: canvasRef,
role: "img",
height,
width
}, canvasProps), fallbackContent);
}
var Chart2 = /* @__PURE__ */ (0, import_react3.forwardRef)(ChartComponent);
var Chart2 = /* @__PURE__ */ (0, import_react4.forwardRef)(ChartComponent);
function createTypedChart(type, registerables) {
Chart.register(registerables);
return /* @__PURE__ */ (0, import_react3.forwardRef)((props, ref) => /* @__PURE__ */ import_react3.default.createElement(Chart2, Object.assign({}, props, {
return /* @__PURE__ */ (0, import_react4.forwardRef)((props, ref) => /* @__PURE__ */ import_react4.default.createElement(Chart2, Object.assign({}, props, {
ref,
type
})));
@ -36784,7 +36795,7 @@ function loadable(anAtom) {
}
// src/CalendarPricesChart.tsx
var import_react4 = __toESM(require_react(), 1);
var import_react5 = __toESM(require_react(), 1);
var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
Chart.register(LineElement, plugin_tooltip, plugin_legend, CategoryScale, LinearScale, PointElement);
var $prices = loadable(atom(async (get) => {
@ -36800,7 +36811,7 @@ var $prices = loadable(atom(async (get) => {
}));
function CalendarPricesChart() {
const prices = useAtomValue($prices);
(0, import_react4.useEffect)(() => {
(0, import_react5.useEffect)(() => {
console.log(prices);
}, [prices]);
if (prices.state === "hasData") {
@ -36826,8 +36837,25 @@ function CalendarPricesChart() {
}
}
// src/App.tsx
// src/UnderlyingPrice.tsx
var import_react6 = __toESM(require_react(), 1);
var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
function UnderlyingPrice({ $underlying, $quoteDate }) {
const underlying = useAtomValue($underlying);
const quoteDate = useAtomValue($quoteDate);
const $underlyingPrice = useLocalAtom("", [$underlying, $quoteDate]);
const [underlyingPrice, setUnderlyingPrice] = useAtom($underlyingPrice);
const handleInit = useCommand(() => {
fetch(`${baseUrl}/underlying_quotes/${underlying}/${quoteDate.substring(0, 10)}`).then((x) => x.json()).then((rows) => {
setUnderlyingPrice(rows[0].close.toString());
});
}, [underlying, quoteDate]);
(0, import_react6.useEffect)(handleInit, [underlying, quoteDate]);
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: underlyingPrice });
}
// src/App.tsx
var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
var baseUrl = "http://127.0.0.1:8234";
var $underlyingsUrl = atom(`${baseUrl}/option_quotes/underlyings`);
var $selectedUnderlying = atom("");
@ -36844,38 +36872,46 @@ var $backMonthExpirationPickerUrl = atom((get) => `${baseUrl}/option_quotes/${ge
var $isBackMonthExpirationPickerEnabled = atom((get) => get($selectedQuoteDate) !== "" && get($selectedUnderlying) !== "");
var $selectedBackExpiration = atom("");
function App() {
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: App_module_default.app, children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Header_default, {}),
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: App_module_default.picker, children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "Underlying" }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Picker, { $url: $underlyingsUrl, $selectedOption: $selectedUnderlying })
] }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: App_module_default.picker, children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "Quote Date" }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Picker, { $url: $quoteDatePickerUrl, $isEnabled: $isQuoteDatePickerEnabled, $selectedOption: $selectedQuoteDate })
const selectedUnderlying = useAtomValue($selectedUnderlying);
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: App_module_default.app, children: [
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Header_default, {}),
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: App_module_default.form, children: [
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: App_module_default.underlyingPrice, children: [
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Underlying: " }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: selectedUnderlying }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UnderlyingPrice, { $underlying: $selectedUnderlying, $quoteDate: $selectedQuoteDate })
] }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: App_module_default.picker, children: [
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { children: "Underlying" }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Picker, { $url: $underlyingsUrl, $selectedOptionValue: $selectedUnderlying })
] }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: App_module_default.picker, children: [
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { children: "Quote Date" }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Picker, { $url: $quoteDatePickerUrl, $isEnabled: $isQuoteDatePickerEnabled, $selectedOptionValue: $selectedQuoteDate })
] }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: App_module_default.picker, children: [
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { children: "Strike" }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Picker, { $url: $strikePickerUrl, $isEnabled: $isStrikePickerEnabled, $selectedOptionValue: $selectedStrike })
] }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: App_module_default.picker, children: [
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { children: "Front Expiration" }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Picker, { $url: $frontMonthExpirationPickerUrl, $isEnabled: $isFrontMonthExpirationPickerEnabled, $selectedOptionValue: $selectedFrontExpiration })
] }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: App_module_default.picker, children: [
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { children: "Back Expiration" }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Picker, { $url: $backMonthExpirationPickerUrl, $isEnabled: $isBackMonthExpirationPickerEnabled, $selectedOptionValue: $selectedBackExpiration })
] })
] }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: App_module_default.picker, children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "Strike" }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Picker, { $url: $strikePickerUrl, $isEnabled: $isStrikePickerEnabled, $selectedOption: $selectedStrike })
] }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: App_module_default.picker, children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "Front Expiration" }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Picker, { $url: $frontMonthExpirationPickerUrl, $isEnabled: $isFrontMonthExpirationPickerEnabled, $selectedOption: $selectedFrontExpiration })
] }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: App_module_default.picker, children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "Back Expiration" }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Picker, { $url: $backMonthExpirationPickerUrl, $isEnabled: $isBackMonthExpirationPickerEnabled, $selectedOption: $selectedBackExpiration })
] }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CalendarPricesChart, {})
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CalendarPricesChart, {})
] });
}
var App_default = App;
// src/index.tsx
var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
var rootEl = document.getElementById("app");
var Root = (0, import_client.createRoot)(rootEl);
Root.render(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(App_default, {}));
Root.render(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(App_default, {}));
/*! Bundled license information:
react/cjs/react.development.js:

@ -2,7 +2,18 @@
display:flex;
flex-direction: column;
}
.app > .picker {
.app > .form {
display:flex;
flex-direction: column;
max-width: 30em;
}
.app > .form > .underlyingPrice {
display: flex;
flex-direction: row;
justify-content: flex-start;
gap: 0.6em;
}
.app > .form > .picker {
display: flex;
flex-direction: row;
justify-content: space-between;

@ -2,12 +2,11 @@ import Header from './Header';
import { HistoricalImpliedVolatilityChart } from "./HistoricalImpliedVolatilityChart";
import { Picker } from './Picker';
import { atom as $, useAtomValue } from 'jotai';
import { loadable } from 'jotai/utils';
//import './index.css';
//@ts-ignore
import k from './App.module.css';
import { useEffect } from 'react';
import { CalendarPricesChart } from './CalendarPricesChart';
import { UnderlyingPrice } from './UnderlyingPrice';
export const baseUrl = 'http://127.0.0.1:8234';
@ -32,16 +31,19 @@ export const $isBackMonthExpirationPickerEnabled = $((get) => get($selectedQuote
export const $selectedBackExpiration = $('');
function App() {
const selectedUnderlying = useAtomValue($selectedUnderlying);
return (
<div className={k.app}>
<Header />
<div className={k.picker}><label>Underlying</label><Picker $url={$underlyingsUrl} $selectedOption={$selectedUnderlying} /></div>
<div className={k.picker}><label>Quote Date</label><Picker $url={$quoteDatePickerUrl} $isEnabled={$isQuoteDatePickerEnabled} $selectedOption={$selectedQuoteDate}/></div>
<div className={k.picker}><label>Strike</label><Picker $url={$strikePickerUrl} $isEnabled={$isStrikePickerEnabled} $selectedOption={$selectedStrike}/></div>
<div className={k.picker}><label>Front Expiration</label><Picker $url={$frontMonthExpirationPickerUrl} $isEnabled={$isFrontMonthExpirationPickerEnabled} $selectedOption={$selectedFrontExpiration} /></div>
<div className={k.picker}><label>Back Expiration</label><Picker $url={$backMonthExpirationPickerUrl} $isEnabled={$isBackMonthExpirationPickerEnabled} $selectedOption={$selectedBackExpiration} /></div>
<div className={k.form}>
<div className={k.underlyingPrice}><span>Underlying: </span><span>{selectedUnderlying}</span><UnderlyingPrice $underlying={$selectedUnderlying} $quoteDate={$selectedQuoteDate} /></div>
<div className={k.picker}><label>Underlying</label><Picker $url={$underlyingsUrl} $selectedOptionValue={$selectedUnderlying} /></div>
<div className={k.picker}><label>Quote Date</label><Picker $url={$quoteDatePickerUrl} $isEnabled={$isQuoteDatePickerEnabled} $selectedOptionValue={$selectedQuoteDate}/></div>
<div className={k.picker}><label>Strike</label><Picker $url={$strikePickerUrl} $isEnabled={$isStrikePickerEnabled} $selectedOptionValue={$selectedStrike}/></div>
<div className={k.picker}><label>Front Expiration</label><Picker $url={$frontMonthExpirationPickerUrl} $isEnabled={$isFrontMonthExpirationPickerEnabled} $selectedOptionValue={$selectedFrontExpiration} /></div>
<div className={k.picker}><label>Back Expiration</label><Picker $url={$backMonthExpirationPickerUrl} $isEnabled={$isBackMonthExpirationPickerEnabled} $selectedOptionValue={$selectedBackExpiration} /></div>
</div>
<CalendarPricesChart />
{/* <HistoricalImpliedVolatilityChart /> */}
</div>

@ -1,10 +1,12 @@
import { useEffect, useMemo } from "react";
import { atom as $, useAtom, Atom, PrimitiveAtom, useAtomValue, useSetAtom, } from 'jotai';
import { baseUrl } from "./App";
import { useCommand } from "./util";
type PickerInput = {
$options?:PrimitiveAtom<Array<string>>,
$isLoading?:PrimitiveAtom<boolean>,
$selectedOption?:PrimitiveAtom<string>,
$selectedOptionValue?:PrimitiveAtom<string>,
$url:Atom<string>,
$isEnabled?:Atom<boolean>
};
@ -13,25 +15,25 @@ export function Picker({
$options = useMemo(()=>$([]), []),
$isLoading = useMemo(()=>$(true),[]),
$isEnabled = useMemo(()=>$(true),[]),
$selectedOption = useMemo(()=>$(''), [])
$selectedOptionValue = useMemo(()=>$(''), [])
}: PickerInput){
const url = useAtomValue($url);
const options = useAtomValue($options);
const isLoading = useAtomValue($isLoading);
const [selectedOption, setSelectedOption] = useAtom($selectedOption);
const [selectedOptionValue, setSelectedOptionValue] = useAtom($selectedOptionValue);
const isEnabled = useAtomValue($isEnabled);
const optionsFetched = useSetAtom(useMemo(()=>$(null, (get,set,options)=>{
const handleFetchedOptions = useCommand((get,set,options)=>{
set($options, options);
set($isLoading, false);
}),[$options, $isLoading]));
}, [$options, $isLoading]);
useEffect(()=>{
if(isEnabled){
fetch(url)
.then(x=>x.json())
.catch((err)=>['AAPL', 'MSFT', 'GOOG'])
.then(optionsFetched)
.then(handleFetchedOptions)
}
},[url, isEnabled])
@ -41,7 +43,7 @@ export function Picker({
?
<span>Loading...</span>
:
<select value={selectedOption} onChange={(e)=>{ setSelectedOption(e.target.value); }}>
<select value={selectedOptionValue} onChange={(e)=>{ setSelectedOptionValue(e.target.value); }}>
<option key="" value=""></option>
{options.map((date)=>
<option key={date} value={date}>{date}</option>

@ -0,0 +1,23 @@
import { atom as $, useAtom, useAtomValue } from 'jotai';
import { useEffect } from 'react';
import { useCommand, useLocalAtom } from './util';
import { baseUrl } from './App';
export function UnderlyingPrice({$underlying, $quoteDate}){
const underlying = useAtomValue($underlying);
const quoteDate = useAtomValue($quoteDate) as String;
const $underlyingPrice = useLocalAtom('', [$underlying, $quoteDate]);
const [underlyingPrice, setUnderlyingPrice] = useAtom($underlyingPrice);
const handleInit = useCommand(()=>{
fetch(`${baseUrl}/underlying_quotes/${underlying}/${quoteDate.substring(0,10)}`).then((x)=>x.json())
.then((rows)=>{ setUnderlyingPrice(rows[0].close.toString()); });
},[underlying, quoteDate]);
//@ts-ignore
useEffect(handleInit,[underlying, quoteDate]);
return (
<span>{underlyingPrice}</span>
);
}

@ -0,0 +1,96 @@
import { useSetAtom, atom as $ } from "jotai";
import { useMemo } from "react";
// from [https://stackoverflow.com/a/14873282]
function erf(x) {
// save the sign of x
var sign = (x >= 0) ? 1 : -1;
x = Math.abs(x);
// constants
var a1 = 0.254829592;
var a2 = -0.284496736;
var a3 = 1.421413741;
var a4 = -1.453152027;
var a5 = 1.061405429;
var p = 0.3275911;
// A&S formula 7.1.26
var t = 1.0/(1.0 + p*x);
var y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
return sign * y; // erf(-x) = -erf(x);
}
// produced by ChatGPT
export function calculateImpliedVolatility(optionPrice, underlyingPrice, strikePrice, timeToExpiration, riskFreeRate, optionType, maxIterations = 100, tolerance = 0.0001) {
let iv = 0.5; // Initial guess for implied volatility
let epsilon = 1e-6; // Small value to avoid division by zero
for (let i = 0; i < maxIterations; i++) {
const optionPriceEstimate = calculateOptionPrice(underlyingPrice, strikePrice, timeToExpiration, iv, riskFreeRate, optionType);
const vega = calculateVega(underlyingPrice, strikePrice, timeToExpiration, iv, riskFreeRate);
const diff = optionPrice - optionPriceEstimate;
if (Math.abs(diff) < tolerance) {
return iv;
}
iv = iv + (diff / (vega || epsilon)); // Avoid division by zero
}
return NaN; // If max iterations are reached, return NaN (no convergence)
}
function calculateOptionPrice(S, K, T, impliedVolatility, r, optionType) {
const d1 = (Math.log(S / K) + (r + (impliedVolatility ** 2) / 2) * T) / (impliedVolatility * Math.sqrt(T));
const d2 = d1 - impliedVolatility * Math.sqrt(T);
if (optionType === 'call') {
return S * Math.exp(-r * T) * cumulativeDistributionFunction(d1) - K * Math.exp(-r * T) * cumulativeDistributionFunction(d2);
} else if (optionType === 'put') {
return K * Math.exp(-r * T) * cumulativeDistributionFunction(-d2) - S * Math.exp(-r * T) * cumulativeDistributionFunction(-d1);
} else {
throw new Error('Invalid option type. Use "call" or "put".');
}
}
function calculateVega(S, K, T, impliedVolatility, r) {
const d1 = (Math.log(S / K) + (r + (impliedVolatility ** 2) / 2) * T) / (impliedVolatility * Math.sqrt(T));
return S * Math.sqrt(T) * probabilityDensityFunction(d1);
}
function cumulativeDistributionFunction(x) {
return 0.5 * (1 + erf(x / Math.sqrt(2)));
}
function probabilityDensityFunction(x) {
return Math.exp(-0.5 * x ** 2) / Math.sqrt(2 * Math.PI);
}
// Example usage
/*
const optionPrice = 5.25; // Example option price
const underlyingPrice = 50; // Example underlying stock price
const strikePrice = 50; // Example strike price
const timeToExpiration = 0.25; // Example time to expiration (in years)
const riskFreeRate = 0.03; // Example risk-free interest rate
const optionType = 'call'; // Example option type ('call' or 'put')
const impliedVolatility = calculateImpliedVolatility(optionPrice, underlyingPrice, strikePrice, timeToExpiration, riskFreeRate, optionType);
console.log('Implied Volatility:', impliedVolatility);
*/
export function useLocalAtom(initialValue, deps){
return useMemo(()=>$(initialValue), deps);
}
/**
* Define a "command": a function that mutates state. It's passed `get` and `set` functions to access Jotai atoms, in addition to any other parameters.
* The function is memoized and is returned in the form of a Jotai "set" atom. It's called like any other function.
* @param fn The function to memoize
* @param deps Dependency array; when to re-memoize the function
* @returns
*/
export function useCommand(fn, deps){
return useSetAtom(useMemo(()=>$(null, fn),deps));
}
Loading…
Cancel
Save