import os
import typing
import pandas
import matplotlib.pyplot
import matplotlib.figure
from .helper import Helper
from .sip import SIP
[docs]
class Visual:
'''
Provides functionality for visualizing data.
'''
def _validate_figure_file(
self,
figure_file: str
) -> None:
'''
Validate the direction and extension of given figure file.
'''
# Directory path and file name
dir_path, file_name = os.path.split(figure_file)
# Validate directory path
if not os.path.isdir(dir_path):
raise NotADirectoryError(
f'Invalid directory path "{str(dir_path)}" for the figure file'
)
# Create figure
figure = matplotlib.pyplot.figure(
figsize=(1, 1)
)
# Check figure file extension
fig_ext = os.path.splitext(file_name)[-1][1:]
if fig_ext not in list(figure.canvas.get_supported_filetypes().keys()):
raise TypeError(
f'Input figure_file extension ".{fig_ext}" is not supported for saving the figure'
)
matplotlib.pyplot.close(figure)
return None
[docs]
def cagr_leaders_by_category(
self,
excel_file: str,
figure_file: str,
number_leader: int = 5,
fig_width: int | float = 15,
fig_height: int | float = 10,
fig_title: typing.Optional[str] = None,
fig_ylabel: typing.Optional[str] = None,
gui_window: bool = True
) -> matplotlib.figure.Figure:
'''
Generate a bar plot of top-performing securities by CAGR (%) within each category since launch.
Parameters
----------
excel_file : str
Path to the input Excel file generated by :meth:`BharatFinTrack.CAGR.sort_since_inception`
with ``within_category=True``.
figure_file : str
File Path to save the output figure.
number_leader : int, optional
Number of top indices to display per category based on CAGR (%). Default is 5.
fig_width : float, optional
Width of the figure in inches. Default is 15.
fig_height : float, optional
Height of the figure in inches. Default is 10.
fig_title : str, optional
Title of the figure. Default is None.
fig_ylabel : str, optional
Label for the y-axis.. Default is None.
gui_window : bool, optional
If True (default), open a graphical user interface window for the plot.
Returns
-------
Figure
Bar plot showing the top CAGR (%) securities within each category.
'''
# Check static type of input variable origin
Helper()._validate_variable_origin_static_type(
vars_types=typing.get_type_hints(
obj=self.cagr_leaders_by_category
),
vars_values=locals()
)
# Check validity of figure file
self._validate_figure_file(
figure_file=figure_file
)
# Input DataFrame
df = pandas.read_excel(
io=excel_file
)
group_df = df.groupby(by='Category').head(number_leader)
# Category of indices
categories = group_df['Category'].unique()
# Color for category
colormap = matplotlib.colormaps.get_cmap('Set2')
category_color = {
categories[count]: colormap(count / len(categories)) for count in range(len(categories))
}
# Create figure
figure = matplotlib.pyplot.figure(
figsize=(fig_width, fig_height)
)
subplot = figure.subplots(1, 1)
# Plotting indices CAGR(%) values
categories_legend = set()
for count, (index, row) in enumerate(group_df.iterrows()):
category = row['Category']
color = category_color[category]
if category not in categories_legend:
subplot.barh(
row['Index Name'], row['CAGR(%)'],
color=color,
label=category
)
categories_legend.add(category)
else:
subplot.barh(
row['Index Name'], row['CAGR(%)'],
color=color
)
subplot.text(
row['CAGR(%)'], count, row['Y-M-D'],
va='center',
fontsize=8
)
# X-axis customization
xtick_gap = 5
xaxis_max = int((group_df['CAGR(%)'].max() / xtick_gap) + 2) * xtick_gap
subplot.set_xlim(0, xaxis_max)
xticks = range(0, xaxis_max + 1, xtick_gap)
subplot.set_xticks(
ticks=xticks
)
subplot.set_xticklabels(
labels=[
str(val) for val in xticks
],
fontsize=12
)
subplot.tick_params(
axis='x', which='both',
direction='in', length=6, width=1,
top=True, bottom=True,
labeltop=True, labelbottom=True
)
subplot.grid(
visible=True,
which='major', axis='x',
color='gray',
linestyle='--', linewidth=0.3
)
close_date = group_df['Close Date'].iloc[0].strftime(Helper()._date_str_fmt)
subplot.set_xlabel(
f'CAGR (%) since launch (as of {close_date})',
fontsize=12,
labelpad=15
)
# Y-axis customization
subplot.set_ylim(len(group_df), -1)
subplot.tick_params(
axis='y',
labelsize=10
)
if fig_ylabel is not None:
subplot.set_ylabel(
fig_ylabel,
fontsize=12,
labelpad=15
)
# Legend
subplot.legend(
title='Category',
loc='lower right',
fontsize=12,
title_fontsize=12
)
# Figure title
if fig_title is not None:
figure.suptitle(
fig_title,
fontsize=15,
y=1
)
# Saving figure
figure.tight_layout()
figure.savefig(
figure_file,
bbox_inches='tight'
)
# Close the figure to prevent duplicate plots from displaying
matplotlib.pyplot.show() if gui_window else None
matplotlib.pyplot.close(figure)
return figure
[docs]
def cagr_leaders(
self,
excel_file: str,
figure_file: str,
number_leader: int = 15,
fig_width: int | float = 15,
fig_height: int | float = 10,
fig_title: typing.Optional[str] = None,
fig_ylabel: typing.Optional[str] = None,
gui_window: bool = True
) -> matplotlib.figure.Figure:
'''
Generate a bar plot of top-performing securities by CAGR (%) since launch.
Parameters
----------
excel_file : str
Path to the input Excel file generated by :meth:`BharatFinTrack.CAGR.sort_since_inception`
with ``within_category=False``.
figure_file : str
File Path to save the output figure.
number_leader : int, optional
Number of top indices to display based on CAGR (%). Default is 15.
fig_width : float, optional
Width of the figure in inches. Default is 15.
fig_height : float, optional
Height of the figure in inches. Default is 10.
fig_title : str, optional
Title of the figure. Default is None.
fig_ylabel : str, optional
Label for the y-axis.. Default is None.
gui_window : bool, optional
If True (default), open a graphical user interface window for the plot.
Returns
-------
Figure
Bar plot showing the top CAGR (%) securities.
'''
# Check static type of input variable origin
Helper()._validate_variable_origin_static_type(
vars_types=typing.get_type_hints(
obj=self.cagr_leaders
),
vars_values=locals()
)
# Check validity of figure file
self._validate_figure_file(
figure_file=figure_file
)
# Input DataFrame
df = pandas.read_excel(
io=excel_file
).head(number_leader)
# Create figure
figure = matplotlib.pyplot.figure(
figsize=(fig_width, fig_height)
)
subplot = figure.subplots(1, 1)
# Plotting indices CAGR(%) values
for count, (index, row) in enumerate(df.iterrows()):
subplot.barh(
row['Index Name'], row['CAGR(%)'],
color='lawngreen'
)
subplot.text(
row['CAGR(%)'], count, row['Y-M-D'],
va='center',
fontsize=8
)
# X-axis customization
xtick_gap = 5
xaxis_max = int((df['CAGR(%)'].max() / xtick_gap) + 2) * xtick_gap
subplot.set_xlim(0, xaxis_max)
xticks = range(0, xaxis_max + 1, xtick_gap)
subplot.set_xticks(
ticks=xticks
)
subplot.set_xticklabels(
labels=[
str(val) for val in xticks
],
fontsize=12
)
subplot.tick_params(
axis='x', which='both',
direction='in', length=6, width=1,
top=True, bottom=True,
labeltop=True, labelbottom=True
)
subplot.grid(
visible=True,
which='major', axis='x',
color='gray',
linestyle='--', linewidth=0.3
)
close_date = df['Close Date'].iloc[0].strftime(Helper()._date_str_fmt)
subplot.set_xlabel(
f'CAGR (%) since launch (as of {close_date})',
fontsize=12,
labelpad=15
)
# Y-axis customization
subplot.set_ylim(len(df), -1)
subplot.tick_params(
axis='y',
labelsize=10
)
if fig_ylabel is not None:
subplot.set_ylabel(
fig_ylabel,
fontsize=12,
labelpad=15
)
# Figure title
if fig_title is not None:
figure.suptitle(
fig_title,
fontsize=15,
y=1
)
# Saving figure
figure.tight_layout()
figure.savefig(
figure_file,
bbox_inches='tight'
)
# Close the figure to prevent duplicate plots from displaying
matplotlib.pyplot.show() if gui_window else None
matplotlib.pyplot.close(figure)
return figure
[docs]
def compare_sip_to_bond_benchmark(
self,
excel_file: str,
figure_file: str,
bond_yield: int | float = 7,
fig_width: int | float = 15,
fig_height: int | float = 10,
fig_title: typing.Optional[str] = None,
ytick_gap: int = 200,
gui_window: bool = True,
asset_legend: str = 'Index growth'
) -> matplotlib.figure.Figure:
'''
Generate a bar plot comparing the SIP value of a specified security
against government bond over time, assuming a monthly
investment of 1 Rupee.
Parameters
----------
excel_file : str
Path to the input Excel file generated by :meth:`BharatFinTrack.SIP.yearly_return`.
figure_file : str
File Path to save the output figure.
bond_yield : float, optional
Expected annual yield (%) of government securities. Default is 7.
fig_width : float, optional
Width of the figure in inches. Default is 15.
fig_height : float, optional
Height of the figure in inches. Default is 10.
fig_title : str, optional
Title of the figure. Default is None.
ytick_gap : int, optional
Gap between two y-axis ticks. Default is 200.
gui_window : bool, optional
If True (default), open a graphical user interface window for the plot.
asset_legend : str, optional
Label used for the asset in the plot legend. Default is 'Index growth'.
Returns
-------
Figure
Bar plot comparing SIP values of the security and government bond over time.
'''
# Check static type of input variable origin
Helper()._validate_variable_origin_static_type(
vars_types=typing.get_type_hints(
obj=self.compare_sip_to_bond_benchmark
),
vars_values=locals()
)
# Check validity of figure file
self._validate_figure_file(
figure_file=figure_file
)
# SIP DataFRame
df = pandas.read_excel(
io=excel_file
)
sip_years = int(df['Year'].max())
df = df[df['Year'] <= sip_years].reset_index(drop=True)
monthly_sip = df['Investment'].iloc[0] / 12
df['Investment'] = df['Investment'] / monthly_sip
df['Close Value'] = df['Close Value'] / monthly_sip
# Government security growth DataFrame
gsec_df = SIP().investment_growth(
invest=1,
frequency='monthly',
annual_return=bond_yield,
years=sip_years
)
# Create figure
figure = matplotlib.pyplot.figure(
figsize=(fig_width, fig_height)
)
subplot = figure.subplots(1, 1)
# Plot SIP growth value
xticks = pandas.Series(
data=range(len(df))
)
bar_width = 0.3
subplot.bar(
x=xticks - bar_width,
height=df['Investment'],
width=bar_width,
label='Investment',
color='gold'
)
subplot.bar(
x=xticks,
height=gsec_df['Close Value'],
width=bar_width,
label='G-Sec growth',
color='cyan'
)
subplot.bar(
x=xticks + bar_width,
height=df['Close Value'],
width=bar_width,
label=asset_legend,
color='lightgreen'
)
for xt in xticks:
multiple = df['Multiple(X)'].iloc[xt]
xirr = df['XIRR(%)'].iloc[xt]
subplot.annotate(
f'{multiple:.1f}X\n{xirr:.1f}%',
xy=(xticks.iloc[xt] + bar_width, df['Close Value'].iloc[xt]),
ha='center', va='bottom',
fontsize=8
)
# X-axis customization
subplot.set_xticks(
ticks=xticks
)
xticklabels = list(
map(
lambda x: x[0].strftime(Helper()._date_str_fmt) + f' ({x[1]:.0f}Y)', zip(df['Start Date'], df['Year'])
)
)
subplot.set_xticklabels(
labels=xticklabels,
rotation=90,
fontsize=10
)
subplot.set_xlabel(
xlabel='Start Date',
fontsize=12
)
# Y-axis customization
df_max = df['Close Value'].max()
gsec_max = gsec_df['Close Value'].max()
yaxis_max = (round(max(df_max, gsec_max) / ytick_gap) + 2) * ytick_gap
subplot.set_ylim(0, yaxis_max)
yticks = range(0, yaxis_max + 1, ytick_gap)
subplot.set_yticks(
ticks=yticks
)
subplot.set_yticklabels(
labels=[
str(yt) for yt in yticks
],
fontsize=10
)
subplot.tick_params(
axis='y', which='both',
direction='in', length=6, width=1,
left=True, right=True,
labelleft=True
)
subplot.grid(
visible=True,
which='major', axis='y',
color='gray',
linestyle='--', linewidth=0.3
)
close_date = df['Close Date'].iloc[0].strftime(Helper()._date_str_fmt)
subplot.set_ylabel(
ylabel=f'Amount (as of {close_date})',
fontsize=12
)
# Legend
subplot.legend(
loc='upper left',
fontsize=12
)
# Figure title
if fig_title is not None:
figure.suptitle(
fig_title,
fontsize=15,
y=1
)
# Saving figure
figure.tight_layout()
figure.savefig(
figure_file,
bbox_inches='tight'
)
# Close the figure to prevent duplicate plots from displaying
matplotlib.pyplot.show() if gui_window else None
matplotlib.pyplot.close(figure)
return figure