adjust css; develop atom pattern
This commit is contained in:
+12
-1
@@ -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;
|
||||
|
||||
+10
-8
@@ -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>
|
||||
|
||||
+9
-7
@@ -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>
|
||||
);
|
||||
}
|
||||
+96
@@ -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));
|
||||
}
|
||||
Reference in New Issue
Block a user