From fcdb5fc7e2b2ee97029fb2f7eda6ab8d579fa52e Mon Sep 17 00:00:00 2001 From: mnsrulz Date: Sat, 20 Apr 2024 21:19:11 +0000 Subject: [PATCH 1/4] corrected the stock options view --- src/components/TickerName.tsx | 3 ++- src/components/stock-options-view.tsx | 33 +++++++++++++-------------- src/lib/socket.ts | 4 +++- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/components/TickerName.tsx b/src/components/TickerName.tsx index 39adea4..60cdfa0 100644 --- a/src/components/TickerName.tsx +++ b/src/components/TickerName.tsx @@ -5,7 +5,8 @@ import dayjs from "dayjs"; export const TickerName = (p: { trade: ITradeView; }) => { const { trade } = p; - const dt = trade.contractExpiry; + debugger; + const dt = `${trade.contractExpiry}`.substring(0, 10); //keeping only the date part const isYearAfter = dayjs(dt).diff(dayjs(), 'days') > 365; const fmtDate = isYearAfter ? dayjs(dt).format('M/D/YY') : dayjs(dt).format('M/D'); return
diff --git a/src/components/stock-options-view.tsx b/src/components/stock-options-view.tsx index a8f99fd..df56e33 100644 --- a/src/components/stock-options-view.tsx +++ b/src/components/stock-options-view.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { NumberRange, useOptionTracker } from '../lib/socket'; import { GridColDef, DataGrid, gridClasses } from '@mui/x-data-grid'; -import { Box, FormControl, Grid, InputLabel, MenuItem, Paper, Select, Slider, Stack, Tab, Tabs, LinearProgress } from '@mui/material'; +import { Box, FormControl, Grid, InputLabel, MenuItem, Paper, Select, Slider, Stack, Tab, Tabs, LinearProgress, TextField } from '@mui/material'; import { useState } from 'react'; import dayjs from 'dayjs'; import { percentageFormatter } from '@/lib/formatters'; @@ -53,15 +53,15 @@ type PriceModeType = 'LAST_PRICE' | 'BID_PRICE' | 'ASK_PRICE' | 'AVG_PRICE' type ValueModeType = 'PRICE' | 'ANNUAL_RETURN' | 'TOTAL_RETURN' const numberFormatter = (v: string) => v && Number(v); -const todaysDate = dayjs(); +const todaysDate = dayjs().format('YYYY-MM-DD'); export const StockOptionsView = (props: ITickerProps) => { - const { data, isLoading, strikePriceRange, setStrikePriceRange } = useOptionTracker(props.symbol); + const { data, isLoading, strikePriceRange, setStrikePriceRange, targetPrice, setTargetPrice } = useOptionTracker(props.symbol); const [putCallTabValue, setPutCallTabValue] = useState<'PUT' | 'CALL'>('PUT'); const [priceMode, setPriceMode] = useState('AVG_PRICE'); const [valueMode, setValueMode] = useState('PRICE'); - if (isLoading) return ; + if (isLoading) return ; const allDates = data && Array.from(Object.keys(data.options)); const allStrikePrices = allDates && Array.from(new Set(allDates.flatMap(d => Object.keys(data.options[d].c))))//.map(Number).sort(function (a, b) { return a - b; }); if (!allDates || !allStrikePrices) return
no option data found!!!
; @@ -85,6 +85,7 @@ export const StockOptionsView = (props: ITickerProps) => { }) const traderows = allDates?.map(d => { + if (dayjs(d).isBefore(todaysDate)) return null; const o: any = { id: d } @@ -98,7 +99,7 @@ export const StockOptionsView = (props: ITickerProps) => { case 'ASK_PRICE': return po?.a; case 'AVG_PRICE': - return (po?.a + po?.b) ? (po?.a + po?.b)/2 : null; + return (po?.a + po?.b) ? (po?.a + po?.b) / 2 : null; default: return po?.b; } @@ -107,24 +108,19 @@ export const StockOptionsView = (props: ITickerProps) => { o[s.strikePrice] = price && (() => { switch (valueMode) { case 'TOTAL_RETURN': - /* - 135 - 138 --> 1.10 - - */ if (putCallTabValue == 'PUT') { - return (data.currentPrice > s.value ? price : (price - (s.value - data.currentPrice))) / s.value; + return (targetPrice > s.value ? price : (price - (s.value - targetPrice))) / (s.value); } else { - return (price / data.currentPrice); + return (price / targetPrice); } case 'ANNUAL_RETURN': if (putCallTabValue == 'PUT') { - const sellCost = (data.currentPrice > s.value ? price : (price - (s.value - data.currentPrice))); + const sellCost = (targetPrice > s.value ? price : (price - (s.value - targetPrice))); const risk = s.value; return (sellCost / risk) * (365 / numberofdays); } else { - const sellCost = (data.currentPrice < s.value ? price : (price - (data.currentPrice - s.value))); - return (sellCost / data.currentPrice) * (365 / numberofdays); + const sellCost = (targetPrice < s.value ? price : (price - (targetPrice - s.value))); + return (sellCost / targetPrice) * (365 / numberofdays); } default: return price?.toFixed(2); @@ -132,7 +128,7 @@ export const StockOptionsView = (props: ITickerProps) => { })(); }); return o; - }); + }).filter(r => r); return Symbol: {props.symbol} - {data.currentPrice} @@ -156,12 +152,15 @@ export const StockOptionsView = (props: ITickerProps) => { Value Mode - setValueMode(e.target.value as ValueModeType)}> PRICE ANNUAL_RETURN TOTAL_RETURN + + setTargetPrice(Number(v.target.value))} type='number' /> + setPutCallTabValue(v)} variant="fullWidth" indicatorColor="secondary" textColor="secondary"> diff --git a/src/lib/socket.ts b/src/lib/socket.ts index 28e4c6f..4fd705a 100644 --- a/src/lib/socket.ts +++ b/src/lib/socket.ts @@ -113,6 +113,7 @@ export type NumberRange = { start: number, end: number } export const useOptionTracker = (symbol: string) => { const [data, setOd] = useState(); const [isLoading, setIsLoading] = useState(true); + const [targetPrice, setTargetPrice] = useState(0); const [strikePriceRange, setStrikePriceRange] = useState({ start: 0, end: Number.MAX_VALUE }); useEffect(() => { @@ -126,6 +127,7 @@ export const useOptionTracker = (symbol: string) => { start: Math.round(currentPrice - thresholdValue), end: Math.round(currentPrice + thresholdValue) }); + setTargetPrice(r.currentPrice); }).finally(() => setIsLoading(false)); // socket.emit('options-subscribe-request', item); // socket.on(`options-subscribe-response`, setOd); @@ -134,7 +136,7 @@ export const useOptionTracker = (symbol: string) => { // socket.off('options-subscribe-response', setOd); // } }, []); - return { data, isLoading, strikePriceRange, setStrikePriceRange }; + return { data, isLoading, strikePriceRange, setStrikePriceRange, targetPrice, setTargetPrice }; } From b3d924a63610ac972a29dc54f1e0851957f2efd5 Mon Sep 17 00:00:00 2001 From: mnsrulz Date: Sat, 20 Apr 2024 21:26:03 +0000 Subject: [PATCH 2/4] removed comment --- src/components/TickerName.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/TickerName.tsx b/src/components/TickerName.tsx index 60cdfa0..9d9614e 100644 --- a/src/components/TickerName.tsx +++ b/src/components/TickerName.tsx @@ -1,11 +1,9 @@ 'use client'; import { ITradeView } from "@/lib/types"; -import { shortDateFormatter } from "./trades"; import dayjs from "dayjs"; export const TickerName = (p: { trade: ITradeView; }) => { const { trade } = p; - debugger; const dt = `${trade.contractExpiry}`.substring(0, 10); //keeping only the date part const isYearAfter = dayjs(dt).diff(dayjs(), 'days') > 365; const fmtDate = isYearAfter ? dayjs(dt).format('M/D/YY') : dayjs(dt).format('M/D'); From d2ded1e691cdbb5b08b0e9427a86845cfd37c5ff Mon Sep 17 00:00:00 2001 From: mnsrulz Date: Thu, 25 Apr 2024 04:11:11 +0000 Subject: [PATCH 3/4] added pcr feature --- package-lock.json | 230 ++++++++++++++++++++++++++ package.json | 1 + src/components/StrikePriceSlider.tsx | 35 ++++ src/components/pcr.tsx | 89 ++++++++++ src/components/stock-options-view.tsx | 75 ++++----- src/lib/socket.ts | 20 +-- src/lib/types.ts | 24 +++ 7 files changed, 415 insertions(+), 59 deletions(-) create mode 100644 src/components/StrikePriceSlider.tsx create mode 100644 src/components/pcr.tsx diff --git a/package-lock.json b/package-lock.json index 74d1f9b..186a9a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.11.5", "@mui/icons-material": "^5.15.15", "@mui/material": "^5.15.14", + "@mui/x-charts": "^7.3.0", "@mui/x-data-grid": "7.0", "@mui/x-date-pickers": "^6.19.9", "@prisma/client": "^5.12.1", @@ -778,6 +779,44 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/@mui/x-charts": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.3.0.tgz", + "integrity": "sha512-TicUELcLGFCUzF2YKHxZ/U4z93NkVkN8PMSsBs0rwQKRYbmO/C1vrmAWT0QUHk3hloCw4fcxbu0BCkz4Jd62TQ==", + "dependencies": { + "@babel/runtime": "^7.24.0", + "@mui/base": "^5.0.0-beta.40", + "@mui/system": "^5.15.14", + "@mui/utils": "^5.15.14", + "@react-spring/rafz": "^9.7.3", + "@react-spring/web": "^9.7.3", + "clsx": "^2.1.0", + "d3-color": "^3.1.0", + "d3-delaunay": "^6.0.4", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, "node_modules/@mui/x-data-grid": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.0.0.tgz", @@ -1141,6 +1180,71 @@ "@prisma/debug": "5.12.1" } }, + "node_modules/@react-spring/animated": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz", + "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==", + "dependencies": { + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz", + "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/rafz": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.3.tgz", + "integrity": "sha512-9vzW1zJPcC4nS3aCV+GgcsK/WLaB520Iyvm55ARHfM5AuyBqycjvh1wbmWmgCyJuX4VPoWigzemq1CaaeRSHhQ==" + }, + "node_modules/@react-spring/shared": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz", + "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==", + "dependencies": { + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/types": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz", + "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==" + }, + "node_modules/@react-spring/web": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz", + "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/core": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@remix-run/router": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", @@ -1880,6 +1984,111 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -1998,6 +2207,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3202,6 +3419,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -4663,6 +4888,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index 3e6e7f0..4ee8617 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@emotion/styled": "^11.11.5", "@mui/icons-material": "^5.15.15", "@mui/material": "^5.15.14", + "@mui/x-charts": "^7.3.0", "@mui/x-data-grid": "7.0", "@mui/x-date-pickers": "^6.19.9", "@prisma/client": "^5.12.1", diff --git a/src/components/StrikePriceSlider.tsx b/src/components/StrikePriceSlider.tsx new file mode 100644 index 0000000..0ff0ba2 --- /dev/null +++ b/src/components/StrikePriceSlider.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { Grid, Slider, Stack } from '@mui/material'; +import { IStrikePriceSliderPorps } from './stock-options-view'; + +export const StrikePriceSlider = (props: IStrikePriceSliderPorps) => { + const { allStrikePricesValues, onChange, strikePriceRange } = props; + const t = [strikePriceRange.start, strikePriceRange.end]; + const [minStrikePrice, maxStrikePrice] = [Math.min.apply(null, allStrikePricesValues), Math.max.apply(null, allStrikePricesValues)]; + // const [minStrikePrice, maxStrikePrice] = [currentPrice - thresholdValue, currentPrice + thresholdValue]; + const strikePriceMarks = allStrikePricesValues.map(m => ({ value: m })); + const handleChange = (e: Event, v: number | number[]) => { + const value = v as number[]; + onChange({ + start: value[0], + end: value[1] + }); + }; + + return
+
Strike Price Range: {t[0]} - {t[1]}
+ + + + 'Strike price'} + value={t} + onChange={handleChange} + valueLabelDisplay="auto" + min={minStrikePrice} + max={maxStrikePrice} + marks={strikePriceMarks} + step={null} /> + +
; +}; diff --git a/src/components/pcr.tsx b/src/components/pcr.tsx new file mode 100644 index 0000000..6d60828 --- /dev/null +++ b/src/components/pcr.tsx @@ -0,0 +1,89 @@ +import { IOptionsGrid, NumberRange, OptionsInnerData } from "@/lib/types"; +import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from "@mui/material"; +import { BarChart } from '@mui/x-charts/BarChart'; +import { StrikePriceSlider } from "./StrikePriceSlider"; +import { axisClasses } from '@mui/x-charts'; +import { useState } from "react"; + +interface ITickerProps { + open: boolean, + data: OptionsInnerData, + onClose: () => void, + currentPrice: number +} + +const valueFormatter = (value: number | null) => `${value}`; +const chartSetting = { + yAxis: [ + { + label: 'Open interest', + }, + ], + // width: 500, + colors: ['red', 'green'], + height: 500, + sx: { + [`.${axisClasses.left} .${axisClasses.label}`]: { + //transform: 'translate(-20px, 0)', + }, + }, +}; + +export const Pcr = (props: ITickerProps) => { + const { open, onClose, data, currentPrice } = props; + const allStrikePrices = Object.keys(data.c); + const allStrikePricesValues = allStrikePrices?.map(Number) + const thresholdValue = currentPrice * 0.1; + const [strikePriceRange, setStrikePriceRange] = useState({ + start: Math.round(currentPrice - thresholdValue), + end: Math.round(currentPrice + thresholdValue) + }); + + const workingStrikePrices = allStrikePrices.map(s => ({ + strikePrice: s, + value: Number(s) + })).filter(n => n.value >= strikePriceRange.start && n.value <= strikePriceRange.end); + + const dataset = workingStrikePrices.map(d => { + return { + strike: d.value, + p: data.p[d.strikePrice].oi, + c: data.c[d.strikePrice].oi + } + }) + + return ( + + Put Call Ratio - {currentPrice} + + + + + + + + + ); +}; \ No newline at end of file diff --git a/src/components/stock-options-view.tsx b/src/components/stock-options-view.tsx index df56e33..df1f4e0 100644 --- a/src/components/stock-options-view.tsx +++ b/src/components/stock-options-view.tsx @@ -1,56 +1,25 @@ import * as React from 'react'; -import { NumberRange, useOptionTracker } from '../lib/socket'; +import { useOptionTracker } from '../lib/socket'; import { GridColDef, DataGrid, gridClasses } from '@mui/x-data-grid'; -import { Box, FormControl, Grid, InputLabel, MenuItem, Paper, Select, Slider, Stack, Tab, Tabs, LinearProgress, TextField } from '@mui/material'; +import { Box, FormControl, InputLabel, MenuItem, Paper, Select, Tab, Tabs, LinearProgress, TextField, Button, Link } from '@mui/material'; import { useState } from 'react'; import dayjs from 'dayjs'; import { percentageFormatter } from '@/lib/formatters'; import { ConditionalFormattingBox } from './conditional-formatting'; +import { Pcr } from './pcr'; +import { IOptionsGrid, NumberRange, OptionsInnerData } from '@/lib/types'; +import { StrikePriceSlider } from './StrikePriceSlider'; interface ITickerProps { symbol: string } -type IStrikePriceSliderPorps = { allStrikePricesValues: number[], onChange: (v: NumberRange) => void, currentPrice: number, strikePriceRange: NumberRange } +export type IStrikePriceSliderPorps = { allStrikePricesValues: number[], onChange: (v: NumberRange) => void, currentPrice: number, strikePriceRange: NumberRange } -const StrikePriceSlider = (props: IStrikePriceSliderPorps) => { - const { allStrikePricesValues, onChange, strikePriceRange } = props; - const t = [strikePriceRange.start, strikePriceRange.end]; - const [minStrikePrice, maxStrikePrice] = [Math.min.apply(null, allStrikePricesValues), Math.max.apply(null, allStrikePricesValues)]; - // const [minStrikePrice, maxStrikePrice] = [currentPrice - thresholdValue, currentPrice + thresholdValue]; - const strikePriceMarks = allStrikePricesValues.map(m => ({ value: m })); - const handleChange = (e: Event, v: number | number[]) => { - const value = v as number[]; - onChange({ - start: value[0], - end: value[1] - }); - }; - - return
-
Strike Price Range: {t[0]} - {t[1]}
- - - - 'Strike price'} - value={t} - onChange={handleChange} - valueLabelDisplay="auto" - min={minStrikePrice} - max={maxStrikePrice} - marks={strikePriceMarks} - step={null} - // scale={calculateValue} - //getAriaValueText={valuetext} - /> - -
-} type PriceModeType = 'LAST_PRICE' | 'BID_PRICE' | 'ASK_PRICE' | 'AVG_PRICE' -type ValueModeType = 'PRICE' | 'ANNUAL_RETURN' | 'TOTAL_RETURN' +type ValueModeType = 'PRICE' | 'ANNUAL_RETURN' | 'TOTAL_RETURN' | 'PCR' const numberFormatter = (v: string) => v && Number(v); const todaysDate = dayjs().format('YYYY-MM-DD'); @@ -60,6 +29,16 @@ export const StockOptionsView = (props: ITickerProps) => { const [putCallTabValue, setPutCallTabValue] = useState<'PUT' | 'CALL'>('PUT'); const [priceMode, setPriceMode] = useState('AVG_PRICE'); const [valueMode, setValueMode] = useState('PRICE'); + const [pcrSelectedData, setPcrSelectedData] = useState(); + const [pcrOpen, setPcrOpen] = useState(false); + + function handlePcrSelection(v: string) { + const fss = data?.options[v]; + if (fss) { + setPcrSelectedData(fss); + setPcrOpen(true); + } + } if (isLoading) return ; const allDates = data && Array.from(Object.keys(data.options)); @@ -67,7 +46,7 @@ export const StockOptionsView = (props: ITickerProps) => { if (!allDates || !allStrikePrices) return
no option data found!!!
; const allStrikePricesValues = allStrikePrices?.map(Number) const columns: GridColDef[] = [ - { field: 'id', width: 120, headerName: 'expiry' }, + { field: 'id', width: 120, headerName: 'expiry', renderCell: (v) => handlePcrSelection(v.value)}>{v.value} }, ]; const workingStrikePrices = allStrikePrices.map(s => ({ @@ -79,14 +58,14 @@ export const StockOptionsView = (props: ITickerProps) => { columns.push({ field: s.strikePrice, width: 10, headerName: `${parseFloat(s.strikePrice)}`, - valueFormatter: valueMode == 'PRICE' ? numberFormatter : percentageFormatter, type: 'number', - renderCell: valueMode == 'PRICE' ? undefined : (p) => + valueFormatter: ['PRICE', 'PCR'].includes(valueMode) ? numberFormatter : percentageFormatter, type: 'number', + renderCell: ['PRICE', 'PCR'].includes(valueMode) ? undefined : (p) => }) }) const traderows = allDates?.map(d => { if (dayjs(d).isBefore(todaysDate)) return null; - const o: any = { + const o: IOptionsGrid = { id: d } const numberofdays = dayjs(d).diff(todaysDate, 'days') + 1; @@ -105,7 +84,7 @@ export const StockOptionsView = (props: ITickerProps) => { } })(); - o[s.strikePrice] = price && (() => { + (o as any)[s.strikePrice] = price && (() => { switch (valueMode) { case 'TOTAL_RETURN': if (putCallTabValue == 'PUT') { @@ -122,6 +101,8 @@ export const StockOptionsView = (props: ITickerProps) => { const sellCost = (targetPrice < s.value ? price : (price - (targetPrice - s.value))); return (sellCost / targetPrice) * (365 / numberofdays); } + case 'PCR': + return po.oi; default: return price?.toFixed(2); } @@ -156,6 +137,7 @@ export const StockOptionsView = (props: ITickerProps) => { PRICE ANNUAL_RETURN TOTAL_RETURN + PCR @@ -210,5 +192,12 @@ export const StockOptionsView = (props: ITickerProps) => { columns={columns} sx={{ display: 'grid' }} getRowId={(r) => `${r.symbol} - ${r.name}`} /> */} + { + pcrSelectedData && setPcrOpen(false)} /> + }
} diff --git a/src/lib/socket.ts b/src/lib/socket.ts index 4fd705a..50c2834 100644 --- a/src/lib/socket.ts +++ b/src/lib/socket.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react'; import { io } from 'socket.io-client'; import ky from 'ky'; -import { StockPriceData } from './types'; +import { NumberRange, OptionsInnerData, StockPriceData } from './types'; const URL = `https://tidy-spider-52.deno.dev` // const URL = `https://studious-telegram-4qq55vqj74hqgwp-8000.app.github.dev` @@ -90,25 +90,13 @@ export const useMyStockList = () => { return { mytickers, addToWatchlist, removeFromWatchlist }; } + type OptionsData = { currentPrice: number, - options: Record, - p: Record - }> + options: Record } -export type NumberRange = { start: number, end: number } + export const useOptionTracker = (symbol: string) => { const [data, setOd] = useState(); diff --git a/src/lib/types.ts b/src/lib/types.ts index 1beedf0..7f37ad3 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -23,3 +23,27 @@ export type StockPriceData = { } } } + +export type IOptionsGrid = { + id: string +} + +export type NumberRange = { start: number, end: number } + + +export type OptionsInnerData = { + c: Record, + p: Record +} \ No newline at end of file From 32b14321757a1f70d322fa7b97e8623c9b606db9 Mon Sep 17 00:00:00 2001 From: mnsrulz Date: Thu, 25 Apr 2024 04:28:17 +0000 Subject: [PATCH 4/4] added the indicator --- src/components/pcr.tsx | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/components/pcr.tsx b/src/components/pcr.tsx index 6d60828..862ee55 100644 --- a/src/components/pcr.tsx +++ b/src/components/pcr.tsx @@ -1,5 +1,5 @@ import { IOptionsGrid, NumberRange, OptionsInnerData } from "@/lib/types"; -import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from "@mui/material"; +import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography, Grid } from "@mui/material"; import { BarChart } from '@mui/x-charts/BarChart'; import { StrikePriceSlider } from "./StrikePriceSlider"; import { axisClasses } from '@mui/x-charts'; @@ -44,6 +44,7 @@ export const Pcr = (props: ITickerProps) => { value: Number(s) })).filter(n => n.value >= strikePriceRange.start && n.value <= strikePriceRange.end); + const dataset = workingStrikePrices.map(d => { return { strike: d.value, @@ -52,10 +53,33 @@ export const Pcr = (props: ITickerProps) => { } }) + const totalCalls = dataset.map(d => d.c).reduce((a, b) => a + b); + const totalPuts = dataset.map(d => d.p).reduce((a, b) => a + b); + const pcr = (totalPuts / totalCalls); + const sentiment = pcr > 0.7 ? 'Bearish': 'Bullish'; + return ( - Put Call Ratio - {currentPrice} + {/* Put Call Ratio */} + + + + Current Stock Price + + + {currentPrice} + + + + + Put Call Ratio + + + {pcr.toFixed(2)} {sentiment} + + +