diff --git a/package-lock.json b/package-lock.json
index c020ba1..ac5f9fc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -33,6 +33,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",
@@ -52,6 +53,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",
@@ -1476,6 +1478,13 @@
"undici-types": "~5.26.4"
}
},
+ "node_modules/@types/numeral": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/numeral/-/numeral-2.0.5.tgz",
+ "integrity": "sha512-kH8I7OSSwQu9DS9JYdFWbuvhVzvFRoCPCkGxNwoGgaPeDfEPJlcxNvEOypZhQ3XXHsGbfIuYcxcJxKUfJHnRfw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
@@ -4434,6 +4443,15 @@
}
}
},
+ "node_modules/numeral": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz",
+ "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/nuqs": {
"version": "1.17.8",
"resolved": "https://registry.npmjs.org/nuqs/-/nuqs-1.17.8.tgz",
diff --git a/package.json b/package.json
index b2ca41e..3b68755 100644
--- a/package.json
+++ b/package.json
@@ -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",
@@ -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",
diff --git a/src/app/options/analyze/[symbol]/page.tsx b/src/app/options/analyze/[symbol]/page.tsx
index 09d60eb..78d802e 100644
--- a/src/app/options/analyze/[symbol]/page.tsx
+++ b/src/app/options/analyze/[symbol]/page.tsx
@@ -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
-}
-
-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 }>
-
+
}
\ No newline at end of file
diff --git a/src/app/options/analyze/page.tsx b/src/app/options/analyze/page.tsx
index ad07e7e..56b9ceb 100644
--- a/src/app/options/analyze/page.tsx
+++ b/src/app/options/analyze/page.tsx
@@ -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() {
diff --git a/src/components/Expo.tsx b/src/components/Expo.tsx
index 48779ba..690cd3e 100644
--- a/src/components/Expo.tsx
+++ b/src/components/Expo.tsx
@@ -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;
@@ -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
}
]
}
diff --git a/src/components/OptionsAnalysisChartWrapper.tsx b/src/components/OptionsAnalysisChartWrapper.tsx
new file mode 100644
index 0000000..0e227d7
--- /dev/null
+++ b/src/components/OptionsAnalysisChartWrapper.tsx
@@ -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
+}
\ No newline at end of file
diff --git a/src/app/options/analyze/[symbol]/C.tsx b/src/components/OptionsAnalysisComponent.tsx
similarity index 96%
rename from src/app/options/analyze/[symbol]/C.tsx
rename to src/components/OptionsAnalysisComponent.tsx
index e13e776..43bb0fc 100644
--- a/src/app/options/analyze/[symbol]/C.tsx
+++ b/src/components/OptionsAnalysisComponent.tsx
@@ -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";
@@ -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));
diff --git a/src/lib/cboeService.ts b/src/lib/cboeService.ts
new file mode 100644
index 0000000..0439dd1
--- /dev/null
+++ b/src/lib/cboeService.ts
@@ -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 }
+}
\ No newline at end of file
diff --git a/src/lib/formatters.ts b/src/lib/formatters.ts
index 7157cdc..1606125 100644
--- a/src/lib/formatters.ts
+++ b/src/lib/formatters.ts
@@ -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();
}
\ No newline at end of file