From 7e1655915b42758a910e78ff5f726514e343ff23 Mon Sep 17 00:00:00 2001 From: Suyesh Shankar Date: Mon, 9 Oct 2023 10:54:20 +0530 Subject: [PATCH] Updated Features --- src/app/components/Charts/LineChart.tsx | 60 ++++++++ src/pages/analysis/index.tsx | 26 ++++ src/pages/api/historicalData.ts | 35 +++++ src/pages/nse-options/index.tsx | 189 ++++++++++++++++++++---- src/stores/ChartStore.ts | 76 ++++------ src/stores/ExpiryDateStore.ts | 2 +- src/stores/NseFetchStore.ts | 50 ++++++- src/stores/storesContext.ts | 14 ++ src/types.ts | 3 +- 9 files changed, 380 insertions(+), 75 deletions(-) create mode 100644 src/app/components/Charts/LineChart.tsx create mode 100644 src/pages/analysis/index.tsx create mode 100644 src/pages/api/historicalData.ts create mode 100644 src/stores/storesContext.ts diff --git a/src/app/components/Charts/LineChart.tsx b/src/app/components/Charts/LineChart.tsx new file mode 100644 index 0000000..1885903 --- /dev/null +++ b/src/app/components/Charts/LineChart.tsx @@ -0,0 +1,60 @@ +import { createChart, ColorType } from 'lightweight-charts'; +import React, { useEffect, useRef } from 'react'; + +interface ChartProps { + data: { time: string; value: number }[]; + colors?: { + backgroundColor?: string; + lineColor?: string; + textColor?: string; + areaTopColor?: string; + areaBottomColor?: string; + }; +} + +export const ChartComponent: React.FC = ({ data, colors = {} }) => { + const { + backgroundColor = 'white', + lineColor = '#2962FF', + textColor = 'black', + areaTopColor = '#2962FF', + areaBottomColor = 'rgba(41, 98, 255, 0.28)', + } = colors; + + const chartContainerRef = useRef(null); + + useEffect(() => { + if (chartContainerRef.current) { + const chart = createChart(chartContainerRef.current, { + layout: { + background: { type: ColorType.Solid, color: backgroundColor }, + textColor, + }, + width: chartContainerRef.current?.clientWidth || 0, + height: 300, + }); + + const handleResize = () => { + if (chartContainerRef.current) { + chart.applyOptions({ width: chartContainerRef.current.clientWidth }); + } + }; + + chart.timeScale().fitContent(); + + const newSeries = chart.addAreaSeries({ lineColor, topColor: areaTopColor, bottomColor: areaBottomColor }); + newSeries.setData(data); + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + chart.remove(); + }; + } + }, [data, backgroundColor, lineColor, textColor, areaTopColor, areaBottomColor]); + + return ( +
+ ); +}; \ No newline at end of file diff --git a/src/pages/analysis/index.tsx b/src/pages/analysis/index.tsx new file mode 100644 index 0000000..0b5ec47 --- /dev/null +++ b/src/pages/analysis/index.tsx @@ -0,0 +1,26 @@ +import useSWR from 'swr'; +import axios from 'axios'; +import {ChartComponent} from '../../app/components/Charts/LineChart'; // Assuming ChartComponent is in the same file + +const fetcher = (url: string) => axios.get(url).then(res => res.data); + +function App(props: any) { + const { data, error } = useSWR(`/api/historicalData?symbol=NIFTY`, fetcher); + + if (error) return
Error loading data
; + if (!data) return
Loading...
; + + // Process data into the format required by the chart + const chartData = data.map((item: { _id: { year: number, month: number }, data: Array<{ datetime: string, close: number }> }) => { + return { + time: `${item._id.year}-${item._id.month}`, + value: item.data.reduce((acc, curr) => acc + Number(curr.close), 0) / item.data.length // average close value for the month + }; + }); + + return ( + + ); +} + +export default App; \ No newline at end of file diff --git a/src/pages/api/historicalData.ts b/src/pages/api/historicalData.ts new file mode 100644 index 0000000..ce77646 --- /dev/null +++ b/src/pages/api/historicalData.ts @@ -0,0 +1,35 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { MongoClient } from 'mongodb'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { symbol } = req.query; + + if (typeof symbol !== 'string') { + res.status(400).json({ error: 'Invalid symbol' }); + return; + } + + const client = new MongoClient('mongodb://thestonepot:5PZAXjH03P4IT7pDuzBn3A7w1mzHwt0nIphn1shsO936Fj60clpMjdFig7BVrCsQabGfOaefWunWACDbuoUNRw%3D%3D@thestonepot.mongo.cosmos.azure.com:10255/?ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=@thestonepot@'); + await client.connect(); + const db = client.db('yourDatabaseName'); + const collection = db.collection('time_series'); + + const historicalData = await collection.aggregate([ + { $match: { script: symbol } }, + { $project: { + year: { $year: "$datetime" }, + month: { $month: "$datetime" }, + day: { $dayOfMonth: "$datetime" }, + data: "$$ROOT" + }}, + { $group: { + _id: { year: "$year", month: "$month" }, + data: { $push: "$data" } + }}, + { $sort: { "_id.year": 1, "_id.month": 1 } } + ]).toArray(); + + await client.close(); + + res.status(200).json(historicalData); +} \ No newline at end of file diff --git a/src/pages/nse-options/index.tsx b/src/pages/nse-options/index.tsx index b9457b9..d2edd9a 100644 --- a/src/pages/nse-options/index.tsx +++ b/src/pages/nse-options/index.tsx @@ -24,6 +24,8 @@ const NseFlatDataOptions = observer(({ initialData, initialStock }: { initialDat // Add a new state to store the previous instrument value const [prevInstrumentValue, setPrevInstrumentValue] = useState(null); const [isFetchingExpiryDates, setIsFetchingExpiryDates] = useState(false); + const [isDividedByLotSize, setIsDividedByLotSize] = useState(false); + const dataManager = new DataManager({ json: initialData, @@ -109,6 +111,24 @@ symbolStoreInstance.symbolStore.fetchSymbols().then(() => { }; }, [store]); + const [isInitialRender, setIsInitialRender] = useState(true); + +useEffect(() => { + const currentInstrumentValue = store?.nseFetchStore.data?.[0]?.CE_underlyingValue + || store?.nseFetchStore.data?.[0]?.PE_underlyingValue + || null; + + if (currentInstrumentValue !== null) { + if (isInitialRender) { + // For the initial render, just set the prevInstrumentValue to the current value. + setIsInitialRender(false); + } else if (prevInstrumentValue !== currentInstrumentValue) { + // For subsequent renders, only update prevInstrumentValue if the current value has changed. + setPrevInstrumentValue(currentInstrumentValue); + } + } +}, [store, isInitialRender, prevInstrumentValue]); + @@ -122,21 +142,34 @@ symbolStoreInstance.symbolStore.fetchSymbols().then(() => { const totalPE_totalTradedVolume = displayData.reduce((total, row) => total + (row.PE_totalTradedVolume || 0), 0); // This calculates the ATM's index within the `displayData` array const newATMIndex = atmIndex - startSliceIndex; + const [hasError, setHasError] = useState(false); console.log('Store:', store?.nseFetchStore); console.log('ATM Strike Index:', store?.nseFetchStore.atmStrikeIndex); console.log('Data Length:', store?.nseFetchStore.data.length); - // rowDataBound event handler - const rowDataBound = (args: any) => { - const rowIndex = Number(args.row.getAttribute('aria-rowindex')); - if (store && store.nseFetchStore.atmStrikeIndex !== null) { - if (rowIndex - 1 === (store.nseFetchStore.atmStrikeIndex - - Math.max((store?.nseFetchStore.atmStrikeIndex || 0) - selectedRange, 0))) { - args.row.style.background = 'beige'; +// rowDataBound event handler +const rowDataBound = (args: any) => { + const rowIndex = Number(args.row.getAttribute('aria-rowindex')); + if (store && store.nseFetchStore.atmStrikeIndex !== null) { + if (rowIndex - 1 === (store.nseFetchStore.atmStrikeIndex - + Math.max((store?.nseFetchStore.atmStrikeIndex || 0) - selectedRange, 0))) { + args.row.style.background = 'beige'; + + // Find the Strike Price cell and apply custom styling + const strikePriceCell = args.row.querySelector('[aria-colindex="5"]'); + if (strikePriceCell) { + strikePriceCell.style.fontWeight = 'bold'; + strikePriceCell.style.fontSize = '13'; + strikePriceCell.style.color = '#090909'; + strikePriceCell.style.padding = '10px'; // Increase cell size + strikePriceCell.style.boxShadow = '5px 0 5px -2px #888, -5px 0 5px -2px #888'; // Add a shadow } } - }; + } + + +}; // queryCellInfo event handler const queryCellInfo = (args: any) => { @@ -166,6 +199,7 @@ symbolStoreInstance.symbolStore.fetchSymbols().then(() => { if (args.column.field === 'strikePrice') { args.cell.style.backgroundColor = '#C9C8C8'; + } // Center align the content in all columns @@ -178,14 +212,17 @@ symbolStoreInstance.symbolStore.fetchSymbols().then(() => { } const cellTemplate = (type: 'CE' | 'PE', property: 'Delta' | 'Vega' | 'Gamma' | 'Theta', rowData: any) => { + const formatNumber = (number: number) => { + return Math.round(number).toLocaleString('en-IN'); + }; switch (property) { case 'Delta': - return ( -
-
{rowData[`${type}_lastPrice`]}
-
Delta: {rowData[`${type}_delta`] ? Number(rowData[`${type}_delta`]).toFixed(2) : 'N/A'}
-
- ); + return ( +
+
{rowData[`${type}_lastPrice`]}
+
Delta: {rowData[`${type}_delta`]}
+
+ ); case 'Vega': return type === 'CE' ? ceVega(rowData) : peVega(rowData); @@ -194,7 +231,7 @@ symbolStoreInstance.symbolStore.fetchSymbols().then(() => { case 'Gamma': return (
-
{rowData[`${type}_totalTradedVolume`]}
+
{rowData[`${type}_totalTradedVolume`].toLocaleString()}
Gamma: {rowData[`${type}_gamma`]}
); @@ -211,8 +248,21 @@ symbolStoreInstance.symbolStore.fetchSymbols().then(() => { const ceVega = (rowData: any) => { const color = rowData['CE_changeinOpenInterest'] > 0 ? 'green' : 'red'; - const changeInOI = Math.abs(rowData['CE_changeinOpenInterest']); - const maxSize = 150000; // Adjust this value as needed + //const changeInOI = Math.abs(rowData['CE_changeinOpenInterest']); + + const lot_size = store?.nseFetchStore?.lot_size; + + // const oi = isDividedByLotSize && lot_size && lot_size !== 0 ? rowData['CE_openInterest'] / lot_size : rowData['CE_openInterest']; + //const changeInOI = isDividedByLotSize && lot_size && lot_size !== 0 ? rowData['CE_changeinOpenInterest'] / lot_size : rowData['CE_changeinOpenInterest']; + const oi = isDividedByLotSize && lot_size && lot_size !== 0 + ? Math.abs(rowData['CE_openInterest'] / lot_size) + : rowData['CE_openInterest']; + +const changeInOI = isDividedByLotSize && lot_size && lot_size !== 0 + ? Math.abs(rowData['CE_changeinOpenInterest'] / lot_size) + : Math.abs(rowData['CE_changeinOpenInterest']); + const maxSize = isDividedByLotSize ? 200000 / (lot_size || 1) : 200000; // Adjust this line + //const maxSize = 200000; // Adjust this value as needed const size = Math.min(changeInOI / maxSize * 5, 100); const progressStyle = { backgroundColor: color === 'green' ? '#77AE57' : '#ff0000', @@ -228,7 +278,8 @@ symbolStoreInstance.symbolStore.fetchSymbols().then(() => {
- {rowData['CE_openInterest']} ({rowData['CE_changeinOpenInterest']}) + {oi.toLocaleString()} ({changeInOI.toLocaleString()}) +
Vega: {rowData['CE_vega']}
@@ -238,7 +289,7 @@ symbolStoreInstance.symbolStore.fetchSymbols().then(() => { const peVega = (rowData: any) => { const color = rowData['PE_changeinOpenInterest'] > 0 ? 'green' : 'red'; const changeInOI = Math.abs(rowData['PE_changeinOpenInterest']); - const maxSize = 150000; // Adjust this value as needed + const maxSize = 200000; // Adjust this value as needed const size = Math.min(changeInOI / maxSize * 5, 100); const progressStyle = { backgroundColor: color === 'green' ? '#77AE57' : '#ff0000', @@ -254,21 +305,34 @@ symbolStoreInstance.symbolStore.fetchSymbols().then(() => {
- {rowData['PE_openInterest']} ({rowData['PE_changeinOpenInterest']}) + {rowData['PE_openInterest'].toLocaleString()} ({rowData['PE_changeinOpenInterest'].toLocaleString()}) +
Vega: {rowData['PE_vega']}
); }; - - + const calculateFairPrice = (data: any) => { + const atmStrikePrice = store?.nseFetchStore.atmStrike || 0; // Changed this line to fetch atmStrike directly from the store + const ceLastPrice = data?.[0]?.CE_lastPrice || 0; + const peLastPrice = data?.[0]?.PE_lastPrice || 0; + + // Log the values + console.log("ATM Strike Price: ", atmStrikePrice); + console.log("CE Last Price: ", ceLastPrice); + console.log("PE Last Price: ", peLastPrice); + + return atmStrikePrice + ceLastPrice - peLastPrice; + }; + // helper function to round a value to the nearest half up function roundHalfUp(niftyValue: number, base: number) { return Math.sign(niftyValue) * Math.round(Math.abs(niftyValue) / base) * base; } + const lotSize = store?.nseFetchStore?.lot_size; @@ -294,6 +358,8 @@ symbolStoreInstance.symbolStore.fetchSymbols().then(() => {
{ (() => { + console.log('Prev Instrument Value:', prevInstrumentValue); // Print the current value + const data = store?.nseFetchStore?.data; const underlyingValue = data?.[0]?.CE_underlyingValue || data?.[0]?.PE_underlyingValue || 'N/A'; const difference = data && data.length > 0 && 'CE_underlyingValue' in data[0] @@ -312,6 +378,59 @@ symbolStoreInstance.symbolStore.fetchSymbols().then(() => { }
+
+ { + (() => { + const data = store?.nseFetchStore?.data; + const fairPrice = calculateFairPrice(data); + + return ( +
+ Fair Price: {fairPrice.toFixed(2)} +
+ ); + })() + } +
+
+ setIsDividedByLotSize(false)} + /> + + + setIsDividedByLotSize(true)} + /> + +
+ +
+ { + (() => { + const lot_size = store?.nseFetchStore?.lot_size; // Accessing lotSize from the store + + return ( +
+ {/* Display lotSize if it's available */} + {lot_size !== null && lot_size !== undefined ? ( +

Lot Size: {lot_size}

+ ) : ( +

Lot Size is not available

+ )} +
+ ); + })() + } +
+
{/* This is the new div for selecting range */} {[3,5,10].map(num => (
{
-

Total CE Open Interest: {totalCE_openInterest}

-

Total CE Total Traded Volume: {totalCE_totalTradedVolume}

-

Total PE Open Interest: {totalPE_openInterest}

-

Total PE Total Traded Volume: {totalPE_totalTradedVolume}

-
+

Total CE Open Interest: + {(isDividedByLotSize && lotSize) ? + (totalCE_openInterest / lotSize).toLocaleString('en-US') : + totalCE_openInterest.toLocaleString('en-US')} +

+

Total CE Total Traded Volume: + {(isDividedByLotSize && lotSize) ? + (totalCE_totalTradedVolume / lotSize).toLocaleString('en-US') : + totalCE_totalTradedVolume.toLocaleString('en-US')} +

+

Total PE Open Interest: + {(isDividedByLotSize && lotSize) ? + (totalPE_openInterest / lotSize).toLocaleString('en-US') : + totalPE_openInterest.toLocaleString('en-US')} +

+

Total PE Total Traded Volume: + {(isDividedByLotSize && lotSize) ? + (totalPE_totalTradedVolume / lotSize).toLocaleString('en-US') : + totalPE_totalTradedVolume.toLocaleString('en-US')} +

+
)} diff --git a/src/stores/ChartStore.ts b/src/stores/ChartStore.ts index a150332..2d3bbf0 100644 --- a/src/stores/ChartStore.ts +++ b/src/stores/ChartStore.ts @@ -1,48 +1,36 @@ -import { makeObservable, observable, action, runInAction } from 'mobx'; -import { NseFetchStore } from './NseFetchStore'; -import {NseOptionData} from '../types'; +import { useLocalObservable } from 'mobx-react-lite'; +import axios from 'axios'; -interface ChartData { - CE_strikePrice: number; - CE_openInterest: number; - CE_changeinOpenInterest: number; - PE_strikePrice: number; - PE_openInterest: number; - PE_changeinOpenInterest: number; +interface HistoricalData { + // Define the shape of your historical data here + datetime: string; + stock_code: string; + exchange_code: string; + product_type: string; + expiry_date: string; + right: string; + strike_price: string; + open: string; + high: string; + low: string; + close: string; + volume: string; + open_interest: string; + count: number; } -export class ChartStore { - chartData: ChartData[] = []; - private nseFetchStore: NseFetchStore; +export const useChartStore = () => { + const store = useLocalObservable(() => ({ + historicalData: [] as HistoricalData[], + fetchHistoricalData: async (symbol: string) => { + try { + const response = await axios.get(`/api/historicalData?symbol=${symbol}`); + store.historicalData = response.data; + } catch (error) { + console.error('Error fetching historical data:', error); + } + }, + })); - constructor(nseFetchStore: NseFetchStore) { - this.nseFetchStore = nseFetchStore; - makeObservable(this, { - chartData: observable, - setChartData: action, - }); - - this.nseFetchStore.fetchData().then((data: NseOptionData[]) => { - - console.log('Data from nseFetchStore:', data); - const chartData: ChartData[] = data.map(item => ({ - CE_strikePrice: item.strikePrice, - CE_openInterest: item.CE_openInterest, - CE_changeinOpenInterest: item.CE_changeinOpenInterest, - PE_strikePrice: item.strikePrice, - PE_openInterest: item.PE_openInterest, - PE_changeinOpenInterest: item.PE_changeinOpenInterest, - })); - - runInAction(() => { - this.setChartData(chartData); - }); - - console.log('Index graph data:', this.chartData); - }); - } - - setChartData(data: ChartData[]) { - this.chartData = data; - } -} \ No newline at end of file + return store; +}; \ No newline at end of file diff --git a/src/stores/ExpiryDateStore.ts b/src/stores/ExpiryDateStore.ts index 9848fec..f8a4ced 100644 --- a/src/stores/ExpiryDateStore.ts +++ b/src/stores/ExpiryDateStore.ts @@ -27,7 +27,7 @@ export class ExpiryDateStore { try { - const response = await fetch(`https://tradepodapisrv.azurewebsites.net/api/get-expiry?symbol=${symbol}`); + const response = await fetch(`http://127.0.0.1:8000/api/get-expiry?symbol=${symbol}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } diff --git a/src/stores/NseFetchStore.ts b/src/stores/NseFetchStore.ts index 333f7cd..d38c534 100644 --- a/src/stores/NseFetchStore.ts +++ b/src/stores/NseFetchStore.ts @@ -1,5 +1,4 @@ import { makeObservable, observable, action, reaction, runInAction, autorun } from 'mobx'; -import useSWR from 'swr'; import axios from 'axios'; import { NseOptionData, NseApiResponse} from '../types'; import { ExpiryDateStore } from './ExpiryDateStore'; @@ -17,6 +16,10 @@ export class NseFetchStore { symbol: string = 'NIFTY'; expiryDateStore: ExpiryDateStore; defaultStore: DefaultStore; + lot_size: number | null = null; // Added a new observable property for lot size + fairPrice: number | null = null; + + setSymbol = async (symbol: string): Promise => { console.log('setSymbol called with symbol:', symbol); @@ -63,6 +66,8 @@ export class NseFetchStore { setExpiryDate: action, setExpiryDates: action, setSymbol: action, + fetchPutCallRatioData: action, + lot_size: observable // Added this line }); // Use autorun instead of reaction @@ -76,6 +81,8 @@ export class NseFetchStore { this.setSymbol(this.symbol).then(() => { if (initialNseData) { this.data.replace(initialNseData); + // Set lot_size when initializing the store with data if available + this.lot_size = initialNseData[0]?.lot_size || null; } if (typeof window !== 'undefined') { @@ -98,6 +105,8 @@ export class NseFetchStore { if (data.length > 0) { console.log('Setting underlyingValue to:', data[0].CE_underlyingValue || data[0].PE_underlyingValue); this.underlyingValue = data[0].CE_underlyingValue || data[0].PE_underlyingValue; + // Extract and set lot_size from the fetched data + this.lot_size = data[0].lot_size || null; } else { this.underlyingValue = null; } @@ -144,6 +153,7 @@ export class NseFetchStore { setExpiryDates(dates: string[]): void { this.expiryDates = dates; } + fetchData = async (userSelectedStock: string = this.symbol || 'NIFTY', firstExpiryDate: string | null = this.expiryDate) => { if (!firstExpiryDate) { @@ -153,7 +163,8 @@ export class NseFetchStore { this.isLoading = true; try { - const response = await axios.get(`https://tradepodapisrv.azurewebsites.net/api/paytm/?symbol=${encodeURIComponent(this.symbol)}&expiry_date=${encodeURIComponent(firstExpiryDate)}`); + const response = await axios.get(`http://127.0.0.1:8000/api/paytm/?symbol=${encodeURIComponent(this.symbol)}&expiry_date=${encodeURIComponent(firstExpiryDate)}`); + console.log("API Response: ", response.data); // Add this line to log the API response const data = response.data as NseApiResponse; if (data && data.nse_options_data) { @@ -177,6 +188,41 @@ export class NseFetchStore { window.clearInterval(this.intervalId); } } + + + + fetchPutCallRatioData = async (symbol: string, expiryDate: string) => { + // Fetch the data for the given symbol and expiry date + const data = await this.fetchData(symbol, expiryDate); + + // Calculate the Put/Call ratio for each strike price + const putCallRatioData = data.map(option => { + if (option.CE_totalTradedVolume && option.PE_totalTradedVolume) { + return { + strikePrice: option.strikePrice, + putCallRatio: option.PE_totalTradedVolume / option.CE_totalTradedVolume + }; + } else { + return null; + } + }).filter(item => item !== null); + + // Format the data for the chart + const formattedData = putCallRatioData.map(item => { + if (item) { + const time = new Date(item.strikePrice).getTime() / 1000; // Convert the strike price to a UNIX timestamp + + return { + time, + value: item.putCallRatio + }; + } + return null; + }).filter(item => item !== null); + + return formattedData; + }; + } export const initializeNseFetchStore = (defaultStore: DefaultStore, expiryDateStore: ExpiryDateStore, initialNseData?: NseOptionData[]): NseFetchStore => { diff --git a/src/stores/storesContext.ts b/src/stores/storesContext.ts new file mode 100644 index 0000000..c753b0c --- /dev/null +++ b/src/stores/storesContext.ts @@ -0,0 +1,14 @@ +import React from 'react'; +import { initializeStores } from './initializeStores'; +import { NseOptionData, OptionData } from '@/types'; + +// Initialize your stores here +const initialData: { oldData?: OptionData[]; nseData?: NseOptionData[] } = { + oldData: [], + nseData: [], +}; +const stores = initializeStores(initialData); + +export const storesContext = React.createContext(stores); + +export const useStores = () => React.useContext(storesContext); \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index c61d862..2701779 100644 --- a/src/types.ts +++ b/src/types.ts @@ -139,7 +139,7 @@ export interface NseOptionData { PE_vega: number; PE_gamma: number; PE_theta: number; - PE_delta: number; + PE_delta: number | null; PE_underlyingValue: number; CE_calcIV: number | null; PE_calcIV: number; @@ -147,6 +147,7 @@ export interface NseOptionData { CE_VOLUME: number; PE_OI: number; PE_VOLUME: number; + lot_size: number; }