Compare commits

...

13 Commits

@ -0,0 +1,9 @@
Choose Underlying
Choose Quote Date (i.e. from which point in time are you running the analysis)
Choose Strike
For each front-month-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, for all possible underlying prices.
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.

@ -1,19 +1,24 @@
import * as esbuild from 'esbuild'
import CssModulesPlugin from 'esbuild-css-modules-plugin';
const config = {
entryPoints: ['src/index.tsx'],
const config = {
entryPoints: ['src/index.tsx', 'src/App.module.css'],
bundle: true,
outdir: 'dist',
platform: 'browser',
format: 'esm',
external: ['fsevents'],
plugins: [
CssModulesPlugin(),
],
};
if(process.argv[2] === 'serve'){
const context = await esbuild.context(config);
const {host, port} = await context.serve({
servedir: 'dist'
servedir: 'dist',
host: '127.0.0.1'
});
console.log(`Listening on http://${host}:${port}/`);

@ -0,0 +1,21 @@
/* esbuild-css-modules-plugin-ns-css:src/App.module.css */
.App-module__app_gPMrEW__001 {
flex-direction: column;
display: flex;
}
.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;
}

10
dist/App.module.js vendored

@ -0,0 +1,10 @@
// src/App.module.css
var App_module_default = {
"app": "App-module__app_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
};

21
dist/index.css vendored

@ -0,0 +1,21 @@
/* esbuild-css-modules-plugin-ns-css:src/App.module.css */
.App-module__app_gPMrEW__001 {
flex-direction: column;
display: flex;
}
.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
dist/index.html vendored

@ -2,6 +2,7 @@
<html>
<head>
<title>Calendar Optimizer</title>
<link rel="stylesheet" href="./App.module.css"></link>
</head>
<body>
<div id="app"></div>

2978
dist/index.js vendored

File diff suppressed because it is too large Load Diff

@ -12,7 +12,8 @@
"devDependencies": {
"@types/react": "^18.2.12",
"@types/react-dom": "^18.2.5",
"esbuild": "^0.17.18"
"esbuild": "^0.17.18",
"esbuild-css-modules-plugin": "^3.0.0-dev.16"
},
"dependencies": {
"chart.js": "^4.3.0",

@ -0,0 +1,20 @@
.app {
display:flex;
flex-direction: column;
}
.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;
}

@ -1,34 +1,51 @@
//import Chart from 'chart.js/auto';
import { Chart as ChartJS, Tooltip, Legend, LineElement, CategoryScale, LinearScale, PointElement } from "chart.js";
import { Line } from 'react-chartjs-2';
import Header from './Header';
import { QuoteDatePicker } from "./QuoteDatePicker";
import { HistoricalImpliedVolatilityChart } from "./HistoricalImpliedVolatilityChart";
import { Picker } from './Picker';
import { atom as $, useAtomValue } from 'jotai';
//import './index.css';
//@ts-ignore
import k from './App.module.css';
import { CalendarPricesChart } from './CalendarPricesChart';
import { UnderlyingPrice } from './UnderlyingPrice';
export const baseUrl = 'http://127.0.0.1:8234';
/* The following are wrapped in atoms to prevent re-creating them if App() re-runs. */
export const $underlyingsUrl = $(`${baseUrl}/option_quotes/underlyings`);
export const $selectedUnderlying = $('');
export const $quoteDatePickerUrl = $((get) => `${baseUrl}/option_quotes/${get($selectedUnderlying)}/quote_dates`);
export const $isQuoteDatePickerEnabled = $((get) => get($selectedUnderlying)!=='');
export const $selectedQuoteDate = $('');
export const $strikePickerUrl = $((get) => `${baseUrl}/option_quotes/${get($selectedUnderlying)}/${get($selectedQuoteDate)}/strikes`);
export const $isStrikePickerEnabled = $((get) => get($selectedQuoteDate)!=='' && get($selectedUnderlying)!=='');
export const $selectedStrike = $('');
export const $frontMonthExpirationPickerUrl = $((get) => `${baseUrl}/option_quotes/${get($selectedUnderlying)}/${get($selectedQuoteDate)}/expirations`);
export const $isFrontMonthExpirationPickerEnabled = $((get) => get($selectedQuoteDate)!=='' && get($selectedUnderlying)!=='');
export const $selectedFrontExpiration = $('');
export const $backMonthExpirationPickerUrl = $((get) => `${baseUrl}/option_quotes/${get($selectedUnderlying)}/${get($selectedQuoteDate)}/expirations`);
export const $isBackMonthExpirationPickerEnabled = $((get) => get($selectedQuoteDate)!=='' && get($selectedUnderlying)!=='');
export const $selectedBackExpiration = $('');
ChartJS.register(LineElement, Tooltip, Legend, CategoryScale, LinearScale, PointElement);
function App() {
const selectedUnderlying = useAtomValue($selectedUnderlying);
return (
<div>
<div className={k.app}>
<Header />
<QuoteDatePicker />
<Line
datasetIdKey='id'
data={{
labels: ['Jun', 'Jul', 'Aug'],
datasets: [
{//@ts-ignore
id: 1,
label: '',
data: [5, 6, 7],
},
{//@ts-ignore
id: 2,
label: '',
data: [3, 2, 1],
},
],
}}
/>
<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>
);
}

@ -0,0 +1,52 @@
import { Chart as ChartJS, Tooltip, Legend, LineElement, CategoryScale, LinearScale, PointElement } from "chart.js";
import { Line } from 'react-chartjs-2';
import { atom as $, useAtomValue } from 'jotai';
import { loadable } from 'jotai/utils';
import { useEffect } from "react";
import { baseUrl, $selectedUnderlying, $selectedStrike, $selectedFrontExpiration, $selectedBackExpiration } from "./App";
ChartJS.register(LineElement, Tooltip, Legend, CategoryScale, LinearScale, PointElement);
export type CalendarPrice = {
quote_date: string,
price: number
};
const $prices = loadable<Promise<Array<CalendarPrice>>>($(async (get)=>{
const selectedUnderlying = get($selectedUnderlying);
const selectedStrike = get($selectedStrike);
const selectedFrontExpiration = get($selectedFrontExpiration);
const selectedBackExpiration = get($selectedBackExpiration);
if(selectedUnderlying!=='' && selectedStrike!=='' && selectedFrontExpiration!=='' && selectedBackExpiration!==''){
return await fetch(`${baseUrl}/option_quotes/${selectedUnderlying}/${selectedStrike}/${selectedFrontExpiration}/${selectedBackExpiration}`).then(x=>x.json()).catch(()=>[]);
}
else{
return [];
}
}));
export function CalendarPricesChart(){
const prices = useAtomValue($prices);
useEffect(()=>{ console.log(prices); },[prices])
if(prices.state === 'hasData'){
return (<div>
<Line
datasetIdKey='id'
data={{
labels: prices.data.map((x)=>x.quote_date.substring(0,10)),
datasets: [
{//@ts-ignore
id: 1,
label: '',
data: prices.data.map((x)=>x.price),
},
],
}}
/>
</div>);
}
else {
return (<></>);
}
}

@ -0,0 +1,27 @@
import { Chart as ChartJS, Tooltip, Legend, LineElement, CategoryScale, LinearScale, PointElement } from "chart.js";
import { Line } from 'react-chartjs-2';
ChartJS.register(LineElement, Tooltip, Legend, CategoryScale, LinearScale, PointElement);
export function HistoricalImpliedVolatilityChart(){
return (<div>
<Line
datasetIdKey='id'
data={{
labels: ['Jun', 'Jul', 'Aug'],
datasets: [
{//@ts-ignore
id: 1,
label: '',
data: [5, 6, 7],
},
{//@ts-ignore
id: 2,
label: '',
data: [3, 2, 1],
},
],
}}
/>
</div>);
}

@ -0,0 +1,55 @@
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>,
$selectedOptionValue?:PrimitiveAtom<string>,
$url:Atom<string>,
$isEnabled?:Atom<boolean>
};
export function Picker({
$url,
$options = useMemo(()=>$([]), []),
$isLoading = useMemo(()=>$(true),[]),
$isEnabled = useMemo(()=>$(true),[]),
$selectedOptionValue = useMemo(()=>$(''), [])
}: PickerInput){
const url = useAtomValue($url);
const options = useAtomValue($options);
const isLoading = useAtomValue($isLoading);
const [selectedOptionValue, setSelectedOptionValue] = useAtom($selectedOptionValue);
const isEnabled = useAtomValue($isEnabled);
const handleFetchedOptions = useCommand((get,set,options)=>{
set($options, options);
set($isLoading, false);
}, [$options, $isLoading]);
useEffect(()=>{
if(isEnabled){
fetch(url)
.then(x=>x.json())
.catch((err)=>['AAPL', 'MSFT', 'GOOG'])
.then(handleFetchedOptions)
}
},[url, isEnabled])
return (
<div>
{isLoading
?
<span>Loading...</span>
:
<select value={selectedOptionValue} onChange={(e)=>{ setSelectedOptionValue(e.target.value); }}>
<option key="" value=""></option>
{options.map((date)=>
<option key={date} value={date}>{date}</option>
)}
</select>
}
</div>
);
}

