Skip to content

Commit

Permalink
minor refactoring in the options analysis page
Browse files Browse the repository at this point in the history
  • Loading branch information
mnsrulz committed Dec 29, 2024
1 parent 0cc95e3 commit 6439ea0
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 30 deletions.
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"match-sorter": "^6.3.4",
"next": "14.2.2",
"next-auth": "^5.0.0-beta.16",
"numeral": "^2.0.6",
"nuqs": "^1.17.8",
"react": "^18",
"react-dom": "^18",
Expand All @@ -57,6 +58,7 @@
"@types/he": "^1.2.3",
"@types/lodash.chunk": "^4.2.9",
"@types/node": "^20",
"@types/numeral": "^2.0.5",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
Expand Down
21 changes: 6 additions & 15 deletions src/app/options/analyze/[symbol]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
import { getDexGexAnalysisView } from "@/lib/tradierService";
import { C } from "./C";
import { DexGexType } from "@/lib/types";
import { Suspense } from "react";
import { LinearProgress } from "@mui/material";
import { OptionsAnalysisChartWrapper } from "@/components/OptionsAnalysisChartWrapper";

const ChartWrapper = async (props: { symbol: string, dte: number, tab: DexGexType, strikeCountValue: number }) => {
const { symbol, dte, tab, strikeCountValue } = props;
const cachedDates: string[] = [];//await getCachedSummaryDatesBySymbol(symbol);
const { currentPrice, mappedOptions } = await getDexGexAnalysisView(symbol);
const dataMode = 'Live'
return <C cachedDates={cachedDates} dte={dte} sc={strikeCountValue} dataMode={dataMode} data={mappedOptions} symbol={symbol} price={currentPrice} tab={tab} />
}

