From 3cf34f43819d26fe28deff88a17408845c0b146b Mon Sep 17 00:00:00 2001 From: Debasish Pal <48341250+debpal@users.noreply.github.com> Date: Wed, 2 Oct 2024 20:05:00 +0300 Subject: [PATCH] new version --- BharatFinTrack/__init__.py | 4 +- BharatFinTrack/data/equity_indices.xlsx | Bin 24385 -> 24291 bytes BharatFinTrack/nse_index.py | 194 ++++++++++++++++++++++++ BharatFinTrack/nse_tri.py | 3 +- README.md | 3 +- docs/changelog.rst | 19 ++- docs/data_download.rst | 44 ------ docs/functionality.rst | 29 ++++ docs/index.rst | 2 +- docs/quickstart.rst | 33 +++- docs/requirements-docs.txt | 2 + pyproject.toml | 6 +- requirements-gh-action.txt | 2 + requirements-mypy.txt | 1 + tests/test_bharatfintrack.py | 33 +++- 15 files changed, 319 insertions(+), 56 deletions(-) create mode 100644 BharatFinTrack/nse_index.py delete mode 100644 docs/data_download.rst create mode 100644 docs/functionality.rst diff --git a/BharatFinTrack/__init__.py b/BharatFinTrack/__init__.py index a8916df..1f1bbd3 100644 --- a/BharatFinTrack/__init__.py +++ b/BharatFinTrack/__init__.py @@ -1,11 +1,13 @@ from .nse_product import NSEProduct +from .nse_index import NSEIndex from .nse_tri import NSETRI __all__ = [ 'NSEProduct', + 'NSEIndex', 'NSETRI' ] -__version__ = '0.1.0' +__version__ = '0.1.1' diff --git a/BharatFinTrack/data/equity_indices.xlsx b/BharatFinTrack/data/equity_indices.xlsx index 27746f424dbb42aac1361eeaa3b8143f9b03fa76..99ebf97c5771a01cd75bccb7e7ede6fe25cc7436 100644 GIT binary patch delta 7365 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 delta 7445 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 GZTcS{fDRY{ diff --git a/BharatFinTrack/nse_index.py b/BharatFinTrack/nse_index.py new file mode 100644 index 0000000..724c692 --- /dev/null +++ b/BharatFinTrack/nse_index.py @@ -0,0 +1,194 @@ +import os +import tempfile +import typing +import datetime +import dateutil.relativedelta +import pandas +import requests +import bs4 +import matplotlib +from .nse_product import NSEProduct +from .core import Core + + +class NSEIndex: + + ''' + Download and analyze NSE index price data. + ''' + + def all_equity_index_cagr_from_inception( + self, + excel_file: str, + http_headers: typing.Optional[dict[str, str]] = None + ) -> pandas.DataFrame: + + ''' + Returns a DataFrame with the CAGR(%) of all NSE equity indices + (excluding dividend reinvestment) from inception. + + Parameters + ---------- + excel_file : str + Path to an Excel file to save the DataFrame. + + http_headers : dict, optional + HTTP headers for the web request. Defaults to + :attr:`BharatFinTrack.core.Core.default_http_headers` if not provided. + + Returns + ------- + DataFrame + A multi-index DataFrame with the CAGR(%) for all NSE equity indices from inception, + sorted in descending order by CAGR(%) within each index category. + ''' + + # web request headers + headers = Core().default_http_headers if http_headers is None else http_headers + + # download data + main_url = 'https://www.niftyindices.com' + csv_url = main_url + '/reports/daily-reports' + response = requests.get(csv_url, headers=headers) + soup = bs4.BeautifulSoup(response.content, 'html.parser') + for anchor in soup.find_all('a'): + if anchor['href'].endswith('.csv') and anchor['id'] == 'dailysnapOneDaybefore': + csv_link = main_url + anchor['href'] + else: + pass + response = requests.get(csv_link, headers=headers) + with tempfile.TemporaryDirectory() as tmp_dir: + daily_file = os.path.join(tmp_dir, 'daily.csv') + with open(daily_file, 'wb') as daily_data: + daily_data.write(response.content) + daily_df = pandas.read_csv(daily_file) + + # processing downloaded data + date_string = datetime.datetime.strptime( + daily_df.loc[0, 'Index Date'], '%d-%m-%Y' + ) + daily_date = date_string.date() + daily_df = daily_df[['Index Name', 'Index Date', 'Closing Index Value']] + daily_df.columns = ['Index Name', 'Date', 'Close'] + daily_df['Index Name'] = daily_df['Index Name'].apply(lambda x: x.upper()) + exclude_word = [ + 'G-SEC', + '1D RATE', + 'INDIA VIX', + 'DIVIDEND POINTS' + ] + exclude_index = daily_df['Index Name'].apply( + lambda x: any(word in x for word in exclude_word) + ) + daily_df = daily_df[~exclude_index].reset_index(drop=True) + daily_df['Date'] = daily_date + + # processing base DataFrame + base_df = NSEProduct()._dataframe_equity_index + base_df = base_df.reset_index() + category = list(base_df['Category'].unique()) + base_df = base_df.drop(columns=['ID', 'API TRI']) + base_df['Base Date'] = base_df['Base Date'].apply(lambda x: x.date()) + + # checking absent indices in both files + base_unmatch = { + 'NIFTY 50 FUTURES INDEX': 'NIFTY 50 FUTURES PR', + 'NIFTY 50 FUTURES TR INDEX': 'NIFTY 50 FUTURES TR', + 'NIFTY HEALTHCARE INDEX': 'NIFTY HEALTHCARE' + } + daily_df['Index Name'] = daily_df['Index Name'].apply( + lambda x: base_unmatch.get(x, x) + ) + # base_index = base_df['Index Name'] + # daily_index = daily_df['Index Name'] + # unavailable_base = list(base_index[~base_index.isin(daily_index)]) + # print(f'Base indices {unavailable_base} are not available in daily indices.') + # unavailable_daily = list(daily_index[~daily_index.isin(base_index)]) + # print(f'Daily indices {unavailable_daily} are not available in base indices.') + + # merging data + cagr_df = base_df.merge(daily_df) + cagr_df['Return(1 INR)'] = (cagr_df['Close'] / cagr_df['Base Value']).round(2) + cagr_df['Years'] = list( + map( + lambda x: dateutil.relativedelta.relativedelta(daily_date, x).years, cagr_df['Base Date'] + ) + ) + cagr_df['Days'] = list( + map( + lambda x, y: (daily_date - x.replace(year=x.year + y)).days, cagr_df['Base Date'], cagr_df['Years'] + ) + ) + total_years = cagr_df['Years'] + (cagr_df['Days'] / 365) + cagr_df['CAGR(%)'] = 100 * (pow(cagr_df['Close'] / cagr_df['Base Value'], 1 / total_years) - 1) + + # Convert 'Category' column to categorical data types with a defined order + cagr_df['Category'] = pandas.Categorical( + cagr_df['Category'], + categories=category, + ordered=True + ) + + # Sort the dataframe + cagr_df = cagr_df.sort_values( + by=['Category', 'CAGR(%)', 'Years', 'Days'], + ascending=[True, False, False, False] + ) + + # output + dfs_category = map(lambda x: cagr_df[cagr_df['Category'] == x], category) + dataframes = list( + map( + lambda x: x.drop(columns=['Category']).reset_index(drop=True), dfs_category + ) + ) + output = pandas.concat( + dataframes, + keys=[word.upper() for word in category], + names=['Category', 'ID'] + ) + + # saving the DataFrame + excel_ext = Core()._excel_file_extension(excel_file) + if excel_ext != '.xlsx': + raise Exception( + f'Input file extension "{excel_ext}" does not match the required ".xlsx".' + ) + else: + with pandas.ExcelWriter(excel_file, engine='xlsxwriter') as excel_writer: + output.to_excel(excel_writer, index=True) + workbook = excel_writer.book + worksheet = excel_writer.sheets['Sheet1'] + # number of columns for DataFrame indices + index_cols = len(output.index.names) + # format columns + worksheet.set_column(0, index_cols - 1, 15) + worksheet.set_column(index_cols, index_cols, 60) + worksheet.set_column(index_cols + 1, index_cols + output.shape[1] - 2, 15) + worksheet.set_column( + index_cols + output.shape[1] - 1, + index_cols + output.shape[1] - 1, 15, + workbook.add_format({'num_format': '#,##0.0'}) + ) + # Dataframe colors + get_colormap = matplotlib.colormaps.get_cmap('Pastel2') + colors = [ + get_colormap(count / len(category)) for count in range(len(category)) + ] + hex_colors = [ + '{:02X}{:02X}{:02X}'.format(*[int(num * 255) for num in color]) for color in colors + ] + # coloring of DataFrames + start_col = index_cols - 1 + end_col = index_cols + len(output.columns) - 1 + start_row = 1 + for df, color in zip(dataframes, hex_colors): + color_format = workbook.add_format({'bg_color': color}) + end_row = start_row + len(df) - 1 + worksheet.conditional_format( + start_row, start_col, end_row, end_col, + {'type': 'no_blanks', 'format': color_format} + ) + start_row = end_row + 1 + + return output diff --git a/BharatFinTrack/nse_tri.py b/BharatFinTrack/nse_tri.py index a1a046c..1eff518 100644 --- a/BharatFinTrack/nse_tri.py +++ b/BharatFinTrack/nse_tri.py @@ -8,7 +8,8 @@ class NSETRI: ''' - Download and analyze NSE TRI (Total Return Index) data. + Download and analyze NSE TRI (Total Return Index) data, + including both price index and dividend reinvestment. ''' @property diff --git a/README.md b/README.md index dee3d94..6fb320f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ It is designed to simplify the process of downloading and analyzing financial da * [Nifty Indices](https://www.niftyindices.com/) - Provides access to the characteristics of NSE equity indices. - - Facilitates downloading TRI (Total Return Index) data for all NSE equity indices. + - Calculates the CAGR(%) of all NSE equity indices (excluding dividend reinvestment) from inception. + - Facilitates downloading Total Return Index, including both price and dividend reinvestment, data for all NSE equity indices. ## Roadmap diff --git a/docs/changelog.rst b/docs/changelog.rst index 50d0a14..f27ccc4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,14 +2,27 @@ Release Notes =============== + +Version 0.1.1 +-------------- + +* **Release date:** 02-Oct-2024 + +* **Feature Additions:** Introduced the :class:`BharatFinTrack.NSEIndex` class, which currently calculates the CAGR(%) of all NSE equity indices + (excluding dividend reinvestment) from inception. Additional features are planned for future releases. + +* **Documentation:** Updated to reflect the newly introduced features. + +* **Development Status:** Upgraded from Pre-Alpha to Alpha. + + Version 0.1.0 --------------- * **Release date:** 30-Sep-2024. * **Feature Additions:** Introduced :class:`BharatFinTrack.NSETRI` class, which facilitates downloading Total Return Index (TRI) data for all NSE equity indices. - - + * **Changes:** * Renamed class :class:`BharatFinTrack.NSETrack` to :class:`BharatFinTrack.NSEProduct` for improved clarity. @@ -17,7 +30,7 @@ Version 0.1.0 * **Documentation:** Added a tutorial on how to use the newly introduced features. -* **Development status:** Upgraded to Pre-Alpha from Planning. +* **Development status:** Upgraded from Planning to Pre-Alpha. Version 0.0.3 diff --git a/docs/data_download.rst b/docs/data_download.rst deleted file mode 100644 index c95949e..0000000 --- a/docs/data_download.rst +++ /dev/null @@ -1,44 +0,0 @@ -================== -Downloading Data -================== - -A brief overview of the features for downloading data. - - -Total Return Index Data -------------------------- -Download historical daily data for the NIFTY 50 index: - -.. code-block:: python - - import BharatFinTrack - nse_tri = BharatFinTrack.NSETRI() - nse_tri.download_historical_daily_data( - index='NIFTY 50', - start_date='23-SEP-2024', - end_date='27-SEP-2024' - ) - - -Expected output: - -.. code-block:: text - - Date Close - 0 2024-09-23 38505.51 - 1 2024-09-24 38507.55 - 2 2024-09-25 38602.21 - 3 2024-09-26 38916.76 - 4 2024-09-27 38861.64 - - - - - - - - - - - - diff --git a/docs/functionality.rst b/docs/functionality.rst new file mode 100644 index 0000000..a0a5e67 --- /dev/null +++ b/docs/functionality.rst @@ -0,0 +1,29 @@ +=============== +Functionality +=============== + + +Index CAGR(%) +--------------- +Save the CAGR(%) of all NSE equity indices (excluding dividend reinvestment) from inception to an Excel file. + +.. code-block:: python + + import BharatFinTrack + nse_index = BharatFinTrack.NSEIndex() + nse_index.all_index_cagr_from_inception( + excel_file=r"C:\Users\Username\Folder\file.xlsx" + ) + + + + + + + + + + + + + diff --git a/docs/index.rst b/docs/index.rst index df13ae4..74d3942 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,7 +13,7 @@ Welcome to BharatFinTrack's documentation! introduction installation quickstart - data_download + functionality modules changelog diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9f7051b..29ce379 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -90,4 +90,35 @@ Expected output: .. code-block:: text '03-Nov-1995' - 1000.0 \ No newline at end of file + 1000.0 + + + +Download Data +--------------- + +Total Return Index (TRI) +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Download historical daily TRI data, including both price and dividend reinvestment, for the NIFTY 50 index: + +.. code-block:: python + + import BharatFinTrack + nse_tri = BharatFinTrack.NSETRI() + nse_tri.download_historical_daily_data( + index='NIFTY 50', + start_date='23-SEP-2024', + end_date='27-SEP-2024' + ) + + +Expected output: + +.. code-block:: text + + Date Close + 0 2024-09-23 38505.51 + 1 2024-09-24 38507.55 + 2 2024-09-25 38602.21 + 3 2024-09-26 38916.76 + 4 2024-09-27 38861.64 \ No newline at end of file diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index f9d885e..1aae231 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -3,5 +3,7 @@ pandas>=2.2.2 requests>=2.32.3 openpyxl>=3.1.5 xlsxwriter>=3.2.0 +beautifulsoup4>=4.12.3 +matplotlib>=3.9.2 diff --git a/pyproject.toml b/pyproject.toml index 761248c..e9e7da5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,12 +13,14 @@ dependencies = [ "pandas>=2.2.2", "requests>=2.32.3", "openpyxl>=3.1.5", - "xlsxwriter>=3.2.0" + "xlsxwriter>=3.2.0", + "beautifulsoup4>=4.12.3", + "matplotlib>=3.9.2" ] readme = "README.md" requires-python = ">=3.10" classifiers = [ - "Development Status :: 2 - Pre-Alpha", + "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/requirements-gh-action.txt b/requirements-gh-action.txt index eab35f6..0c2aa22 100644 --- a/requirements-gh-action.txt +++ b/requirements-gh-action.txt @@ -4,3 +4,5 @@ pandas>=2.2.2 requests>=2.32.3 openpyxl>=3.1.5 xlsxwriter>=3.2.0 +beautifulsoup4>=4.12.3 +matplotlib>=3.9.2 diff --git a/requirements-mypy.txt b/requirements-mypy.txt index e5738a6..e77c8cb 100644 --- a/requirements-mypy.txt +++ b/requirements-mypy.txt @@ -1,2 +1,3 @@ types-requests +types-python-dateutil mypy diff --git a/tests/test_bharatfintrack.py b/tests/test_bharatfintrack.py index e0992a6..856b5b9 100644 --- a/tests/test_bharatfintrack.py +++ b/tests/test_bharatfintrack.py @@ -12,6 +12,12 @@ def nse_product(): yield BharatFinTrack.NSEProduct() +@pytest.fixture(scope='class') +def nse_index(): + + yield BharatFinTrack.NSEIndex() + + @pytest.fixture(scope='class') def nse_tri(): @@ -55,7 +61,7 @@ def test_save_dataframes_equity_indices( # pass test with tempfile.TemporaryDirectory() as tmp_dir: - excel_file = os.path.join(tmp_dir, 'equity_index.xlsx') + excel_file = os.path.join(tmp_dir, 'equity.xlsx') df = nse_product.save_dataframe_equity_index_parameters( excel_file=excel_file ) @@ -186,7 +192,7 @@ def test_download_historical_daily_data( # pass test for saving the output DataFrame to an Excel file with tempfile.TemporaryDirectory() as tmp_dir: - excel_file = os.path.join(tmp_dir, 'equity_index.xlsx') + excel_file = os.path.join(tmp_dir, 'equity.xlsx') nse_tri.download_historical_daily_data( index='NIFTY 50', start_date='23-Sep-2024', @@ -241,3 +247,26 @@ def test_index_download_historical_daily_data( end_date='27-Sep-2024' ) assert float(df.iloc[-1, -1]) == expected_value + + +def test_all_index_cagr_from_inception( + nse_index, + message +): + + # pass test + with tempfile.TemporaryDirectory() as tmp_dir: + excel_file = os.path.join(tmp_dir, 'equity.xlsx') + nse_index.all_equity_index_cagr_from_inception( + excel_file=excel_file + ) + df = pandas.read_excel(excel_file, index_col=[0, 1]) + assert df.shape[1] == 9 + assert len(df.index.get_level_values('Category').unique()) == 5 + + # error test for invalid Excel file input + with pytest.raises(Exception) as exc_info: + nse_index.all_equity_index_cagr_from_inception( + excel_file='equily.xl' + ) + assert exc_info.value.args[0] == message['error_excel']