vibhorag101
commited on
Commit
·
dfbd37e
1
Parent(s):
e84070c
Refactored the code, and added XIRR
Browse files- app.py +47 -136
- portfolio.py +83 -0
- returns.py +71 -0
- utils.py +37 -0
app.py
CHANGED
@@ -1,9 +1,16 @@
|
|
|
|
1 |
import gradio as gr
|
|
|
|
|
|
|
2 |
import pandas as pd
|
3 |
import plotly.graph_objects as go
|
4 |
-
|
5 |
-
import
|
6 |
-
import
|
|
|
|
|
|
|
7 |
|
8 |
js_func = """
|
9 |
function refresh() {
|
@@ -18,114 +25,38 @@ function refresh() {
|
|
18 |
|
19 |
locale.setlocale(locale.LC_MONETARY, 'en_IN')
|
20 |
|
21 |
-
|
22 |
-
# labels = list(schemes.keys())
|
23 |
-
# values = list(schemes.values())
|
24 |
-
|
25 |
-
# fig = go.Figure(data=[go.Pie(labels=labels, values=values)])
|
26 |
-
# fig.update_layout(title_text="Scheme Weightages")
|
27 |
-
# return fig
|
28 |
-
|
29 |
-
|
30 |
-
def get_nav_data(scheme_code):
|
31 |
-
url = f"https://api.mfapi.in/mf/{scheme_code}"
|
32 |
-
response = requests.get(url)
|
33 |
-
data = response.json()
|
34 |
-
df = pd.DataFrame(data['data'])
|
35 |
-
df['date'] = pd.to_datetime(df['date'], format='%d-%m-%Y')
|
36 |
-
df['nav'] = df['nav'].astype(float)
|
37 |
-
df = df.sort_values('date')
|
38 |
-
inception_date = df['date'].min()
|
39 |
-
return df, inception_date
|
40 |
-
|
41 |
-
def calculate_sip_returns(nav_data, sip_amount, upfront_amount, stepup, start_date, end_date, SIP_Date):
|
42 |
-
start_date = pd.Timestamp(start_date)
|
43 |
-
end_date = pd.Timestamp(end_date)
|
44 |
-
|
45 |
-
nav_data_filtered = nav_data[(nav_data['date'] >= start_date) & (nav_data['date'] <= end_date)].copy()
|
46 |
-
nav_data_filtered['date'] = pd.to_datetime(nav_data_filtered['date'])
|
47 |
-
if SIP_Date == 'start':
|
48 |
-
last_dates = nav_data_filtered.groupby([nav_data_filtered['date'].dt.year, nav_data_filtered['date'].dt.month]).head(1)
|
49 |
-
elif SIP_Date == 'end':
|
50 |
-
last_dates = nav_data_filtered.groupby([nav_data_filtered['date'].dt.year, nav_data_filtered['date'].dt.month]).tail(1)
|
51 |
-
else:
|
52 |
-
last_dates = nav_data_filtered.groupby([nav_data_filtered['date'].dt.year, nav_data_filtered['date'].dt.month]).apply(lambda x: x.iloc[len(x)//2])
|
53 |
-
|
54 |
-
total_investment = upfront_amount
|
55 |
-
current_sip_amount = sip_amount
|
56 |
-
|
57 |
-
# do calculation for upfront investment
|
58 |
-
units_bought = upfront_amount / nav_data_filtered.iloc[0]['nav']
|
59 |
-
units_accumulated = units_bought
|
60 |
-
previous_year = start_date.year
|
61 |
-
|
62 |
-
for _, row in last_dates.iloc[:-1].iterrows():
|
63 |
-
# Check if a year has passed and increase SIP amount accordingly
|
64 |
-
if row['date'].year > previous_year:
|
65 |
-
current_sip_amount += current_sip_amount * (stepup / 100)
|
66 |
-
previous_year = row['date'].year
|
67 |
-
|
68 |
-
units_bought = current_sip_amount / row['nav']
|
69 |
-
units_accumulated += units_bought
|
70 |
-
total_investment += current_sip_amount
|
71 |
-
|
72 |
-
final_value = units_accumulated * last_dates.iloc[-1]['nav']
|
73 |
-
total_return = (final_value - total_investment) / total_investment * 100
|
74 |
-
|
75 |
-
return total_return, final_value, total_investment
|
76 |
-
|
77 |
-
|
78 |
-
def calculate_portfolio_returns(schemes, sip_amount, upfront_amount, stepup, start_date, end_date, SIP_date, schemes_df):
|
79 |
-
scheme_returns = []
|
80 |
-
total_investment = 0
|
81 |
-
final_value = 0
|
82 |
-
inception_dates = []
|
83 |
-
|
84 |
-
for scheme_name, scheme_weight in schemes.items():
|
85 |
-
scheme_code = schemes_df[schemes_df['schemeName'] == scheme_name]['schemeCode'].values[0]
|
86 |
-
nav_data, inception_date = get_nav_data(scheme_code)
|
87 |
-
inception_dates.append((scheme_name, inception_date))
|
88 |
-
scheme_return, scheme_final_value, scheme_total_investment = calculate_sip_returns(nav_data, sip_amount * scheme_weight / 100, upfront_amount * scheme_weight / 100, stepup, start_date, end_date, SIP_date)
|
89 |
-
scheme_returns.append((scheme_name, scheme_return,scheme_final_value,scheme_total_investment))
|
90 |
-
final_value += scheme_final_value
|
91 |
-
total_investment += scheme_total_investment
|
92 |
-
|
93 |
-
portfolio_return = (final_value - total_investment) / total_investment * 100
|
94 |
-
return portfolio_return, final_value, total_investment, scheme_returns, inception_dates
|
95 |
-
|
96 |
-
def update_sip_calculator(*args):
|
97 |
period = args[0]
|
98 |
custom_start_date = args[1]
|
99 |
custom_end_date = args[2]
|
100 |
SIP_Date = args[3]
|
101 |
sip_amount = args[4]
|
102 |
-
|
103 |
stepup = args[6]
|
104 |
schemes_df = args[7]
|
105 |
-
|
|
|
106 |
|
107 |
-
for i in range(8, len(args) - 1, 2):
|
108 |
if args[i] and args[i+1]:
|
109 |
-
|
110 |
|
111 |
-
use_inception_date = args[-1]
|
112 |
|
113 |
-
if not
|
114 |
return "Please add at least one scheme.", None, None, None
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
end_date = datetime.now().date()
|
119 |
|
120 |
if use_inception_date:
|
121 |
-
start_date =
|
122 |
elif period == "Custom":
|
123 |
if not custom_start_date or not custom_end_date:
|
124 |
return "Please provide both start and end dates for custom period.", None, None, None
|
125 |
-
start_date =
|
126 |
-
end_date =
|
127 |
elif period == "YTD":
|
128 |
-
start_date =
|
129 |
elif not period:
|
130 |
return "Please select a period, provide custom dates, or use the inception date.", None, None, None
|
131 |
else:
|
@@ -135,47 +66,28 @@ def update_sip_calculator(*args):
|
|
135 |
|
136 |
if 'year' in period_parts[1]:
|
137 |
years = int(period_parts[0])
|
138 |
-
start_date = end_date -
|
139 |
else:
|
140 |
months = int(period_parts[0])
|
141 |
-
start_date = end_date -
|
142 |
|
143 |
-
|
144 |
-
portfolio_return, final_value, total_investment, scheme_returns, inception_dates = calculate_portfolio_returns(schemes, sip_amount, upfront_amount,stepup, start_date, end_date, SIP_Date, schemes_df)
|
145 |
-
except Exception as e:
|
146 |
-
return f"Error: {str(e)}", None, None, None
|
147 |
|
148 |
-
# Check if start_date is before any scheme's inception date
|
149 |
inception_warnings = []
|
150 |
-
earliest_inception_date = max(inception_date for _, inception_date in inception_dates)
|
151 |
for scheme_name, inception_date in inception_dates:
|
152 |
-
if start_date < inception_date
|
153 |
-
inception_warnings.append(f"Warning: {scheme_name} inception date ({inception_date.date()}) is after the chosen start date ({start_date}).")
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
for scheme_name, scheme_return, scheme_final_value, scheme_total_investment in scheme_returns:
|
166 |
-
result += f"---- {scheme_name} ----:\n"
|
167 |
-
result += f"Return: {scheme_return:.2f}%\n"
|
168 |
-
result += f"Total investment: {locale.currency(scheme_total_investment,grouping=True)}\n"
|
169 |
-
result += f"Final value: {locale.currency(scheme_final_value,grouping=True)}\n\n"
|
170 |
-
# pie_chart = create_pie_chart(schemes)
|
171 |
-
# return result, pie_chart, final_value, total_investment
|
172 |
-
return result
|
173 |
-
|
174 |
-
def fetch_scheme_data():
|
175 |
-
url = "https://api.mfapi.in/mf"
|
176 |
-
response = requests.get(url)
|
177 |
-
schemes = response.json()
|
178 |
-
return pd.DataFrame(schemes)
|
179 |
|
180 |
def quick_search_schemes(query, schemes_df):
|
181 |
if not query:
|
@@ -271,9 +183,8 @@ def handle_row_selection(schemes_list, evt: gr.SelectData, table_data):
|
|
271 |
return table_data, schemes_list
|
272 |
|
273 |
def create_ui():
|
274 |
-
schemes_df =
|
275 |
-
|
276 |
-
with gr.Blocks(js=js_func) as app:
|
277 |
gr.Markdown("# Mutual Fund SIP Returns Calculator")
|
278 |
|
279 |
with gr.Row():
|
@@ -358,7 +269,7 @@ def create_ui():
|
|
358 |
inception_dates = []
|
359 |
for scheme_name, _ in schemes_list:
|
360 |
scheme_code = schemes_df[schemes_df['schemeName'] == scheme_name]['schemeCode'].values[0]
|
361 |
-
_, inception_date =
|
362 |
inception_dates.append(inception_date)
|
363 |
return max(inception_dates).strftime("%Y-%m-%d") if inception_dates else ""
|
364 |
|
@@ -387,14 +298,14 @@ def create_ui():
|
|
387 |
return inputs
|
388 |
|
389 |
calculate_button.click(
|
390 |
-
lambda *args:
|
391 |
inputs=[period, custom_start_date, custom_end_date, SIP_Date, sip_amount,upfront_amount,stepup,schemes_list, gr.State(schemes_df), use_inception_date, inception_date_display],
|
392 |
outputs=[result]
|
393 |
# outputs=[result, final_value, total_investment]
|
394 |
# outputs=[result, pie_chart, final_value, total_investment]
|
395 |
)
|
396 |
|
397 |
-
return
|
398 |
|
399 |
-
|
400 |
-
|
|
|
1 |
+
import locale
|
2 |
import gradio as gr
|
3 |
+
import matplotlib.pyplot as plt
|
4 |
+
import numpy as np
|
5 |
+
import numpy_financial as npf
|
6 |
import pandas as pd
|
7 |
import plotly.graph_objects as go
|
8 |
+
import seaborn as sns
|
9 |
+
from pandas.tseries.offsets import DateOffset, MonthEnd
|
10 |
+
from scipy import optimize
|
11 |
+
|
12 |
+
from portfolio import calculate_portfolio_returns
|
13 |
+
from utils import get_all_mf_schemes_df,get_mf_scheme_data
|
14 |
|
15 |
js_func = """
|
16 |
function refresh() {
|
|
|
25 |
|
26 |
locale.setlocale(locale.LC_MONETARY, 'en_IN')
|
27 |
|
28 |
+
def get_portfolio_report(*args):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
period = args[0]
|
30 |
custom_start_date = args[1]
|
31 |
custom_end_date = args[2]
|
32 |
SIP_Date = args[3]
|
33 |
sip_amount = args[4]
|
34 |
+
lumpsum_amount = args[5]
|
35 |
stepup = args[6]
|
36 |
schemes_df = args[7]
|
37 |
+
|
38 |
+
scheme_name_and_weight = {}
|
39 |
|
40 |
+
for i in range(8, len(args) - 1, 2):
|
41 |
if args[i] and args[i+1]:
|
42 |
+
scheme_name_and_weight[args[i]] = float(args[i+1])
|
43 |
|
44 |
+
use_inception_date = args[-1]
|
45 |
|
46 |
+
if not scheme_name_and_weight:
|
47 |
return "Please add at least one scheme.", None, None, None
|
48 |
+
|
49 |
+
end_date = pd.Timestamp.now().floor('D')
|
|
|
|
|
50 |
|
51 |
if use_inception_date:
|
52 |
+
start_date = pd.Timestamp(custom_start_date)
|
53 |
elif period == "Custom":
|
54 |
if not custom_start_date or not custom_end_date:
|
55 |
return "Please provide both start and end dates for custom period.", None, None, None
|
56 |
+
start_date = pd.Timestamp(custom_start_date)
|
57 |
+
end_date = pd.Timestamp(custom_end_date)
|
58 |
elif period == "YTD":
|
59 |
+
start_date = pd.Timestamp(f"{end_date.year}-01-01")
|
60 |
elif not period:
|
61 |
return "Please select a period, provide custom dates, or use the inception date.", None, None, None
|
62 |
else:
|
|
|
66 |
|
67 |
if 'year' in period_parts[1]:
|
68 |
years = int(period_parts[0])
|
69 |
+
start_date = end_date - DateOffset(years=years)
|
70 |
else:
|
71 |
months = int(period_parts[0])
|
72 |
+
start_date = end_date - DateOffset(months=months)
|
73 |
|
74 |
+
portfolio_return_string, inception_dates, scheme_individual_returns = calculate_portfolio_returns(scheme_name_and_weight, sip_amount, lumpsum_amount, stepup, start_date, end_date, SIP_Date, schemes_df)
|
|
|
|
|
|
|
75 |
|
|
|
76 |
inception_warnings = []
|
|
|
77 |
for scheme_name, inception_date in inception_dates:
|
78 |
+
if start_date < inception_date:
|
79 |
+
inception_warnings.append(f"Warning: {scheme_name} inception date ({inception_date.date()}) is after the chosen start date ({start_date.date()}).")
|
80 |
+
|
81 |
+
result_string = f"""
|
82 |
+
{portfolio_return_string}
|
83 |
+
------------------------------------
|
84 |
+
Individual Scheme Returns
|
85 |
+
------------------------------------
|
86 |
+
{''.join(scheme_individual_returns)}
|
87 |
+
{''.join(inception_warnings)}
|
88 |
+
"""
|
89 |
+
return result_string
|
90 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
|
92 |
def quick_search_schemes(query, schemes_df):
|
93 |
if not query:
|
|
|
183 |
return table_data, schemes_list
|
184 |
|
185 |
def create_ui():
|
186 |
+
schemes_df = get_all_mf_schemes_df()
|
187 |
+
with gr.Blocks(js=js_func) as demo:
|
|
|
188 |
gr.Markdown("# Mutual Fund SIP Returns Calculator")
|
189 |
|
190 |
with gr.Row():
|
|
|
269 |
inception_dates = []
|
270 |
for scheme_name, _ in schemes_list:
|
271 |
scheme_code = schemes_df[schemes_df['schemeName'] == scheme_name]['schemeCode'].values[0]
|
272 |
+
_, inception_date = get_mf_scheme_data(scheme_code)
|
273 |
inception_dates.append(inception_date)
|
274 |
return max(inception_dates).strftime("%Y-%m-%d") if inception_dates else ""
|
275 |
|
|
|
298 |
return inputs
|
299 |
|
300 |
calculate_button.click(
|
301 |
+
lambda *args: get_portfolio_report(*prepare_inputs_with_inception(*args)),
|
302 |
inputs=[period, custom_start_date, custom_end_date, SIP_Date, sip_amount,upfront_amount,stepup,schemes_list, gr.State(schemes_df), use_inception_date, inception_date_display],
|
303 |
outputs=[result]
|
304 |
# outputs=[result, final_value, total_investment]
|
305 |
# outputs=[result, pie_chart, final_value, total_investment]
|
306 |
)
|
307 |
|
308 |
+
return demo
|
309 |
|
310 |
+
demo = create_ui()
|
311 |
+
demo.launch(debug=True)
|
portfolio.py
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
from utils import get_mf_scheme_data
|
3 |
+
from returns import get_investment_xirr, get_investment_sip_absolute_returns
|
4 |
+
|
5 |
+
def get_portfolio_nav_df(schemes_name_and_weight, start_date, end_date, schemes_df):
|
6 |
+
# start_date = pd.to_datetime(start_date)
|
7 |
+
# end_date = pd.to_datetime(end_date)
|
8 |
+
|
9 |
+
portfolio_nav_df = pd.DataFrame()
|
10 |
+
|
11 |
+
for scheme_name, scheme_weight in schemes_name_and_weight.items():
|
12 |
+
scheme_code = schemes_df[schemes_df['schemeName'] == scheme_name]['schemeCode'].values[0]
|
13 |
+
scheme_nav_df, _ = get_mf_scheme_data(scheme_code= scheme_code)
|
14 |
+
scheme_nav_df = scheme_nav_df[(scheme_nav_df['date'] >= start_date) & (scheme_nav_df['date'] <= end_date)]
|
15 |
+
scheme_nav_df['nav'] = scheme_nav_df['nav'] * scheme_weight / 100
|
16 |
+
if portfolio_nav_df.empty:
|
17 |
+
portfolio_nav_df = scheme_nav_df
|
18 |
+
else:
|
19 |
+
portfolio_nav_df['nav'] += scheme_nav_df['nav']
|
20 |
+
|
21 |
+
return portfolio_nav_df
|
22 |
+
|
23 |
+
|
24 |
+
def get_portfolio_xirr_returns(portfolio_df, start_date, end_date, SIP_date, lumpsum_amount, sip_amount):
|
25 |
+
return(get_investment_xirr(portfolio_df, start_date, end_date, SIP_date, lumpsum_amount, sip_amount))
|
26 |
+
|
27 |
+
def get_portfolio_absolute_returns(portfolio_df, sip_amount, lumpsum_amount, stepup, start_date, end_date, SIP_date):
|
28 |
+
|
29 |
+
return (get_investment_sip_absolute_returns(portfolio_df, sip_amount, lumpsum_amount, stepup, start_date, end_date, SIP_date))
|
30 |
+
|
31 |
+
def get_invdividual_scheme_returns(scheme_df, scheme_name,scheme_sip_amount, scheme_lumpsum_amount, stepup, start_date, end_date, SIP_date):
|
32 |
+
absolute_return, final_value, invested_value = get_investment_sip_absolute_returns(scheme_df, scheme_sip_amount, scheme_lumpsum_amount, stepup, start_date, end_date, SIP_date)
|
33 |
+
xirr = get_investment_xirr(scheme_df, start_date, end_date, SIP_date, scheme_lumpsum_amount, scheme_sip_amount)
|
34 |
+
scheme_string = f"""
|
35 |
+
Scheme: {scheme_name}
|
36 |
+
------------------------------------
|
37 |
+
Investment: {invested_value}
|
38 |
+
Scheme Final Value: {final_value}
|
39 |
+
Absolute Return: {absolute_return}%
|
40 |
+
XIRR: {xirr}%\n
|
41 |
+
"""
|
42 |
+
return (scheme_string)
|
43 |
+
|
44 |
+
|
45 |
+
|
46 |
+
def calculate_portfolio_returns(scheme_name_and_weight, sip_amount, lumpsum_amount, stepup, start_date, end_date, SIP_date, schemes_df):
|
47 |
+
inception_dates = []
|
48 |
+
scheme_individual_returns = []
|
49 |
+
portfolio_df = get_portfolio_nav_df(scheme_name_and_weight, start_date, end_date,schemes_df)
|
50 |
+
portfolio_absolute_return, portfolio_final_value, portfolio_invested_value = get_portfolio_absolute_returns(portfolio_df, sip_amount, lumpsum_amount, stepup, start_date, end_date, SIP_date)
|
51 |
+
portfolio_return_string = f"""
|
52 |
+
Portfolio
|
53 |
+
------------------------------------
|
54 |
+
SIP Amount: {sip_amount}
|
55 |
+
Lumpsum Amount: {lumpsum_amount}
|
56 |
+
Stepup: {stepup}
|
57 |
+
Start Date: {start_date}
|
58 |
+
End Date: {end_date}
|
59 |
+
SIP Date: {SIP_date}
|
60 |
+
Total Investment: {portfolio_invested_value}
|
61 |
+
------------------------------------
|
62 |
+
Portfolio Returns
|
63 |
+
XIRR = {get_portfolio_xirr_returns(portfolio_df, start_date, end_date, SIP_date, lumpsum_amount, sip_amount)}%
|
64 |
+
Absolute Returns = {portfolio_absolute_return}%
|
65 |
+
Portfolio Final Value = {portfolio_final_value}
|
66 |
+
"""
|
67 |
+
|
68 |
+
for scheme_name, scheme_weight in scheme_name_and_weight.items():
|
69 |
+
scheme_code = schemes_df[schemes_df['schemeName'] == scheme_name]['schemeCode'].values[0]
|
70 |
+
scheme_df, scheme_inception_date = get_mf_scheme_data(scheme_code)
|
71 |
+
inception_dates.append((scheme_name, scheme_inception_date))
|
72 |
+
scheme_sip_amount = sip_amount * scheme_weight / 100
|
73 |
+
scheme_lumpsum_amount = lumpsum_amount * scheme_weight / 100
|
74 |
+
scheme_individual_returns.append(get_invdividual_scheme_returns(scheme_df, scheme_name, scheme_sip_amount, scheme_lumpsum_amount, stepup, start_date, end_date, SIP_date))
|
75 |
+
|
76 |
+
return portfolio_return_string, inception_dates, scheme_individual_returns
|
77 |
+
|
78 |
+
|
79 |
+
|
80 |
+
|
81 |
+
|
82 |
+
|
83 |
+
|
returns.py
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from scipy import optimize
|
2 |
+
from utils import get_monthly_sip_nav_df
|
3 |
+
|
4 |
+
|
5 |
+
def calculate_xnpv(rate, cashflows):
|
6 |
+
chron_order = sorted(cashflows, key=lambda x: x[0])
|
7 |
+
t0 = chron_order[0][0]
|
8 |
+
return sum([cf/(1+rate)**((t-t0).days/365.0) for (t,cf) in chron_order])
|
9 |
+
|
10 |
+
def calculate_xirr(cashflows, guess=0.1):
|
11 |
+
return optimize.newton(lambda r: calculate_xnpv(r,cashflows), guess)
|
12 |
+
|
13 |
+
def get_investment_xirr(investment_df, start_date, end_date, SIP_date, lumpsum_amount=0, sip_amount=1000):
|
14 |
+
# Get the monthly NAVs
|
15 |
+
monthly_nav_df = get_monthly_sip_nav_df(investment_df, start_date, end_date, SIP_date)
|
16 |
+
|
17 |
+
# Initialize lists to store cash flows and their corresponding dates
|
18 |
+
cash_flows = []
|
19 |
+
dates = []
|
20 |
+
|
21 |
+
# Add the lumpsum investment at the start
|
22 |
+
cash_flows.append(-lumpsum_amount)
|
23 |
+
dates.append(start_date)
|
24 |
+
|
25 |
+
# Calculate initial units from lumpsum investment
|
26 |
+
initial_units = lumpsum_amount / monthly_nav_df['nav'].iloc[0]
|
27 |
+
total_units = initial_units
|
28 |
+
|
29 |
+
# Iterate over each row and record the SIP investments
|
30 |
+
for _, row in monthly_nav_df.iterrows():
|
31 |
+
cash_flows.append(-sip_amount) # SIP investment is negative cash flow
|
32 |
+
dates.append(row['date'])
|
33 |
+
total_units += sip_amount / row['nav']
|
34 |
+
|
35 |
+
# Add the final value as a positive cash flow
|
36 |
+
final_value = total_units * monthly_nav_df['nav'].iloc[-1]
|
37 |
+
cash_flows.append(final_value)
|
38 |
+
dates.append(monthly_nav_df['date'].iloc[-1])
|
39 |
+
|
40 |
+
portfolio_XIRR = calculate_xirr(list(zip(dates, cash_flows)))
|
41 |
+
|
42 |
+
return portfolio_XIRR * 100
|
43 |
+
|
44 |
+
def get_investment_sip_absolute_returns(investment_df, sip_amount, lumpsum_amount, stepup, start_date, end_date, SIP_Date):
|
45 |
+
# start_date = pd.Timestamp(start_date)
|
46 |
+
# end_date = pd.Timestamp(end_date)
|
47 |
+
|
48 |
+
scheme_df_monthly = get_monthly_sip_nav_df(investment_df, start_date, end_date, SIP_Date)
|
49 |
+
|
50 |
+
total_investment = lumpsum_amount
|
51 |
+
current_sip_amount = sip_amount
|
52 |
+
|
53 |
+
# do calculation for upfront investment
|
54 |
+
units_bought = lumpsum_amount / scheme_df_monthly.iloc[0]['nav']
|
55 |
+
units_accumulated = units_bought
|
56 |
+
previous_year = start_date.year
|
57 |
+
|
58 |
+
for _, row in scheme_df_monthly.iloc[:-1].iterrows():
|
59 |
+
# Check if a year has passed and increase SIP amount accordingly
|
60 |
+
if row['date'].year > previous_year:
|
61 |
+
current_sip_amount += current_sip_amount * (stepup / 100)
|
62 |
+
previous_year = row['date'].year
|
63 |
+
|
64 |
+
units_bought = current_sip_amount / row['nav']
|
65 |
+
units_accumulated += units_bought
|
66 |
+
total_investment += current_sip_amount
|
67 |
+
|
68 |
+
final_value = units_accumulated * scheme_df_monthly.iloc[-1]['nav']
|
69 |
+
total_return = (final_value - total_investment) / total_investment * 100
|
70 |
+
|
71 |
+
return total_return, final_value, total_investment
|
utils.py
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import requests
|
3 |
+
|
4 |
+
# This function groups the data by month
|
5 |
+
# and returns the first, last or middle data of each month
|
6 |
+
def get_monthly_sip_nav_df(df, start_date, end_date, SIP_date):
|
7 |
+
df = df.copy()
|
8 |
+
df['date'] = pd.to_datetime(df['date'])
|
9 |
+
df = df[(df['date'] >= start_date) & (df['date'] <= end_date)]
|
10 |
+
if SIP_date == 'start':
|
11 |
+
df = df.groupby([df['date'].dt.year, df['date'].dt.month]).first()
|
12 |
+
elif SIP_date == 'end':
|
13 |
+
df = df.groupby([df['date'].dt.year, df['date'].dt.month]).last()
|
14 |
+
else:
|
15 |
+
df = df.groupby([df['date'].dt.year, df['date'].dt.month]).apply(lambda x: x.iloc[len(x)//2])
|
16 |
+
return df
|
17 |
+
|
18 |
+
|
19 |
+
# This function returns the complete list of mutual fund schemes in a DataFrame
|
20 |
+
def get_all_mf_schemes_df():
|
21 |
+
url = "https://api.mfapi.in/mf"
|
22 |
+
response = requests.get(url)
|
23 |
+
schemes = response.json()
|
24 |
+
return pd.DataFrame(schemes)
|
25 |
+
|
26 |
+
# This function returns the data of a particular mutual fund scheme, and its inception date
|
27 |
+
def get_mf_scheme_data(scheme_code):
|
28 |
+
url = f"https://api.mfapi.in/mf/{scheme_code}"
|
29 |
+
response = requests.get(url)
|
30 |
+
data = response.json()
|
31 |
+
df = pd.DataFrame(data['data'])
|
32 |
+
df['date'] = pd.to_datetime(df['date'], format='%d-%m-%Y')
|
33 |
+
df['nav'] = df['nav'].astype(float)
|
34 |
+
df = df.sort_values('date')
|
35 |
+
inception_date = df['date'].min()
|
36 |
+
return df, inception_date
|
37 |
+
|