@ -1,32 +0,0 @@
import { useEffect, useState } from "react";
import { atom as $, useAtom } from 'jotai';
const $dates = $<Array<string>>([]);
const $isLoading = $<boolean>(false);
export function QuoteDatePicker(){
const [dates, setDates] = useAtom($dates);
const [isLoading, setIsLoading] = useAtom($isLoading);
useEffect(()=>{
fetch('http://127.0.0.1:8234/option_quotes/AAPL/quote_dates')
.then(x=>x.json())
.catch((err)=>['2021-01-02', '2021-01-03', '2021-01-04'])
.then((dates_)=>{ setDates(dates_); setIsLoading(false); })
},[])
return (
<div>
{isLoading
?
<span>Loading...</span>
:
<select>
{dates.map((date)=>
<option key={date} value={date}>{date}</option>
)}
</select>
}
</div>
);
}

@ -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>
);
}

@ -2,6 +2,7 @@
<html>
<head>
<title>Calendar Optimizer</title>
<link rel="stylesheet" href="./App.module.css"></link>
</head>
<body>
<div id="app"></div>

@ -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=0.03, optionType='call', 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));
}

@ -1,5 +1,6 @@
{
"compilerOptions": {
"jsx": "react-jsx"
"jsx": "react-jsx",
"lib": ["DOM", "DOM.Iterable", "ES2017"]
},
}