export default async function Page({ params, searchParams }: { params: { symbol: string }, searchParams: { [key: string]: string | number } }) {
export default async function OptionsAnalysisPage({ params, searchParams }: { params: { symbol: string }, searchParams: { [key: string]: string | number } }) {
const search = searchParams;
const { symbol } = params;
const strikeCountValue = (search['sc'] || 30) as number;
const dte = (search['dte'] || 50) as number;
const tab = (search['tab'] || 'DEX') as DexGexType;
const strikeCount = (search['sc'] || 30) as number;
const daysToExpiration = (search['dte'] || 50) as number;
const analysisType = (search['tab'] || 'DEX') as DexGexType;

return <Suspense fallback={<LinearProgress />}>
<ChartWrapper symbol={symbol} dte={dte} strikeCountValue={strikeCountValue} tab={tab} />
<OptionsAnalysisChartWrapper symbol={symbol} daysToExpiration={daysToExpiration} strikeCount={strikeCount} analysisType={analysisType} />
</Suspense>
}
1 change: 0 additions & 1 deletion src/app/options/analyze/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';
import { TickerSearch } from "@/components/TickerSearch";
import { Container } from "@mui/material";
import { useRouter } from 'next/navigation'

export default function Page() {
Expand Down
9 changes: 8 additions & 1 deletion src/components/Expo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ const calculateLeftMargin = (maxStrikeValue: number) => {
return 64
}

const xAxixFormatter = (datasetType: OptionsDatasetType, v: number) => {
if (datasetType == 'gex' && v > 0) {
return `-${humanAbsCurrencyFormatter(v)}`;
}
return humanAbsCurrencyFormatter(v);
}

export const Expo = (props: IExpo) => {
const { data, dte, symbol, skipAnimation } = props;
// const height = (data.strikes.length < 10 ? 100 : 0) + data.strikes.length * 15;
Expand Down Expand Up @@ -114,7 +121,7 @@ export const Expo = (props: IExpo) => {
scaleType: 'linear',
min: -maxPosition * 1.05, //5% extra to allow some spacing
max: maxPosition * 1.05,
valueFormatter: (v) => (isGex && v > 0) ? `-${humanAbsCurrencyFormatter(v)}` : humanAbsCurrencyFormatter(v) //in case of net gex, the values on right should have negative ticks
valueFormatter: (v: number) => xAxixFormatter(props.exposure, v)// (v) => (isGex && v > 0) ? `-${humanAbsCurrencyFormatter(v)}` : humanAbsCurrencyFormatter(v) //in case of net gex, the values on right should have negative ticks
}
]
}
Expand Down
15 changes: 15 additions & 0 deletions src/components/OptionsAnalysisChartWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { getDexGexAnalysisView } from "@/lib/tradierService";
import { DexGexType } from "@/lib/types";
import { getDexGexAnalysisViewCboe } from "@/lib/cboeService";
import { OptionsAnalysisComponent } from "@/components/OptionsAnalysisComponent";

export const OptionsAnalysisChartWrapper = async (props: { symbol: string, daysToExpiration: number, analysisType: DexGexType, strikeCount: number }) => {
const { symbol, daysToExpiration, analysisType, strikeCount } = props;
const cachedDates: string[] = [];//await getCachedSummaryDatesBySymbol(symbol);
const { currentPrice, mappedOptions } = await getDexGexAnalysisView(symbol);
const dataMode = 'Live';

return <OptionsAnalysisComponent cachedDates={cachedDates} dte={daysToExpiration}
sc={strikeCount} dataMode={dataMode} data={mappedOptions} symbol={symbol}
price={currentPrice} tab={analysisType} />
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { calculateHedgingV2, getCalculatedStrikes } from "@/lib/dgHedgingHelper"
import { DexGexType, MiniOptionContract } from "@/lib/types";
import { FormControl, InputLabel, Select, MenuItem, Box, Tab, Tabs, Paper, Container, Typography } from "@mui/material";
import dayjs from "dayjs";
import { useRouter } from "next/navigation";
import { parseAsInteger, parseAsString, parseAsStringEnum, useQueryState } from "nuqs";
import { useMemo } from "react";

Expand All @@ -23,8 +22,7 @@ const stikeOptions = [20,
100, 150,
200]


export const C = (props: { symbol: string, cachedDates: string[], dte: number, sc: number, dataMode: string, data: MiniOptionContract[], price: number, tab: DexGexType }) => {
export const OptionsAnalysisComponent = (props: { symbol: string, cachedDates: string[], dte: number, sc: number, dataMode: string, data: MiniOptionContract[], price: number, tab: DexGexType }) => {
const { cachedDates, data, symbol, price, sc } = props;
const [dte, setDte] = useQueryState('dte', parseAsInteger.withDefault(props.dte));
const [strikeCounts, setStrikesCount] = useQueryState('sc', parseAsInteger.withDefault(props.sc));
Expand Down
46 changes: 46 additions & 0 deletions src/lib/cboeService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import ky from "ky";

const client = ky.create({
headers: {
'Accept': 'application/json'
},
cache: 'no-cache'
});

//JUST FOR REF.. PLANNING TO USE THE MZTRADING-DATA SERVICE FOR THIS
export const getDexGexAnalysisViewCboe = async (symbol: string) => {
const optionChain = await client(`https://cdn.cboe.com/api/global/delayed_quotes/options/${symbol}.json`).json<{
data: {
options: {
option: string,
open_interest: number,
delta: number,
volume: number,
gamma: number,
}[],
close: number
}
}>();
const currentPrice = optionChain.data.close; //TODO: is this the close price which remains same if the market is open??

console.time(`getDexGexAnalysisViewCboe-mapping-${symbol}`)
const mappedOptions = optionChain.data.options.map(({ option, open_interest,volume, delta, gamma }) => {
//implement mem cache for regex match??
const rxMatch = /(\w+)(\d{6})([CP])(\d+)/.exec(option);
if(!rxMatch) throw new Error('error parsing option')

return {
strike: Number(`${rxMatch[4]}`)/1000,
expiration_date: `20${rxMatch[2].substring(0,2)}-${rxMatch[2].substring(2,4)}-${rxMatch[2].substring(4,6)}`,
open_interest,
option_type: (rxMatch[3] == 'C' ? 'call': 'put') as 'call' | 'put',
volume,
greeks: {
delta: delta || 0,
gamma: gamma || 0,
}
}
});
console.timeEnd(`getDexGexAnalysisViewCboe-mapping-${symbol}`)
return { mappedOptions, currentPrice }
}
14 changes: 4 additions & 10 deletions src/lib/formatters.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import numeral from 'numeral';

export const percentageFormatter = (v: number) => v && Number(v).toLocaleString(undefined, { style: 'percent', minimumFractionDigits: 2 }) || '';
export const currencyFormatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format;
export const numberFormatter = new Intl.NumberFormat('en-US', { style: 'decimal', maximumFractionDigits: 2 }).format;
export const positiveNegativeNumberFormatter = new Intl.NumberFormat('en-US', { style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2, signDisplay: 'always' }).format;
export const fixedCurrencyFormatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format;

export const humanAbsCurrencyFormatter = (tick: number) => {
tick = Math.abs(tick);
if (tick >= 1000000000) {
return `${(tick / 1000000000).toFixed(1)}B`; // Billions
} else if (tick >= 1000000) {
return `${(tick / 1000000).toFixed(1)}M`; // Millions
} else if (tick >= 1000) {
return `${(tick / 1000).toFixed(1)}K`; // Thousands
}
return `${tick}`;
export const humanAbsCurrencyFormatter = (tick: number) => {
return numeral(Math.abs(tick)).format('0.[0]a').toUpperCase();
}

0 comments on commit 6439ea0

Please sign in to comment.