diff --git a/BharatFinTrack/nse_tri.py b/BharatFinTrack/nse_tri.py index 32cd274..5cf018a 100644 --- a/BharatFinTrack/nse_tri.py +++ b/BharatFinTrack/nse_tri.py @@ -885,7 +885,7 @@ def sip_summary_from_given_date( return summary - def sip_growth_comparison_across_indices( + def yearwise_sip_xirr_growth_comparison_across_indices( self, indices: list[str], folder_path: str, @@ -893,11 +893,11 @@ def sip_growth_comparison_across_indices( ) -> pandas.DataFrame: ''' - Generates a DataFrame that compares SIP investment growth on the - first date of each month across multiple indices over the years. - The output DataFrame is saved to an Excel file, where the cells with - the highest growth among indices for each year are highlighted in green-yellow, - and those with the lowest growth are highlighted in sandy brown. + Generates two DataFrames that compares year-wise XIRR (%) and + growth multiple (X) on the first date SIP investment of each month across multiple indices. + The output DataFrame are saved to an Excel file, where the cells with + the best performance among indices for each year are highlighted in green-yellow, + and those with the worst performance are highlighted in sandy brown. Additionally, a scoring mechanism is implemented for the indices based on their growth values. For each year, indices are ranked in ascending order of growth, with the lowest value @@ -909,7 +909,7 @@ def sip_growth_comparison_across_indices( Parameters ---------- indices : list - A list of index names to compare in the SIP growth. + A list of index names to compare in the monthly SIP XIRR (%) and growth multiple (X). folder_path : str Path to the directory containing Excel files with historical data for each index. Each Excel file must be @@ -963,26 +963,28 @@ def sip_growth_comparison_across_indices( dataframes = [ df[df['Year'] <= common_year] for df in dataframes ] - dataframes = [ + + # growth DataFrames + growth_dataframes = [ df.drop(columns=['Invest', 'Value', 'XIRR (%)']) for df in dataframes ] - dataframes = [ - df.rename(columns={'Multiple (X)': f'{index} (X)'}) for df, index in zip(dataframes, indices) + growth_dataframes = [ + df.rename(columns={'Multiple (X)': f'{index} (X)'}) for df, index in zip(growth_dataframes, indices) ] - # mergeing the DataFrames - merged_df = dataframes[0] - common_cols = list(merged_df.columns)[:-1] - for df in dataframes[1:]: - merged_df = pandas.merge( - left=merged_df, + # merging the growth DataFrames + mgrowth_df = growth_dataframes[0] + common_cols = list(mgrowth_df.columns)[:-1] + for df in growth_dataframes[1:]: + mgrowth_df = pandas.merge( + left=mgrowth_df, right=df, on=common_cols, how='inner' ) # assing score to indices growth returns - score_df = merged_df.copy() + score_df = mgrowth_df.copy() score_df = score_df.iloc[:, len(common_cols):] for idx, row in score_df.iterrows(): sort_growth = row.sort_values(ascending=True).index @@ -995,17 +997,42 @@ def sip_growth_comparison_across_indices( aggregate_df['Index Name'] = aggregate_df['Index Name'].apply(lambda x: x.replace(' (X)', '')) # rounding of column values to catch exact maximum and minimum with floating point precision - for col in merged_df.columns: + for col in mgrowth_df.columns: if col.endswith('(X)'): - merged_df[col] = merged_df[col].round(5) + mgrowth_df[col] = mgrowth_df[col].round(5) + else: + pass + + # XIRR DataFrames + xirr_dataframes = [ + df.drop(columns=['Invest', 'Value', 'Multiple (X)']) for df in dataframes + ] + xirr_dataframes = [ + df.rename(columns={'XIRR (%)': f'{index} (XIRR)'}) for df, index in zip(xirr_dataframes, indices) + ] + + # mergeing the XIRR DataFrames + mxirr_df = xirr_dataframes[0] + for df in xirr_dataframes[1:]: + mxirr_df = pandas.merge( + left=mxirr_df, + right=df, + on=common_cols, + how='inner' + ) + + # rounding of column values to catch exact maximum and minimum with floating point precision + for col in mxirr_df.columns: + if col.endswith('(XIRR)'): + mxirr_df[col] = mxirr_df[col].round(5) else: pass # saving DataFrames with pandas.ExcelWriter(excel_file, engine='xlsxwriter') as excel_writer: - ################## - # merged DataFrame - merged_df.to_excel( + ########################## + # merged growth DataFrames + mgrowth_df.to_excel( excel_writer=excel_writer, index=False, sheet_name='Multiple(X)' @@ -1016,7 +1043,56 @@ def sip_growth_comparison_across_indices( 0, len(common_cols) - 1, 15 ) worksheet.set_column( - len(common_cols), merged_df.shape[1] - 1, 15, + len(common_cols), mgrowth_df.shape[1] - 1, 15, + workbook.add_format({'num_format': '#,##0.0'}) + ) + # header formatting + header_format = workbook.add_format( + { + 'bold': True, + 'text_wrap': True, + 'align': 'center', + 'valign': 'vcenter' + } + ) + for col_num, col_df in enumerate(mgrowth_df.columns): + worksheet.write(0, col_num, col_df, header_format) + # formatting for maximum and minimum value in each row + for row in range(mgrowth_df.shape[0]): + # minimum value + worksheet.conditional_format( + row + 1, len(common_cols), row + 1, mgrowth_df.shape[1] - 1, + { + 'type': 'cell', + 'criteria': 'equal to', + 'value': mgrowth_df.iloc[row, len(common_cols):].min(), + 'format': workbook.add_format({'bg_color': '#F4A460'}) + } + ) + # maximim value + worksheet.conditional_format( + row + 1, len(common_cols), row + 1, mgrowth_df.shape[1] - 1, + { + 'type': 'cell', + 'criteria': 'equal to', + 'value': mgrowth_df.iloc[row, len(common_cols):].max(), + 'format': workbook.add_format({'bg_color': '#ADFF2F'}) + } + ) + ######################## + # merged XIRR DataFrames + mxirr_df.to_excel( + excel_writer=excel_writer, + index=False, + sheet_name='XIRR(%)' + ) + workbook = excel_writer.book + worksheet = excel_writer.sheets['XIRR(%)'] + worksheet.set_column( + 0, len(common_cols) - 1, 15 + ) + worksheet.set_column( + len(common_cols), mxirr_df.shape[1] - 1, 15, workbook.add_format({'num_format': '#,##0.0'}) ) # header formatting @@ -1028,27 +1104,27 @@ def sip_growth_comparison_across_indices( 'valign': 'vcenter' } ) - for col_num, col_df in enumerate(merged_df.columns): + for col_num, col_df in enumerate(mxirr_df.columns): worksheet.write(0, col_num, col_df, header_format) # formatting for maximum and minimum value in each row - for row in range(merged_df.shape[0]): + for row in range(mxirr_df.shape[0]): # minimum value worksheet.conditional_format( - row + 1, len(common_cols), row + 1, merged_df.shape[1] - 1, + row + 1, len(common_cols), row + 1, mxirr_df.shape[1] - 1, { 'type': 'cell', 'criteria': 'equal to', - 'value': merged_df.iloc[row, len(common_cols):].min(), + 'value': mxirr_df.iloc[row, len(common_cols):].min(), 'format': workbook.add_format({'bg_color': '#F4A460'}) } ) # maximim value worksheet.conditional_format( - row + 1, len(common_cols), row + 1, merged_df.shape[1] - 1, + row + 1, len(common_cols), row + 1, mxirr_df.shape[1] - 1, { 'type': 'cell', 'criteria': 'equal to', - 'value': merged_df.iloc[row, len(common_cols):].max(), + 'value': mxirr_df.iloc[row, len(common_cols):].max(), 'format': workbook.add_format({'bg_color': '#ADFF2F'}) } ) @@ -1066,7 +1142,101 @@ def sip_growth_comparison_across_indices( return aggregate_df - def sip_xirr_comparison_across_indices( + def yearwise_cagr_analysis( + self, + input_excel: str, + output_excel: str + ) -> pandas.DataFrame: + + ''' + Calculates the year-wise CAGR (%) for a given index. Here, year-wise refers to the + CAGR calculated for periods ending on the present date, going back one year, two years, + three years, and so on, up to the available data range. + + Parameters + ---------- + input_excel : str + Path to the Excel file obtained from :meth:`BharatFinTrack.NSETRI.download_historical_daily_data` + and :meth:`BharatFinTrack.NSETRI.update_historical_daily_data` methods. + + output_excel : str + Path to an Excel file to save the output DataFrame. + + Returns + ------- + DataFrame + A DataFrame containing the year-wise CAGR (%) and Growth (X) for any amount. + ''' + + # check the Excel file extension first + excel_ext = Core()._excel_file_extension(output_excel) + if excel_ext == '.xlsx': + pass + else: + raise Exception(f'Input file extension "{excel_ext}" does not match the required ".xlsx".') + + # input DataFrame + df = pandas.read_excel(input_excel) + df['Date'] = df['Date'].apply(lambda x: x.date()) + + # start and end dates + start_date = df['Date'].min() + end_date = df['Date'].max() + + # date difference + date_diff = dateutil.relativedelta.relativedelta(end_date, start_date) + year_diff = date_diff.years + + # CAGR DataFrame + cagr_df = pandas.DataFrame() + for idx in range(year_diff + 1): + if idx < year_diff: + cagr_year = idx + 1 + cagr_start = end_date.replace(year=end_date.year - cagr_year) + while True: + if df['Date'].isin([cagr_start]).any(): + break + else: + cagr_start = cagr_start - datetime.timedelta(days=1) + yi_df = df[df['Date'].isin([cagr_start])] + else: + cagr_year = round(year_diff + (end_date.replace(year=end_date.year - year_diff) - start_date).days / 365, 1) + yi_df = df.iloc[:1, :] + # year-wise CAGR summary + cagr_df.loc[idx, 'Year'] = cagr_year + cagr_df.loc[idx, 'Start Date'] = yi_df.iloc[0, 0] + cagr_df.loc[idx, 'Start Value'] = yi_df.iloc[0, 1] + cagr_df.loc[idx, 'Close Date'] = end_date + cagr_df.loc[idx, 'Close Value'] = df.iloc[-1, -1] + cagr_df.loc[idx, 'CAGR (%)'] = 100 * (pow(df.iloc[-1, -1] / yi_df.iloc[0, 1], 1 / cagr_year) - 1) + cagr_df.loc[idx, 'Multiple (X)'] = cagr_df.loc[idx, 'Close Value'] / cagr_df.loc[idx, 'Start Value'] + + cagr_df['Cumulative (X)'] = cagr_df['Multiple (X)'].cumsum() + + # drop duplicates row if any + cagr_df = cagr_df.drop_duplicates(ignore_index=True) + + # saving DataFrame + with pandas.ExcelWriter(output_excel, engine='xlsxwriter') as excel_writer: + cagr_df.to_excel(excel_writer, index=False) + workbook = excel_writer.book + worksheet = excel_writer.sheets['Sheet1'] + # format columns + for col_num, col_df in enumerate(cagr_df.columns): + if any(col_df.endswith(i) for i in ['(%)', 'Year', '(X)', 'Value']): + worksheet.set_column( + col_num, col_num, 15, + workbook.add_format({'num_format': '#,##0.0'}) + ) + else: + worksheet.set_column( + col_num, col_num, 15, + workbook.add_format({'num_format': '#,##0'}) + ) + + return cagr_df + + def yearwise_cagr_growth_comparison_across_indices( self, indices: list[str], folder_path: str, @@ -1074,14 +1244,15 @@ def sip_xirr_comparison_across_indices( ) -> pandas.DataFrame: ''' - Generates a DataFrame that compares XIRR (%) of SIP growth on the - first date of each month across multiple indices over the years. - The output DataFrame is saved to an Excel file, where the cells with - the highest XIRR (%) among indices for each year are highlighted in green-yellow, - and those with the lowest XIRR (%) are highlighted in sandy brown. - - Additionally, a scoring mechanism is implemented for the indices based on their XIRR (%) values. - For each year, indices are ranked in ascending order of XIRR (%), with the lowest value + Generates two DataFrames that compare year-wise CAGR (%) and growth multiple (X) + of a yearly fixed investment across multiple indices. Here, year-wise refers to the + CAGR calculated for periods ending on the present date, going back one year, two years, and so on, + up to the available data range. The output DataFrames are saved to an Excel file, + where the cells with the best performance among indices for each year are highlighted + in green-yellow, and those with the worst performance are highlighted in sandy brown. + + Additionally, a scoring mechanism is implemented for the indices based on their growth values. + For each year, indices are ranked in ascending order of growth, with the lowest value receiving the lowest score (1), and the highest value receiving the highest score. The total scores for each index are calculated by summing their yearly scores. Indices are then sorted in descending order based on their total scores, @@ -1090,7 +1261,7 @@ def sip_xirr_comparison_across_indices( Parameters ---------- indices : list - A list of index names to compare in the SIP XIRR (%). + A list of index names to compare in the CAGR (%) and growth multiple (X). folder_path : str Path to the directory containing Excel files with historical data for each index. Each Excel file must be @@ -1114,17 +1285,13 @@ def sip_xirr_comparison_across_indices( else: raise Exception(f'Input file extension "{excel_ext}" does not match the required ".xlsx".') - # monthly investment amount - monthly_invest = 1000 - # SIP dataframe of index dataframes = [] with tempfile.TemporaryDirectory() as tmp_dir: for index in indices: index_excel = os.path.join(folder_path, f'{index}.xlsx') - df = NSETRI().yearwise_sip_analysis( + df = NSETRI().yearwise_cagr_analysis( input_excel=index_excel, - monthly_invest=monthly_invest, output_excel=os.path.join(tmp_dir, 'output.xlsx') ) dataframes.append(df) @@ -1144,26 +1311,28 @@ def sip_xirr_comparison_across_indices( dataframes = [ df[df['Year'] <= common_year] for df in dataframes ] - dataframes = [ - df.drop(columns=['Invest', 'Value', 'Multiple (X)']) for df in dataframes + + # Growth DataFrames + growth_dataframes = [ + df.drop(columns=['Start Value', 'Close Value', 'CAGR (%)', 'Cumulative (X)']) for df in dataframes ] - dataframes = [ - df.rename(columns={'XIRR (%)': f'{index} (XIRR)'}) for df, index in zip(dataframes, indices) + growth_dataframes = [ + df.rename(columns={'Multiple (X)': f'{index} (X)'}) for df, index in zip(growth_dataframes, indices) ] - # mergeing the DataFrames - merged_df = dataframes[0] - common_cols = list(merged_df.columns)[:-1] - for df in dataframes[1:]: - merged_df = pandas.merge( - left=merged_df, + # merging the growth DataFrames + mgrowth_df = growth_dataframes[0] + common_cols = list(mgrowth_df.columns)[:-1] + for df in growth_dataframes[1:]: + mgrowth_df = pandas.merge( + left=mgrowth_df, right=df, on=common_cols, how='inner' ) # assing score to indices growth returns - score_df = merged_df.copy() + score_df = mgrowth_df.copy() score_df = score_df.iloc[:, len(common_cols):] for idx, row in score_df.iterrows(): sort_growth = row.sort_values(ascending=True).index @@ -1173,31 +1342,105 @@ def sip_xirr_comparison_across_indices( # aggregate DataFrame of sorted total score aggregate_df = score_df.sum().sort_values(ascending=False).reset_index() aggregate_df.columns = ['Index Name', 'Score'] - aggregate_df['Index Name'] = aggregate_df['Index Name'].apply(lambda x: x.replace(' (XIRR)', '')) + aggregate_df['Index Name'] = aggregate_df['Index Name'].apply(lambda x: x.replace(' (X)', '')) # rounding of column values to catch exact maximum and minimum with floating point precision - for col in merged_df.columns: - if col.endswith('(XIRR)'): - merged_df[col] = merged_df[col].round(5) + for col in mgrowth_df.columns: + if col.endswith('(X)'): + mgrowth_df[col] = mgrowth_df[col].round(5) + else: + pass + + # CAGR DataFrames + cagr_dataframes = [ + df.drop(columns=['Start Value', 'Close Value', 'Multiple (X)', 'Cumulative (X)']) for df in dataframes + ] + cagr_dataframes = [ + df.rename(columns={'CAGR (%)': f'{index} (CAGR)'}) for df, index in zip(cagr_dataframes, indices) + ] + + # mergeing the CAGR DataFrames + mcagr_df = cagr_dataframes[0] + for df in cagr_dataframes[1:]: + mcagr_df = pandas.merge( + left=mcagr_df, + right=df, + on=common_cols, + how='inner' + ) + + # rounding of column values to catch exact maximum and minimum with floating point precision + for col in mcagr_df.columns: + if col.endswith('(CAGR)'): + mcagr_df[col] = mcagr_df[col].round(5) else: pass # saving DataFrames with pandas.ExcelWriter(excel_file, engine='xlsxwriter') as excel_writer: - ################## - # merged DataFrame - merged_df.to_excel( + ########################## + # merged growth DataFrames + mgrowth_df.to_excel( excel_writer=excel_writer, index=False, - sheet_name='XIRR(%)' + sheet_name='Multiple(X)' ) workbook = excel_writer.book - worksheet = excel_writer.sheets['XIRR(%)'] + worksheet = excel_writer.sheets['Multiple(X)'] + worksheet.set_column( + 0, len(common_cols) - 1, 15 + ) + worksheet.set_column( + len(common_cols), mgrowth_df.shape[1] - 1, 15, + workbook.add_format({'num_format': '#,##0.0'}) + ) + # header formatting + header_format = workbook.add_format( + { + 'bold': True, + 'text_wrap': True, + 'align': 'center', + 'valign': 'vcenter' + } + ) + for col_num, col_df in enumerate(mgrowth_df.columns): + worksheet.write(0, col_num, col_df, header_format) + # formatting for maximum and minimum value in each row + for row in range(mgrowth_df.shape[0]): + # minimum value + worksheet.conditional_format( + row + 1, len(common_cols), row + 1, mgrowth_df.shape[1] - 1, + { + 'type': 'cell', + 'criteria': 'equal to', + 'value': mgrowth_df.iloc[row, len(common_cols):].min(), + 'format': workbook.add_format({'bg_color': '#F4A460'}) + } + ) + # maximim value + worksheet.conditional_format( + row + 1, len(common_cols), row + 1, mgrowth_df.shape[1] - 1, + { + 'type': 'cell', + 'criteria': 'equal to', + 'value': mgrowth_df.iloc[row, len(common_cols):].max(), + 'format': workbook.add_format({'bg_color': '#ADFF2F'}) + } + ) + ######################## + # merged CAGR DataFrames + mcagr_df.to_excel( + excel_writer=excel_writer, + index=False, + sheet_name='CAGR(%)' + ) + workbook = excel_writer.book + worksheet = excel_writer.sheets['CAGR(%)'] worksheet.set_column( 0, len(common_cols) - 1, 15 ) worksheet.set_column( - len(common_cols), merged_df.shape[1] - 1, 15, + len(common_cols), mcagr_df.shape[1] - 1, 15, workbook.add_format({'num_format': '#,##0.0'}) ) # header formatting @@ -1209,27 +1452,27 @@ def sip_xirr_comparison_across_indices( 'valign': 'vcenter' } ) - for col_num, col_df in enumerate(merged_df.columns): + for col_num, col_df in enumerate(mcagr_df.columns): worksheet.write(0, col_num, col_df, header_format) # formatting for maximum and minimum value in each row - for row in range(merged_df.shape[0]): + for row in range(mcagr_df.shape[0]): # minimum value worksheet.conditional_format( - row + 1, len(common_cols), row + 1, merged_df.shape[1] - 1, + row + 1, len(common_cols), row + 1, mcagr_df.shape[1] - 1, { 'type': 'cell', 'criteria': 'equal to', - 'value': merged_df.iloc[row, len(common_cols):].min(), + 'value': mcagr_df.iloc[row, len(common_cols):].min(), 'format': workbook.add_format({'bg_color': '#F4A460'}) } ) # maximim value worksheet.conditional_format( - row + 1, len(common_cols), row + 1, merged_df.shape[1] - 1, + row + 1, len(common_cols), row + 1, mcagr_df.shape[1] - 1, { 'type': 'cell', 'criteria': 'equal to', - 'value': merged_df.iloc[row, len(common_cols):].max(), + 'value': mcagr_df.iloc[row, len(common_cols):].max(), 'format': workbook.add_format({'bg_color': '#ADFF2F'}) } ) diff --git a/README.md b/README.md index bf2aa23..9c51e9c 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,13 @@ * Compare CAGR (%) between `Price` and `TRI`. * Sorts NSE equity indices by CAGR (%) values. * Sorts NSE equity indices by CAGR (%) within each category. +* Compares the year-wise CAGR (%) and growth of a fixed yearly investment across multiple NSE equity `TRI` indices. ## Systematic Investment Plan (SIP) * Computes the year-wise SIP return for a fixed monthly contribution to a specified NSE equity `TRI` index. * Calculates the closing summary of an SIP with a fixed monthly contribution to a specified NSE equity `TRI` index, starting from a given date. -* Compares the growth of a monthly SIP investment across multiple NSE equity `TRI` indices over the years. +* Compares the year-wise XIRR (%) and growth of a fixed monthly SIP investment across multiple NSE equity `TRI` indices. * Estimates annual SIP performance, including investment amount, closing balance, and cumulative growth over a specified number of years and expected annual return, with options for yearly, quarterly, monthly, or weekly contributions. diff --git a/docs/functionality.rst b/docs/functionality.rst index 0fa048e..b36a961 100644 --- a/docs/functionality.rst +++ b/docs/functionality.rst @@ -92,9 +92,9 @@ This method shows users the differences in CAGR between the `Price` and `TRI` of ) -SIP Growth ------------- -Computes the year-wise SIP return for a fixed monthly contribution to a specified NSE equity `TRI` index. The data required to compute the SIP must be sourced from the Excel file generated in the :ref:`Total Return Index (TRI) ` section. +Year-wise SIP Growth +---------------------- +Computes the year-wise SIP return for a fixed monthly contribution to a specified NSE equity `TRI` index. The data required to compute the SIP must be sourced from the Excel file generated in the :ref:`Total Return Index (TRI) ` section. .. code-block:: python @@ -106,11 +106,27 @@ Computes the year-wise SIP return for a fixed monthly contribution to a specifie ) -SIP Comparison Across Indices -------------------------------- + +SIP Calculator +---------------- +Estimates the SIP growth over a specified number of years for a fixed investment amount. -Comparing the growth multiples (X) of a monthly SIP investment across `TRI` indices, including popular indices such as NIFTY 50 and NIFTY 500, as well as several other top-performing NSE equity indices over the years. The data required for SIP calculations must be sourced from the Excel files generated in the :ref:`Total Return Index (TRI) ` section. Ensure that all Excel files are stored in the designated input folder, with each file named as `{index}.xlsx` to correspond to the index names provided in the list of indices. The output DataFrame is saved to an Excel file, where the cells with the highest growth among indices for each year are highlighted in green-yellow, and those with the lowest growth are highlighted in sandy brown. +.. code-block:: python + + core.sip_growth( + invest=1000, + frequency='monthly', + annual_return=15, + years=20 + ) + + +Year-wise SIP and CAGR Comparison Across Indices +-------------------------------------------------- +This section compares the year-wise XIRR (%) and growth multiples (X) of a fixed monthly SIP investment, along with the year-wise CAGR (%) and growth multiples of a fixed yearly investment across selected `TRI` indices, including the popular `NIFTY 50` and other top-performing NSE equity indices. + +The required data are sourced from Excel files generated in the :ref:`Total Return Index (TRI) ` section. Ensure that all input Excel files are stored in the designated folder, with each file named as `{index}.xlsx` to correspond to the index names provided in the list. The output highlights the highest growth cells in green-yellow and the lowest growth cells in sandy brown. .. code-block:: python @@ -121,25 +137,14 @@ Comparing the growth multiples (X) of a monthly SIP investment across `TRI` indi 'NIFTY500 MOMENTUM 50' ] - nse_tri.sip_growth_comparison_across_indices( + nse_tri.yearwise_sip_xirr_growth_comparison_across_indices( indices=index_list folder_path=r"C:\Users\Username\Folder", - excel_file=r"C:\Users\Username\Folder\sip_invest_growth_across_indices.xlsx" - ) - - - -SIP Calculator ----------------- -Estimates the SIP growth over a specified number of years for a fixed investment amount. - - -.. code-block:: python - - core.sip_growth( - invest=1000, - frequency='monthly', - annual_return=15, - years=20 + excel_file=r"C:\Users\Username\Folder\yearwise_sip_xirr_growth_across_indices.xlsx" ) + nse_tri.yearwise_cagr_growth_comparison_across_indices( + indices=index_list + folder_path=r"C:\Users\Username\Folder", + excel_file=r"C:\Users\Username\Folder\yearwise_cagr_growth_across_indices.xlsx" + ) \ No newline at end of file diff --git a/docs/introduction.rst b/docs/introduction.rst index 1603587..24c7724 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -22,13 +22,14 @@ Compound Annual Growth Rate (CAGR) * Compare CAGR (%) between `Price` and `TRI`. * Sorts NSE equity indices by CAGR (%) values. * Sorts NSE equity indices by CAGR (%) within each category. +* Compares the year-wise CAGR (%) and growth of a fixed yearly investment across multiple NSE equity `TRI` indices. Systematic Investment Plan (SIP) ---------------------------------- * Computes the year-wise SIP return for a fixed monthly contribution to a specified NSE equity `TRI` index. * Calculates the closing summary of an SIP with a fixed monthly contribution to a specified NSE equity `TRI` index, starting from a given date. -* Compares the growth of a monthly SIP investment across multiple NSE equity `TRI` indices over the years. +* Compares the year-wise XIRR (%) and growth of a fixed monthly SIP investment across multiple NSE equity `TRI` indices. * Estimates annual SIP performance, including investment amount, closing balance, and cumulative growth over a specified number of years and expected annual return, with options for yearly, quarterly, monthly, or weekly contributions. diff --git a/docs/visualization.rst b/docs/visualization.rst index f1f9fe7..afce755 100644 --- a/docs/visualization.rst +++ b/docs/visualization.rst @@ -57,7 +57,7 @@ The resulting plot will resemble the example shown below. SIP Comparison with Government Securities ------------------------------------------- -A bar plot displays the comparison of investments and returns over the years for the `TRI` data of the `NIFTY MIDCAP150 MOMENTUM 50` index and government secutity bond with an assumed coupon rate. Data required to compute the SIP must be sourced from the Excel file generated in the :ref:`Total Return Index (TRI) ` section. +A bar plot displays the comparison of investments and returns over the years for the `TRI` data of the `NIFTY MIDCAP150 MOMENTUM 50` index and government bond with an assumed coupon rate. Data required to compute the SIP must be sourced from the Excel file generated in the :ref:`Total Return Index (TRI) ` section. .. code-block:: python @@ -79,7 +79,7 @@ The resulting plot will look similar to the example below. SIP Comparison Across Indices ------------------------------- -A plot comparing the growth multiples (X) of a monthly SIP investment across `TRI` indices, including popular indices such as NIFTY 50 and NIFTY 500, as well as several other top-performing NSE equity indices over the years. The data required for SIP calculations must be sourced from the Excel files generated in the :ref:`Total Return Index (TRI) ` section. Ensure that all Excel files are stored in the designated input folder, with each file named as `{index}.xlsx` to correspond to the index names provided in the list of indices. +A plot comparing the year-wise growth multiples (X) of a monthly SIP investment across `TRI` indices, including the popular `NIFTY 50` and other top-performing NSE equity indices over the years. The data required for SIP calculations must be sourced from the Excel files generated in the :ref:`Total Return Index (TRI) ` section. Ensure that all Excel files are stored in the designated input folder, with each file named as `{index}.xlsx` to correspond to the index names provided in the list of indices. diff --git a/tests/test_bharatfintrack.py b/tests/test_bharatfintrack.py index bdb6231..ef2173b 100644 --- a/tests/test_bharatfintrack.py +++ b/tests/test_bharatfintrack.py @@ -431,7 +431,7 @@ def test_update_historical_daily_data( assert len_df2 > len_df1 -def test_sip( +def test_sip_cagr( nse_tri, visual ): @@ -450,10 +450,17 @@ def test_sip( sip_df = nse_tri.yearwise_sip_analysis( input_excel=input_excel, monthly_invest=1000, - output_excel=os.path.join(tmp_dir, 'output.xlsx') + output_excel=os.path.join(tmp_dir, 'yearwise_sip.xlsx') ) assert float(round(sip_df.iloc[-1, -1], 1)) == 24.0 assert float(round(sip_df.iloc[-1, -2], 1)) == 1.3 + # pass test for yearwise CAGR analysis + cagr_df = nse_tri.yearwise_cagr_analysis( + input_excel=input_excel, + output_excel=os.path.join(tmp_dir, 'yearwise_cagr.xlsx') + ) + assert float(round(cagr_df.iloc[0, -3], 1)) == 28.4 + assert float(round(cagr_df.iloc[-1, -3], 1)) == 21.6 # pass test for SIP analysis from a fixed date sip_summary = nse_tri.sip_summary_from_given_date( excel_file=input_excel, @@ -507,20 +514,20 @@ def test_sip( start_date='15-Oct-2022', end_date='15-Oct-2024' ) - # SIP growth comparison - aggregate_df = nse_tri.sip_growth_comparison_across_indices( + # pass test for yearwise monthly SIP XIRR(%) and growth comparison across indices + sipaggregate_df = nse_tri.yearwise_sip_xirr_growth_comparison_across_indices( indices=[index, index_1], folder_path=tmp_dir, - excel_file=os.path.join(tmp_dir, 'compare_sip_growth_across_indices.xlsx') + excel_file=os.path.join(tmp_dir, 'compare_yearwise_sip_xirr_across_indices.xlsx') ) - assert len(aggregate_df.columns) == 2 - # SIP XIRR comparison - aggregate_df = nse_tri.sip_xirr_comparison_across_indices( + assert len(sipaggregate_df.columns) == 2 + # pass test for yearwise CAGR(%) and growth comparison across indices + cagraggregate_df = nse_tri.yearwise_cagr_growth_comparison_across_indices( indices=[index, index_1], folder_path=tmp_dir, - excel_file=os.path.join(tmp_dir, 'compare_sip_xirr_across_indices.xlsx') + excel_file=os.path.join(tmp_dir, 'compare_yearwise_cagr_across_indices.xlsx') ) - assert len(aggregate_df.columns) == 2 + assert len(cagraggregate_df.columns) == 2 # error test for unequal end date of two indices nse_tri.download_historical_daily_data( index='NIFTY ALPHA 50', @@ -535,20 +542,20 @@ def test_sip( figure_file=figure_file ) assert exc_info.value.args[0] == 'Last date must be equal across all indices in the Excel files.' - # SIP growth comparison + # error test of unequal last date for yearwise SIP XIRR(%) and growth comparison across indicess with pytest.raises(Exception) as exc_info: - nse_tri.sip_growth_comparison_across_indices( + nse_tri.yearwise_sip_xirr_growth_comparison_across_indices( indices=['NIFTY 50', 'NIFTY ALPHA 50'], folder_path=tmp_dir, - excel_file=os.path.join(tmp_dir, 'comare_sip_growth_across_indices.xlsx') + excel_file=os.path.join(tmp_dir, 'compare_yearwise_sip_xirr_across_indices.xlsx') ) assert exc_info.value.args[0] == 'Last date must be equal across all indices in the Excel files.' - # SIP XIRR comparison + # error test of unequal last date for yearwise CAGR(%) and growth comparison across indicess with pytest.raises(Exception) as exc_info: - nse_tri.sip_xirr_comparison_across_indices( + nse_tri.yearwise_cagr_growth_comparison_across_indices( indices=['NIFTY 50', 'NIFTY ALPHA 50'], folder_path=tmp_dir, - excel_file=os.path.join(tmp_dir, 'compare_sip_xirr_across_indices.xlsx') + excel_file=os.path.join(tmp_dir, 'compare_yearwise_cagr_across_indices.xlsx') ) assert exc_info.value.args[0] == 'Last date must be equal across all indices in the Excel files.' # error test for invalid input of year and month @@ -647,7 +654,14 @@ def test_error_excel( assert exc_info.value.args[0] == message['error_excel'] with pytest.raises(Exception) as exc_info: - nse_tri.sip_growth_comparison_across_indices( + nse_tri.yearwise_cagr_analysis( + input_excel='input.xlsx', + output_excel='output.xl' + ) + assert exc_info.value.args[0] == message['error_excel'] + + with pytest.raises(Exception) as exc_info: + nse_tri.yearwise_sip_xirr_growth_comparison_across_indices( indices=['NIFTY 50'], folder_path=r"C:\Users\Username\Folder", excel_file='output.xl' @@ -655,7 +669,7 @@ def test_error_excel( assert exc_info.value.args[0] == message['error_excel'] with pytest.raises(Exception) as exc_info: - nse_tri.sip_xirr_comparison_across_indices( + nse_tri.yearwise_cagr_growth_comparison_across_indices( indices=['NIFTY 50'], folder_path=r"C:\Users\Username\Folder", excel_file='output.xl'