@ -155,6 +155,20 @@ csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
esbuild-css-modules-plugin@^3.0.0-dev.16:
version "3.0.0-dev.16"
resolved "https://registry.yarnpkg.com/esbuild-css-modules-plugin/-/esbuild-css-modules-plugin-3.0.0-dev.16.tgz#2e88d094f6ecbb9ed1119ce8356ad4c8f31e09b2"
integrity sha512-ejaqU5wO+Dz1ILzbxQVq5ZKws/6pytuM9BRJaaPCh+4wX/xMP8ChVmdXDUcS4gUnF5Y3hTVvG06D5jeWkz83fg==
dependencies:
lightningcss "^1.20.0"
lodash "^4.17.21"
lodash-es "^4.17.21"
esbuild@^0.17.18:
version "0.17.18"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.18.tgz#f4f8eb6d77384d68cd71c53eb6601c7efe05e746"
@ -193,6 +207,72 @@ jotai@^2.2.0:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
lightningcss-darwin-arm64@1.21.0:
version "1.21.0"
resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.21.0.tgz#8d74d3fd5e6fdff4697e1d72a31ee6e30c244c35"
integrity sha512-WcJmVmbNUnCbUqqXV46ZsriFtWJujcPkn+w2cu4R+EgpXuibyTP/gzahmX0gc4RYQxTz2zXIeGx4cF2gr8fLwA==
lightningcss-darwin-x64@1.21.0:
version "1.21.0"
resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.21.0.tgz#7d7ceec31af2fac955e1409fa571dd1d5170bba3"
integrity sha512-xHwMHfcTIHX6fY4YQimI1V/KcbozoNVeKMncZzrp/3NAj0sp3ktxobCj1e0sGqVJMUMaHu/SWvt0mS8jAIhkYw==
lightningcss-linux-arm-gnueabihf@1.21.0:
version "1.21.0"
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.21.0.tgz#bb74da249368006d822cd1a9831c21c38fe2e498"
integrity sha512-rk1cr+C2IA1QHvh0QJAPXsQ2vrwCksms7fgfaw43RIERBWa6EEM5p0/1CWhdZ5zrl9veUdY6NRaNGRJjJL0iLw==
lightningcss-linux-arm64-gnu@1.21.0:
version "1.21.0"
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.21.0.tgz#49ce48a034686d864e358e16c8d10af8456ef7c8"
integrity sha512-JkOG8K2Y4m5MeP3DlaHOgGDDtHbhbJcN8JcizFN0snUIIru1qxYNWPhAQsEwysuTRY9aANP0nScZJkALpcYmgA==
lightningcss-linux-arm64-musl@1.21.0:
version "1.21.0"
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.21.0.tgz#d5963868d6c20f2ea697f3ee19a34f38737d4ff5"
integrity sha512-4Zx51DbR41neTFMs28CI9cZpX/mF5Urc6pChTio5nZhrz6FC1pRGiwxNJ+G15a/YPvRmPmvQd3Mz1N4WEgbj2A==
lightningcss-linux-x64-gnu@1.21.0:
version "1.21.0"
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.21.0.tgz#535079a9dd0f6b2efde496134192608c82ad2068"
integrity sha512-PN33pPK/O3b4qMfWcJ2eis7NLqEkyW2NEh9X4rWfJrBtOnSbgafuYUuEtO5Ylu+dL3oUKc5usB07FGeil3RzeA==
lightningcss-linux-x64-musl@1.21.0:
version "1.21.0"
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.21.0.tgz#d1f7f43e9744959a2ba2996488989d7b9cb06f85"
integrity sha512-S51OT7TRfS5x8aN/8frv/JSXCGm+11VuhM4WCiTqDPjhHUDWd8nwiN/7s5juiwrlrpOxb5UKq21EKDrISoGQpw==
lightningcss-win32-x64-msvc@1.21.0:
version "1.21.0"
resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.21.0.tgz#5e87f2409b7cd2b8a52703058c6ec06e69f3a4b0"
integrity sha512-yW6/ZDJAHrSWtRltH1tr2I+2sn374gK2yclc44HMfpxfjIYgXMUkzqstalloMUQpZFR6M0ltXo5/tuLWoBydGQ==
lightningcss@^1.20.0:
version "1.21.0"
resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.21.0.tgz#31ebf4717f42e801e622186f28cd58db7c914ef7"
integrity sha512-HDznZexdDMvC98c79vRE+oW5vFncTlLjJopzK4azReOilq6n4XIscCMhvgiXkstYMM/dCe6FJw0oed06ck8AtA==
dependencies:
detect-libc "^1.0.3"
optionalDependencies:
lightningcss-darwin-arm64 "1.21.0"
lightningcss-darwin-x64 "1.21.0"
lightningcss-linux-arm-gnueabihf "1.21.0"
lightningcss-linux-arm64-gnu "1.21.0"
lightningcss-linux-arm64-musl "1.21.0"
lightningcss-linux-x64-gnu "1.21.0"
lightningcss-linux-x64-musl "1.21.0"
lightningcss-win32-x64-msvc "1.21.0"
lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"

Loading…
Cancel
Save