gluo88 commited on
Commit
9d129d8
·
verified ·
1 Parent(s): 168f192

Create performance.py

Browse files
Files changed (1) hide show
  1. performance.py +472 -0
performance.py ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ Example 8 for using yfinance
3
+
4
+ Calculate annual, trailing, cumumlative, and CAGR returns for multiple stocks.
5
+ * The start date can be an arbitrary date. The default is the current date.
6
+ * annual return is displayed from the default current day, or an arbitrary given
7
+ day (except for Feb 29 for leap year) TODO-fix
8
+ * trailing, cumumlative returns are currently displayed from the month boundary (last day of Month)
9
+ prior to the given date.
10
+ * However, trailing, cumumlative returns can be displayed
11
+ from any date, which can be not at the month boundary (last day of Month),
12
+ by minor change of setting calculation_end_date_for_others_str = calculation_end_date_str.
13
+ prior to the given date in the function "calculation_response(message, history)"
14
+
15
+ Author: Gang Luo
16
+ '''
17
+ script_version = '(2024-01-24.2)'
18
+ import gradio as gr
19
+ import yfinance as yf
20
+ import pandas as pd
21
+ import numpy as np
22
+ from datetime import datetime, timedelta
23
+ import pytz
24
+ #==============================================================================
25
+
26
+ print_yearly_total_return = True
27
+ num_years_calculation=32 # total years for calculation
28
+
29
+ # Define a list of years to calculate the trailing returns, cumulative returns, and so on
30
+ # remove the row of current year row since it is not a full year.
31
+ years_list = [1, 2, 3, 5, 10, 15, 20, 25, 30]
32
+
33
+ # Set the stock tickers list
34
+ tickers_lists = [["spy", "vfv.to","xiu.to"], #0
35
+ ["spy", "vfv.to", "vgg.to", "zlu.to", "xiu.to", "vdy.to", "xfn.to", "ry.to", "td.to", "na.to",
36
+ "slf.to", "gwo.to", "bce.to", "t.to", "rci-b.to", "enb.to", "trp.to", "zlb.to", "cp.to"], #1
37
+ ["spy","vfv.to", "xiu.to", "zeb.to", "xfn.to", "na.to","ry.to", "bmo.to","bns.to", "td.to", "cm.to", "cwb.to",
38
+ "slf.to", "gwo.to", "bce.to", "t.to", "rci-b.to", "enb.to", "trp.to", "xdv.to","cdz.to","vdy.to"], #2
39
+ ["spy", "vfv.to", "vgg.to", "zlu.to","goog", "msft", "meta", "tsla","AMZN", "AAPL", "shop.to"], #3
40
+ ["^IXIC","qqq","hxq.to","^GSPC","spy","voo","ivv", "vfv.to", "zsp.to","xus.to", "xsp.to","^GSPTSE","xic.to","xiu.to","xfn.to", "fie.to"], #4
41
+ ["^IXIC","ONEQ","CIBR","QQJG", "qqq", "spy", "vfv.to", "HXQ.to", "ZQQ.to", "XQQ.to", "QQC.to"] #5
42
+ ]
43
+
44
+ #==============================================================================
45
+ # Part 1: fetch retrieve yearly total returns by yfinance & display
46
+ # Function to fetch data from yfinance and extract yearly total returns#
47
+ # annual return calculation can start at any given day
48
+ def get_annual_returns_df(ticker, calculation_end_date_str):
49
+ # Get the historical data for the given ticker
50
+ stock = yf.Ticker(ticker)
51
+ calculation_end_date=pd.to_datetime(calculation_end_date_str).tz_localize('America/New_York')
52
+ try:
53
+ '''
54
+ 'try' statement for handlingy the exception error of stock.history that a ticker is not yet at stock market,
55
+ For example, "shop.to" is not there in 2012
56
+ '''
57
+ stock_history=stock.history(period="max")["Close"]
58
+ '''
59
+ Between the start and end days in stock_history variable, there are some missing days where there are no corresponding rows.
60
+ Add rows of missing days such that the values of column "Close" are set to be the value of the closest earlier day's
61
+ value, by using date_range to create full range without any missing date.
62
+ '''
63
+ # Create a DataFrame with a complete date range
64
+ date_range = pd.date_range(start=stock_history.index.min(), end=stock_history.index.max(), freq='D')
65
+ complete_stock_history = pd.DataFrame(index=date_range)
66
+ # Merge the complete DataFrame with the original stock_history
67
+ complete_stock_history = complete_stock_history.merge(stock_history, how='left', left_index=True, right_index=True)
68
+ complete_stock_history['Close'] = complete_stock_history['Close'].ffill() # fill the newy added rows with previous day value
69
+ '''
70
+ Filter out the rows that matches the month and date of calculation_end_date, which are the ends of
71
+ annual periods from the calculation_end_date.
72
+ '''
73
+ # Filter out rows with dates newer than calculation_end_date
74
+ filtered_stock_history = complete_stock_history[complete_stock_history.index <= calculation_end_date]
75
+ #print(filtered_stock_history)
76
+ target_month=filtered_stock_history.index.max().month
77
+ target_day=filtered_stock_history.index.max().day
78
+ #print("target_month", target_month, "target_day",target_day, "start_year", filtered_stock_history.index.max().year)
79
+ annual_returns = filtered_stock_history[(filtered_stock_history.index.month == target_month)
80
+ & (filtered_stock_history.index.day ==target_day)]
81
+ annual_returns_percent = annual_returns.pct_change().dropna()
82
+ except:
83
+ return pd.DataFrame()
84
+ else:
85
+ annual_returns_df = pd.DataFrame(annual_returns_percent, columns=['Close'])
86
+ annual_returns_df.rename(columns={'Close': ticker}, inplace=True)
87
+ return annual_returns_df
88
+
89
+ # Function to fetch data from yfinance and extract yearly total returns
90
+ # annual return calculation starts at only yaer end boundary, i.e, Dec 31,
91
+ # by resample('A')
92
+ def get_annual_returns_year_boundary_df(ticker, calculation_end_date_str):
93
+ # Get the historical data for the given ticker
94
+ stock = yf.Ticker(ticker)
95
+ calculation_end_date = datetime.strptime(calculation_end_date_str, "%Y-%m-%d")
96
+ calculation_start_date_str = (calculation_end_date
97
+ - timedelta(days=num_years_calculation * 365)).strftime("%Y-%m-%d")
98
+
99
+ try:
100
+ '''
101
+ 1. 'try' statement for handlingy the exception error of stock.history that a ticker is not yet at stock market,
102
+ For example, "shop.to" is not there in 2012
103
+ 2. The row with the latest day from .history(.., end='end_day_date') is the day prior to end_day_date. Therefore,
104
+ let end=the expected end day plus one day.
105
+ '''
106
+ calculation_end_date_plus_1day_str = (calculation_end_date + timedelta(days=1)).strftime("%Y-%m-%d")
107
+ annual_returns_history=stock.history(start=calculation_start_date_str,end=calculation_end_date_plus_1day_str)["Close"]
108
+
109
+ #print("debug get_annual_returns_df ", ticker, annual_returns_history)
110
+ # For 'A', 'Y', see https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases
111
+ ffilled_history=annual_returns = annual_returns_history.resample('A').ffill()
112
+ #print(ffilled_history)
113
+ annual_returns = ffilled_history.pct_change().dropna()
114
+ #annual_returns = annual_returns_history.resample('A').ffill().pct_change().dropna()
115
+ #print("debug get_annual_returns_df after resample()", ticker, calculation_end_date, "\n", annual_returns)
116
+ except:
117
+ return pd.DataFrame()
118
+ else:
119
+ annual_returns_df = pd.DataFrame(annual_returns, columns=['Close'])
120
+ annual_returns_df.rename(columns={'Close': ticker}, inplace=True)
121
+ return annual_returns_df
122
+
123
+ #----------------------------------------------------------------------------------
124
+ # handling a list of tickers by calling the functions (either get_annual_returns_df
125
+ # get_annual_returns_year_boundary_df) that handle single tickers
126
+ def get_annual_returns_tickers_common_df(tickers, calculation_end_date_str, annual_returns_func_df):
127
+ # Create an empty DataFrame to store all tickers' total returns
128
+ all_tickers_returns_df = pd.DataFrame()
129
+
130
+ # Loop through each ticker in the list
131
+ for ticker in tickers:
132
+ ticker_returns_df = annual_returns_func_df(ticker, calculation_end_date_str)
133
+ if not ticker_returns_df.empty:
134
+ if all_tickers_returns_df.empty:
135
+ all_tickers_returns_df = ticker_returns_df
136
+ else:
137
+ all_tickers_returns_df = pd.concat([all_tickers_returns_df, ticker_returns_df], axis=1) # Concatenate DataFrames
138
+ else:
139
+ # New column with NaN values
140
+ new_column_name = ticker
141
+ new_column_values = [None] * len(all_tickers_returns_df)
142
+ new_column = pd.DataFrame({new_column_name: new_column_values}, index=all_tickers_returns_df.index)
143
+ # Concatenate the new column to the original DataFrame
144
+ all_tickers_returns_df = pd.concat([all_tickers_returns_df, new_column], axis=1)
145
+ #return date_label_conversion_strip_time(all_tickers_returns_df, calculation_end_date_str)
146
+ return all_tickers_returns_df
147
+
148
+ def get_annual_returns_tickers_df(tickers, calculation_end_date_str):
149
+ return get_annual_returns_tickers_common_df(tickers, calculation_end_date_str,
150
+ get_annual_returns_df)
151
+
152
+ def get_annual_returns_tickers_year_boundary_df(tickers, calculation_end_date_str):
153
+ return get_annual_returns_tickers_common_df(tickers, calculation_end_date_str,
154
+ get_annual_returns_year_boundary_df)
155
+
156
+ #==============================================================================
157
+ # Part 2: calculate the annualized trailing total return from the data generated in step 1 & display
158
+ # Define a function to calculate the annualized trailing total return for a given number of years
159
+ def get_trailing_return(ticker, data, years):
160
+ # Get the total return values for the last n years
161
+ trailing_data = data[ticker].tail(years)
162
+ # Check if there are empty values within years
163
+ if trailing_data.isna().any():
164
+ return "N/A"
165
+ # Check if there are valid total return values for all years
166
+ if len(trailing_data) == years:
167
+ # Convert the percentage strings to numeric values
168
+ trailing_data = trailing_data.astype(str).str.replace('%', '').astype(float)
169
+ """ Calculate the annualized trailing total return using the formula from Investopedia[^1^][1]:
170
+ Annualized Return = [(1 + r1) * (1 + r2) * ... * (1 + rn)]^(1/n) - 1
171
+ Where r1, r2, ..., rn are the total return values for each year """
172
+ annualized_trailing_return = (trailing_data + 1).prod() ** (1 / years) - 1
173
+
174
+ # Format the result as a percentage with two decimal places
175
+ annualized_trailing_return = annualized_trailing_return * 100
176
+ annualized_trailing_return = annualized_trailing_return.round(2)
177
+ return annualized_trailing_return
178
+ else:
179
+ return "N/A"
180
+
181
+ # Define a function to Loop through the list and print the trailing returns for each num_years
182
+ def get_trailing_return_column(ticker, annual_returns_df):
183
+ trailing_return_column = {}
184
+ for num_years in years_list:
185
+ # Check if the ticker data is available in all_tickers_returns_df
186
+ if ticker in annual_returns_df.columns:
187
+ # using data from step 1, avoiding get_annual_returns_df(ticker) for less traffic from yahoo server
188
+ data = annual_returns_df[[ticker]]
189
+ trailing_return = get_trailing_return(ticker, data, num_years)
190
+ trailing_return_column[f"{num_years}-Year"] = trailing_return
191
+ else:
192
+ print(f"Data not available for {ticker}. Skipping.")
193
+ trailing_return_column[f"{num_years}-Year"] = "N/A"
194
+ return trailing_return_column
195
+
196
+ # Create an empty DataFrame to store all tickers' trailing returns
197
+ def get_trailing_return_all(tickers, annual_returns_df):
198
+ all_tickers_trailing_returns_df = pd.DataFrame(index=years_list)
199
+
200
+ # Loop through each ticker in the list
201
+ for ticker in tickers:
202
+ trailing_returns = get_trailing_return_column(ticker, annual_returns_df)
203
+ # Add the trailing returns to the DataFrame
204
+ all_tickers_trailing_returns_df[ticker] = pd.Series(trailing_returns).values
205
+ return all_tickers_trailing_returns_df
206
+
207
+ #==============================================================================
208
+ # Part 3: calculate the cumulative return from the data (all_tickers_returns_df) generated in part 1 & display
209
+ # Define a function to calculate the cumulative return for a given number of years from a ticker
210
+ def get_cumulative_return(ticker, data, years):
211
+ # Calculate the cumulative return
212
+ cumulative_return = (1 + data[ticker]).rolling(window=years).apply(lambda x: x.prod(), raw=True) - 1
213
+ return cumulative_return
214
+
215
+ # Define a function to Loop through the list and return the cumulative returns for each num_years
216
+ def get_cumulative_return_column(ticker, annual_returns_df):
217
+ cumulative_returns = {}
218
+ for years in years_list:
219
+ # Calculate the cumulative return for the given number of years
220
+ cumulative_return = get_cumulative_return(ticker, annual_returns_df, years)
221
+ # Get the last value, which is the cumulative return up to the current year
222
+ cumulative_returns[years] = cumulative_return.iloc[-1]
223
+ return cumulative_returns
224
+
225
+ def get_cumulative_return_all(tickers, annual_returns_df):
226
+ # Create an empty DataFrame with years_list as the index for cumulative returns
227
+ all_tickers_cumulative_returns_df = pd.DataFrame(index=years_list)
228
+ # Loop through each ticker in the list
229
+ for ticker in tickers:
230
+ cumulative_returns = get_cumulative_return_column(ticker, annual_returns_df)
231
+ # Add the trailing returns to the DataFrame
232
+ all_tickers_cumulative_returns_df[ticker] = pd.Series(cumulative_returns).values
233
+ return all_tickers_cumulative_returns_df
234
+
235
+ #==============================================================================
236
+ # Part 4: calculate the CAGR (Compound Annual Growth Rate) from the data
237
+ # in all_tickers_cumulative_returns_df generated earlier & display
238
+ # Define a function to calculate the CAGR from the cumulative value and the years
239
+ def calculate_cagr(value, years):
240
+ # Otherwise, calculate the CAGR using the formula
241
+ cagr = (value + 1) ** (1 / np.array(years)) - 1
242
+ #print("debug-cagr\n", cagr, "end")
243
+ return cagr
244
+
245
+ # Define a function to format the Float64Index values into percentage strings
246
+ def format_to_percentage(value):
247
+ # If any element in the value array is not null, format it as a percentage string with two decimal places
248
+ if np.any(pd.notnull(value)):
249
+ return f"{value:.2f}%"
250
+ # Otherwise, return None
251
+ return None
252
+
253
+ def get_cagr_return_all(all_tickers_cumulative_returns_df):
254
+ # Apply the calculate_cagr function to each column of the DataFrame
255
+ all_tickers_cagrs_df = all_tickers_cumulative_returns_df.apply(lambda x: calculate_cagr(x, x.index), axis=0)
256
+ return all_tickers_cagrs_df
257
+
258
+ #==============================================================================
259
+ # Part 5: utility functions
260
+ # get the last trading day of S&P 500 in string format
261
+ def get_last_trading_day():
262
+ # Get today's date, use .strftime("%Y-%m-%d") to convert to a string
263
+ today_date_str=datetime.now(pytz.timezone('America/New_York')).date().strftime("%Y-%m-%d")
264
+ stock = yf.Ticker("^GSPC") # S&P 500 (^GSPC) ticker
265
+ # search and see yfinance_BUG_1 NOTE in this file
266
+ history_df=stock.history(period="max", end=today_date_str)["Close"]
267
+ last_trading_day_str = history_df.index.max().date().strftime("%Y-%m-%d")
268
+ return last_trading_day_str
269
+
270
+ def str_to_integer(integer_str):
271
+ try:
272
+ integer_number = int(integer_str)
273
+ return integer_number
274
+ except ValueError:
275
+ return -1
276
+
277
+ # validate the date string
278
+ def is_valid_date(date_string):
279
+ try:
280
+ # Attempt to parse the date string
281
+ datetime.strptime(date_string, "%Y-%m-%d")
282
+ return True
283
+ except ValueError:
284
+ # Raised when the date string is not in the expected format
285
+ return False
286
+
287
+ def date_label_conversion_strip_time(all_tickers_returns_df, calculation_end_date_str):
288
+ all_tickers_returns_df.index=all_tickers_returns_df.index.date
289
+ all_tickers_returns_df.index.name='date'
290
+ # print("debug get_annual_returns_tickers_df", all_tickers_returns_df)
291
+ # Convert calculation_end_date_str to a datetime object, replace the index's mon/day portion of date
292
+ end_date_datetime_obj = datetime.strptime(calculation_end_date_str, "%Y-%m-%d")
293
+ all_tickers_returns_df.index = all_tickers_returns_df.index.map(
294
+ lambda x: x.replace(month=end_date_datetime_obj.month,
295
+ day=end_date_datetime_obj.day))
296
+ return all_tickers_returns_df
297
+
298
+ #==============================================================================
299
+ # Part 6:
300
+ # single ticker's Prices, Returns,Dividends, good for verifying whether "Adj Close" is correct.
301
+ '''
302
+ Calculate and display: yearly dividendSum, 'Close' & 'Adj Close' prices,
303
+ Return(by 'Close' price), total return(by 'Adj Close' price),
304
+ CalReturn(total return by 'Close' price and "dividendSum).
305
+ Note: CalReturn from is expected to be nearly same as total return,
306
+ when the 'Adj Close' price is correct.
307
+ '''
308
+ def get_yearly_single_stock_data(ticker):
309
+ stock = yf.Ticker(ticker)
310
+ #-------- mainly for downloading 'Dividends'
311
+ history = stock.history(period="max")
312
+ dividend_history=history['Dividends']
313
+ dividend_history.index=dividend_history.index.date
314
+
315
+ #-------- mainly for downloading 'Close','Adj Close'
316
+ dld_history=yf.download(ticker, period="max")
317
+ dld_history=dld_history[['Close','Adj Close']]
318
+ dld_history.rename(columns={'Adj Close': 'AdjClose'}, inplace=True)
319
+ date_range = pd.date_range(start=dld_history.index.min(), end=dld_history.index.max(), freq='D')
320
+ complete_history = pd.DataFrame(index=date_range)
321
+
322
+ # Merge the complete DataFrame with the original stock_history
323
+ complete_history = complete_history.merge(dld_history, how='left', left_index=True, right_index=True)
324
+ complete_history[['Close','AdjClose']] = complete_history[['Close','AdjClose']].ffill().round(3)
325
+
326
+ # Merge dividend into complete_history
327
+ complete_history = complete_history.merge(dividend_history, how='left', left_index=True, right_index=True)
328
+ # replace all NaN values in the 'Dividends' column with 0.0
329
+ complete_history['Dividends'] = complete_history['Dividends'].fillna(0.0).round(3)
330
+
331
+ complete_history['Year']=complete_history.index.year
332
+ complete_history['Date']=complete_history.index
333
+ yearly_data = complete_history.groupby('Year').agg({'Date': 'last', 'Close': 'last', 'AdjClose': 'last','Dividends': 'sum'})
334
+ yearly_data.rename(columns={'Dividends': 'DivSum'}, inplace=True)
335
+
336
+ # calculating 'Return' and 'TotalReturn'
337
+ yearly_data['DivRatio']=yearly_data['DivSum'] / yearly_data['Close']
338
+ yearly_data['Return']=yearly_data['Close'].pct_change()
339
+ yearly_data['TotalReturn']=yearly_data['AdjClose'].pct_change()
340
+
341
+ '''
342
+ The CalReturn column is the yearly total return calculated from un-adjusted "Close" prices and yearly "dividend sum",
343
+ which is expected to be equal to the total return that is calculated from "AdjClose" prices
344
+ '''
345
+ yearly_data['CalReturn'] = (yearly_data['Close'] + yearly_data['DivSum']) / yearly_data['Close'].shift(1) - 1
346
+ # set the display format
347
+ yearly_data[['DivRatio','Return','TotalReturn','CalReturn']] = yearly_data[['DivRatio','Return','TotalReturn','CalReturn']].mul(100).round(2)
348
+ yearly_data[['DivRatio','Return', 'TotalReturn', 'CalReturn']] = yearly_data[['DivRatio','Return', 'TotalReturn', 'CalReturn']].applymap("{:.2f}%".format)
349
+ # 'Date' column is no longer required
350
+ yearly_data.drop('Date', axis=1, inplace=True)
351
+ return yearly_data
352
+
353
+ #==============================================================================
354
+ # Part 7: gradio handling - Input command handling and display in web page
355
+
356
+ help_info_str="Input Formats:\n \
357
+ 1. ticker list....................Example: spy vfv.to xiu.to xic.to xfn.to ry.to \n \
358
+ 2. One of default ticker list, a number between 1 and 5....Example: 0, or 1, ...,5 \n \
359
+ 3. CalculationEndDate as prefix. Example: 2020-12-31 2 \n \
360
+ .........................................2020-12-31 spy vfv.to xiu.to xic.to xfn.to ry.to \n \
361
+ 4. single ticker: Dividend/Close/AdjClose/Return/TotalReturn/CalReturn(by Close/Dividends). @1 spy \n \
362
+ note: daily adjusted close data are from Yahoo Finance. "
363
+
364
+ # Gradio Web interface
365
+ def calculation_response(message, history):
366
+ # if there is no input, display help information
367
+ if message=="":
368
+ return help_info_str
369
+
370
+ tickers=message.split()
371
+
372
+ # ******************************************************************************
373
+ # processing web input parameters
374
+ # set calculation_end_date_str, and tickers
375
+
376
+ #---------------------------------------------------------
377
+ # single stock ticker - detailed information
378
+ if (tickers[0] == "@1"):
379
+ tickers.pop(0) # remove the first string which is "@1"
380
+ if len(tickers)==0:
381
+ ticker = 'spy' # default ticker = spy
382
+ else:
383
+ ticker=tickers[0]
384
+ output_string=f"\n {ticker}\n"
385
+ output_dataframe0=get_yearly_single_stock_data(ticker)
386
+ output_html=output_string + output_dataframe0.to_html()
387
+ return output_html
388
+
389
+ #----------------------------------------------------------
390
+ # Get today's date, use .strftime("%Y-%m-%d") to convert to a string
391
+ #calculation_end_date_str=datetime.now(pytz.timezone('America/New_York')).date().strftime("%Y-%m-%d")
392
+ calculation_end_date_str = get_last_trading_day()
393
+ # Check whether the first str is date for calculation end date
394
+ if is_valid_date(tickers[0]):
395
+ calculation_end_date_str = tickers[0] # reset calculation_end_date_str
396
+ tickers.pop(0) # remove the first string which is the date
397
+
398
+ #............ For display trailing and cumulative returns at month_boundary_date
399
+ # Assuming calculation_end_date_str contains the date string '2024-01-03'
400
+ calculation_end_date = datetime.strptime(calculation_end_date_str, '%Y-%m-%d')
401
+ # Calculate the first day of the current month
402
+ first_day_of_month = calculation_end_date.replace(day=1)
403
+ # Calculate the last day of the month
404
+ last_day_of_month = (calculation_end_date.replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1)
405
+ # Calculate the last day of the previous month
406
+ last_day_of_previous_month = first_day_of_month - timedelta(days=1)
407
+ # Check if the original date is the last day of the month
408
+ if (calculation_end_date == last_day_of_month):
409
+ calculation_end_date_month_boundary_date_str=calculation_end_date_str
410
+ else:
411
+ calculation_end_date_month_boundary_date_str=last_day_of_previous_month.strftime('%Y-%m-%d')
412
+ # calculation_end_date_for_others are for trailing and cumulative returns
413
+ calculation_end_date_for_others_str=calculation_end_date_month_boundary_date_str
414
+
415
+ ''' TODO handling Feb 29 of leap year.
416
+ Check if involved dates (in calculation_end_date_str and calculation_end_date_for_others_str),
417
+ Feb 28 will be used to replace Feb 29 for calculation
418
+ '''
419
+ #................End
420
+
421
+ # Check whether numebr 0, 1, 2, .. is selected for using a default ticker list
422
+ integer_value=str_to_integer(tickers[0])
423
+ if (integer_value >= 0 and integer_value <len(tickers_lists)):
424
+ tickers=tickers_lists[integer_value]
425
+
426
+ # if no tickers were set, display help information
427
+ if len(tickers)==0:
428
+ return help_info_str
429
+
430
+ #*********************************************************************************
431
+ # Calculating Annual, Trailing, Cumulative, and CAGR & generating html for display
432
+ # annual_returns - at year end boundard, to be displayed
433
+ output_string = f"\nAnnual Total Return (%) as {calculation_end_date_str}\n"
434
+ output_dataframe = get_annual_returns_tickers_year_boundary_df(tickers, calculation_end_date_str)
435
+ output_dataframe = output_dataframe.round(4)*100
436
+ output_dataframe.index=output_dataframe.index.date
437
+ # Assuming your DataFrame is named output_dataframe
438
+ last_date = output_dataframe.index[-1]
439
+ output_dataframe = output_dataframe.rename(index={last_date: calculation_end_date_str})
440
+ # Convert the DataFrame to HTML, Combine the expected string outputs
441
+ output_html1 = output_string + output_dataframe.to_html()
442
+
443
+ # annual_returns - at any given day, for calculating trailing and cumulative returns, not to be displayed
444
+ annual_returns_dataframe=get_annual_returns_tickers_df(tickers, calculation_end_date_for_others_str)
445
+
446
+ # Trailing Return
447
+ output_string2 = f"\nTrailing Total Return (%) as {calculation_end_date_for_others_str}\n"
448
+ output_dataframe2=get_trailing_return_all(tickers, annual_returns_dataframe)
449
+ # Insert an empty to align the ticker symbols with annual return display
450
+ output_dataframe2.insert(0, "--------", " ")
451
+ output_dataframe2.index.name="years"
452
+ output_html2=output_string2 + output_dataframe2.to_html()
453
+
454
+ # Cumulative Return
455
+ output_string3 = f"\nCumulative Return (%) as {calculation_end_date_for_others_str}\n"
456
+ cumulative_return_all_dataframe=get_cumulative_return_all(tickers, annual_returns_dataframe)
457
+ output_dataframe3=cumulative_return_all_dataframe.round(4)*100
458
+ output_dataframe3.index.name="years"
459
+ output_html3=output_string3 + output_dataframe3.to_html()
460
+
461
+ # CAGR Return
462
+ output_string4 = f"\nCompound Annual Growth Rate (CAGR) (%) as {calculation_end_date_for_others_str}\n"
463
+ output_dataframe4=get_cagr_return_all (cumulative_return_all_dataframe)
464
+ output_dataframe4=output_dataframe4.round(4)*100
465
+ output_html4=output_string4 + output_dataframe4.to_html()
466
+
467
+ #output_html = output_html1 + output_html2 + output_html3 + output_html4
468
+ output_html = output_html1 + output_html2 + output_html3
469
+ return output_html
470
+
471
+ demo = gr.ChatInterface(calculation_response)
472
+ demo.launch(debug=False, share=False)