QuantumLearner commited on
Commit
69b6300
·
verified ·
1 Parent(s): 0509bb9

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +716 -0
app.py ADDED
@@ -0,0 +1,716 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ import pandas as pd
4
+ import plotly.graph_objs as go
5
+ from plotly.subplots import make_subplots
6
+ import os
7
+
8
+ # ---------------------------
9
+ # Global API Configuration (hidden)
10
+ # ---------------------------
11
+ API_KEY = os.getenv("FMP_API_KEY")
12
+ BASE_URL = "https://financialmodelingprep.com/api/v3/"
13
+
14
+ # ---------------------------
15
+ # Data Fetching Functions
16
+ # ---------------------------
17
+ def fetch_fmp_data(endpoint, ticker, period="annual", limit=10):
18
+ url = f"{BASE_URL}{endpoint}/{ticker}"
19
+ params = {"period": period, "limit": limit, "apikey": API_KEY}
20
+ r = requests.get(url, params=params)
21
+ if r.status_code == 200:
22
+ return pd.DataFrame(r.json())
23
+ else:
24
+ st.error("Data retrieval error. Check inputs or try again later.")
25
+ return pd.DataFrame()
26
+
27
+ def process_statement_df(df, date_col="date"):
28
+ if df.empty:
29
+ return df
30
+ df[date_col] = pd.to_datetime(df[date_col])
31
+ df.set_index(date_col, inplace=True)
32
+ return df.transpose()
33
+
34
+ def fetch_historical_prices(ticker):
35
+ url = f"{BASE_URL}historical-price-full/{ticker}"
36
+ params = {"serietype": "line", "apikey": API_KEY}
37
+ r = requests.get(url, params=params)
38
+ if r.status_code == 200:
39
+ data = r.json()
40
+ if "historical" in data:
41
+ hist_df = pd.DataFrame(data["historical"])
42
+ hist_df["date"] = pd.to_datetime(hist_df["date"])
43
+ hist_df.sort_values("date", inplace=True)
44
+ return hist_df
45
+ st.error("Unable to fetch historical prices. Try again later.")
46
+ return pd.DataFrame()
47
+
48
+ def get_closing_price_on_or_before(hist_df, date):
49
+ df = hist_df[hist_df["date"] <= date]
50
+ if not df.empty:
51
+ return df.iloc[-1]["close"]
52
+ return None
53
+
54
+ def fetch_all_data(ticker="AAPL", period="annual", limit=10):
55
+ inc = fetch_fmp_data("income-statement", ticker, period, limit)
56
+ bs = fetch_fmp_data("balance-sheet-statement", ticker, period, limit)
57
+ cf = fetch_fmp_data("cash-flow-statement", ticker, period, limit)
58
+
59
+ income_statement = process_statement_df(inc)
60
+ balance_sheet = process_statement_df(bs)
61
+ cash_flow = process_statement_df(cf)
62
+ hist = fetch_historical_prices(ticker)
63
+
64
+ dates = set(income_statement.columns) | set(balance_sheet.columns) | set(cash_flow.columns)
65
+ end_prices = {}
66
+ for d in dates:
67
+ try:
68
+ dt = pd.to_datetime(d)
69
+ except Exception:
70
+ continue
71
+ price = get_closing_price_on_or_before(hist, dt)
72
+ end_prices[d] = price
73
+
74
+ return {
75
+ "income_statement": income_statement,
76
+ "balance_sheet": balance_sheet,
77
+ "cash_flow": cash_flow,
78
+ "end_prices": end_prices
79
+ }
80
+
81
+ # ---------------------------
82
+ # Analysis Functions
83
+ # ---------------------------
84
+ def extract_financial_data_for_column(income_statement, balance_sheet, col_label):
85
+ labels = {
86
+ "Net Income": ["netIncome"],
87
+ "EBT": ["incomeBeforeTax"],
88
+ "EBIT": ["operatingIncome"],
89
+ "Revenue": ["revenue"],
90
+ "Total Assets": ["totalAssets"],
91
+ "Total Equity": ["totalStockholdersEquity", "totalEquity"]
92
+ }
93
+ data = {}
94
+ for key, keys_list in labels.items():
95
+ value = None
96
+ if key in ["Net Income", "EBT", "EBIT", "Revenue"]:
97
+ for k in keys_list:
98
+ if k in income_statement.index and col_label in income_statement.columns:
99
+ value = income_statement.loc[k, col_label]
100
+ break
101
+ else:
102
+ for k in keys_list:
103
+ if k in balance_sheet.index and col_label in balance_sheet.columns:
104
+ value = balance_sheet.loc[k, col_label]
105
+ break
106
+ data[key] = value
107
+ return data
108
+
109
+ def compute_advanced_dupont_roe(fin_data):
110
+ net_income = fin_data.get("Net Income")
111
+ ebt = fin_data.get("EBT")
112
+ ebit = fin_data.get("EBIT")
113
+ revenue = fin_data.get("Revenue")
114
+ total_assets = fin_data.get("Total Assets")
115
+ total_equity = fin_data.get("Total Equity")
116
+
117
+ def safe_div(n, d):
118
+ return n / d if d and d != 0 else None
119
+
120
+ tax_burden = safe_div(net_income, ebt)
121
+ interest_burden = safe_div(ebt, ebit)
122
+ op_margin = safe_div(ebit, revenue)
123
+ asset_turnover = safe_div(revenue, total_assets)
124
+ equity_multiplier = safe_div(total_assets, total_equity)
125
+
126
+ if None in (tax_burden, interest_burden, op_margin, asset_turnover, equity_multiplier):
127
+ dupont_roe = None
128
+ else:
129
+ dupont_roe = tax_burden * interest_burden * op_margin * asset_turnover * equity_multiplier
130
+
131
+ return {
132
+ "Tax Burden": tax_burden,
133
+ "Interest Burden": interest_burden,
134
+ "Operating Margin": op_margin,
135
+ "Asset Turnover": asset_turnover,
136
+ "Equity Multiplier": equity_multiplier,
137
+ "Advanced DuPont ROE": dupont_roe
138
+ }
139
+
140
+ def dupont_analysis_over_time(income_statement, balance_sheet):
141
+ results = {}
142
+ for col in income_statement.columns:
143
+ fin_data = extract_financial_data_for_column(income_statement, balance_sheet, col)
144
+ results[col] = compute_advanced_dupont_roe(fin_data)
145
+ return pd.DataFrame(results)
146
+
147
+ def compute_equity_multiplier_details(balance_sheet):
148
+ asset_keys = ["totalAssets"]
149
+ equity_keys = ["totalStockholdersEquity", "totalEquity"]
150
+ liability_keys = ["totalLiabilities"]
151
+
152
+ def find_label(keys_list, df):
153
+ for k in keys_list:
154
+ if k in df.index:
155
+ return k
156
+ return None
157
+
158
+ asset_row = find_label(asset_keys, balance_sheet)
159
+ equity_row = find_label(equity_keys, balance_sheet)
160
+ liability_row = find_label(liability_keys, balance_sheet)
161
+
162
+ cols = balance_sheet.columns.tolist()
163
+ results = {
164
+ "Fiscal Year": [],
165
+ "Total Assets": [],
166
+ "Total Equity": [],
167
+ "Total Liabilities": [],
168
+ "Equity Multiplier": [],
169
+ "Debt to Equity": [],
170
+ "Assets YoY Change": [],
171
+ "Equity YoY Change": [],
172
+ "EM YoY Change": [],
173
+ "Debt/Equity YoY Change": []
174
+ }
175
+
176
+ prev_assets = prev_equity = prev_em = prev_de = None
177
+
178
+ def yoy_change(curr, prev):
179
+ if prev is None or pd.isna(curr) or pd.isna(prev) or prev == 0:
180
+ return None
181
+ return (curr - prev) / abs(prev)
182
+
183
+ for col in cols:
184
+ assets = balance_sheet.loc[asset_row, col] if asset_row and col in balance_sheet.columns else None
185
+ equity = balance_sheet.loc[equity_row, col] if equity_row and col in balance_sheet.columns else None
186
+ if liability_row and col in balance_sheet.columns:
187
+ liabilities = balance_sheet.loc[liability_row, col]
188
+ elif assets and equity:
189
+ liabilities = assets - equity
190
+ else:
191
+ liabilities = None
192
+
193
+ em = assets / equity if equity and equity != 0 else None
194
+ de = liabilities / equity if equity and equity != 0 else None
195
+
196
+ results["Fiscal Year"].append(col)
197
+ results["Total Assets"].append(assets)
198
+ results["Total Equity"].append(equity)
199
+ results["Total Liabilities"].append(liabilities)
200
+ results["Equity Multiplier"].append(em)
201
+ results["Debt to Equity"].append(de)
202
+
203
+ results["Assets YoY Change"].append(yoy_change(assets, prev_assets))
204
+ results["Equity YoY Change"].append(yoy_change(equity, prev_equity))
205
+ results["EM YoY Change"].append(yoy_change(em, prev_em))
206
+ results["Debt/Equity YoY Change"].append(yoy_change(de, prev_de))
207
+
208
+ prev_assets, prev_equity, prev_em, prev_de = assets, equity, em, de
209
+
210
+ return pd.DataFrame(results)
211
+
212
+ def compute_additional_metrics(income_statement, em_df):
213
+ df = em_df.copy()
214
+ df["Net Income"] = None
215
+ df["Interest Coverage Ratio"] = None
216
+ df["ROE"] = None
217
+
218
+ for idx, row in df.iterrows():
219
+ fy = row["Fiscal Year"]
220
+ net_income = None
221
+ if "netIncome" in income_statement.index and fy in income_statement.columns:
222
+ net_income = income_statement.loc["netIncome", fy]
223
+
224
+ ebit = None
225
+ if "operatingIncome" in income_statement.index and fy in income_statement.columns:
226
+ ebit = income_statement.loc["operatingIncome", fy]
227
+
228
+ interest_exp = None
229
+ if "interestExpense" in income_statement.index and fy in income_statement.columns:
230
+ interest_exp = income_statement.loc["interestExpense", fy]
231
+
232
+ icr = None
233
+ if ebit and interest_exp and interest_exp != 0:
234
+ icr = ebit / interest_exp
235
+
236
+ roe = None
237
+ if net_income and row["Total Equity"] and row["Total Equity"] != 0:
238
+ roe = net_income / row["Total Equity"]
239
+
240
+ df.at[idx, "Net Income"] = net_income
241
+ df.at[idx, "Interest Coverage Ratio"] = icr
242
+ df.at[idx, "ROE"] = roe
243
+
244
+ return df
245
+
246
+ def add_cash_flow_info(cash_flow_df, ext_df):
247
+ df = ext_df.copy()
248
+ df["Operating Cash Flow"] = None
249
+ df["CapEx"] = None
250
+
251
+ ocf_key = None
252
+ for key in ["totalCashFromOperatingActivities", "operatingCashFlow", "cashFlowFromOperatingActivities"]:
253
+ if key in cash_flow_df.index:
254
+ ocf_key = key
255
+ break
256
+
257
+ capex_key = None
258
+ for key in ["capitalExpenditure", "capitalExpenditures", "capex", "investingCapEx"]:
259
+ if key in cash_flow_df.index:
260
+ capex_key = key
261
+ break
262
+
263
+ for idx, row in df.iterrows():
264
+ fy = row["Fiscal Year"]
265
+
266
+ ocf = None
267
+ if ocf_key and fy in cash_flow_df.columns:
268
+ ocf = cash_flow_df.loc[ocf_key, fy]
269
+
270
+ capex = None
271
+ if capex_key and fy in cash_flow_df.columns:
272
+ capex = cash_flow_df.loc[capex_key, fy]
273
+
274
+ df.at[idx, "Operating Cash Flow"] = ocf
275
+ df.at[idx, "CapEx"] = capex
276
+
277
+ return df
278
+
279
+ # ---------------------------
280
+ # Plotting Functions
281
+ # ---------------------------
282
+ def plot_dupont_results(dupont_df):
283
+ df = dupont_df.transpose()
284
+ df.index = pd.to_datetime(df.index)
285
+ df.sort_index(inplace=True)
286
+ dates = df.index.strftime('%Y-%m-%d')
287
+ components = ["Tax Burden", "Interest Burden", "Operating Margin", "Asset Turnover", "Equity Multiplier"]
288
+
289
+ fig = make_subplots(specs=[[{"secondary_y": True}]])
290
+ for comp in components:
291
+ fig.add_trace(go.Bar(x=dates, y=df[comp], name=comp), secondary_y=False)
292
+
293
+ fig.add_trace(
294
+ go.Scatter(
295
+ x=dates,
296
+ y=df["Advanced DuPont ROE"],
297
+ mode="lines+markers",
298
+ name="Advanced DuPont ROE"
299
+ ),
300
+ secondary_y=True
301
+ )
302
+ fig.update_layout(
303
+ title="DuPont Components Over Time",
304
+ xaxis_title="Fiscal Period",
305
+ barmode="group"
306
+ )
307
+ fig.update_yaxes(title_text="Advanced DuPont ROE", secondary_y=True)
308
+ st.plotly_chart(fig, use_container_width=True)
309
+
310
+ def plot_leverage_metrics_plotly(em_df, end_prices):
311
+ em_df["Date"] = pd.to_datetime(em_df["Fiscal Year"])
312
+ em_df.sort_values("Date", inplace=True)
313
+ dates = em_df["Date"].dt.strftime('%Y-%m-%d')
314
+
315
+ trace_assets = go.Scatter(x=dates, y=em_df["Total Assets"], mode='lines+markers', name="Total Assets")
316
+ trace_equity = go.Scatter(x=dates, y=em_df["Total Equity"], mode='lines+markers', name="Total Equity")
317
+ trace_em = go.Scatter(x=dates, y=em_df["Equity Multiplier"], mode='lines+markers', name="Equity Multiplier", yaxis="y2")
318
+ trace_de = go.Scatter(x=dates, y=em_df["Debt to Equity"], mode='lines+markers', name="Debt to Equity", yaxis="y2")
319
+
320
+ stock_prices = [end_prices.get(fy, None) for fy in em_df["Fiscal Year"]]
321
+ trace_sp = go.Scatter(x=dates, y=stock_prices, mode='lines+markers', name="Stock Price", yaxis="y3")
322
+
323
+ fig = make_subplots(specs=[[{"secondary_y": True}]])
324
+ fig.add_trace(trace_assets)
325
+ fig.add_trace(trace_equity)
326
+ fig.add_trace(trace_em, secondary_y=True)
327
+ fig.add_trace(trace_de, secondary_y=True)
328
+ fig.add_trace(trace_sp)
329
+
330
+ fig.update_layout(
331
+ title="Leverage & Stock Price",
332
+ xaxis_title="Fiscal Year"
333
+ )
334
+ fig.update_yaxes(title_text="Assets & Equity", secondary_y=False)
335
+ fig.update_yaxes(title_text="Leverage Ratios", secondary_y=True)
336
+ fig.update_layout(
337
+ yaxis3=dict(
338
+ title="Stock Price (USD)",
339
+ overlaying="y",
340
+ side="right",
341
+ position=0.95
342
+ )
343
+ )
344
+ fig.data[-1].update(yaxis="y3")
345
+ st.plotly_chart(fig, use_container_width=True)
346
+
347
+ def plot_combined_metrics_plotly(ext_df, end_prices):
348
+ ext_df["Date"] = pd.to_datetime(ext_df["Fiscal Year"])
349
+ ext_df.sort_values("Date", inplace=True)
350
+ dates = ext_df["Date"].dt.strftime('%Y-%m-%d')
351
+
352
+ trace_net = go.Bar(x=dates, y=ext_df["Net Income"], name="Net Income")
353
+ trace_ocf = go.Bar(x=dates, y=ext_df["Operating Cash Flow"], name="Op. Cash Flow")
354
+ trace_capex = go.Bar(x=dates, y=ext_df["CapEx"], name="CapEx")
355
+
356
+ trace_roe = go.Scatter(x=dates, y=ext_df["ROE"], mode='lines+markers', name="ROE", yaxis="y2")
357
+ trace_icr = go.Scatter(x=dates, y=ext_df["Interest Coverage Ratio"], mode='lines+markers', name="ICR", yaxis="y2")
358
+
359
+ stock_prices = [end_prices.get(fy, None) for fy in ext_df["Fiscal Year"]]
360
+ trace_sp = go.Scatter(x=dates, y=stock_prices, mode='lines+markers', name="Stock Price", yaxis="y3")
361
+
362
+ fig = make_subplots(specs=[[{"secondary_y": True}]])
363
+ fig.add_trace(trace_net, secondary_y=False)
364
+ fig.add_trace(trace_ocf, secondary_y=False)
365
+ fig.add_trace(trace_capex, secondary_y=False)
366
+ fig.add_trace(trace_roe, secondary_y=True)
367
+ fig.add_trace(trace_icr, secondary_y=True)
368
+ fig.add_trace(trace_sp)
369
+
370
+ fig.update_layout(
371
+ title="Net Income, Cash Flow & ROE",
372
+ xaxis_title="Fiscal Year",
373
+ barmode="group"
374
+ )
375
+ fig.update_yaxes(title_text="Values (USD)", secondary_y=False)
376
+ fig.update_yaxes(title_text="Ratios", secondary_y=True)
377
+ fig.update_layout(
378
+ yaxis3=dict(
379
+ title="Stock Price (USD)",
380
+ overlaying="y",
381
+ side="right",
382
+ position=0.95
383
+ )
384
+ )
385
+ fig.data[-1].update(yaxis="y3")
386
+ st.plotly_chart(fig, use_container_width=True)
387
+
388
+ # ---------------------------
389
+ # Streamlit App Layout & Sidebar
390
+ # ---------------------------
391
+ st.set_page_config(layout="wide", page_title="ROE Decomposition Dashboard")
392
+
393
+ st.title("ROE Decomposition")
394
+ st.markdown("""
395
+ This application deconstructs return on equity using an advanced DuPont analysis approach.
396
+ It examines how profitability, leverage, and operational efficiency contribute to ROE over time.
397
+ """)
398
+
399
+ with st.expander("Advanced DuPont Analysis Explanation", expanded=False):
400
+ st.markdown("The Advanced DuPont Analysis breaks down ROE into multiple components:")
401
+ st.latex(r"\text{ROE} = \text{Tax Burden} \times \text{Interest Burden} \times \text{Operating Margin} \times \text{Asset Turnover} \times \text{Equity Multiplier}")
402
+ st.markdown("Where:")
403
+ st.latex(r"\text{Tax Burden} = \frac{\text{Net Income}}{\text{EBT}}")
404
+ st.latex(r"\text{Interest Burden} = \frac{\text{EBT}}{\text{EBIT}}")
405
+ st.latex(r"\text{Operating Margin} = \frac{\text{EBIT}}{\text{Revenue}}")
406
+ st.latex(r"\text{Asset Turnover} = \frac{\text{Revenue}}{\text{Total Assets}}")
407
+ st.latex(r"\text{Equity Multiplier} = \frac{\text{Total Assets}}{\text{Total Equity}}")
408
+ st.markdown("This breakdown allows analysts to pinpoint whether changes in ROE are driven by tax factors, operating performance, asset efficiency, or financial leverage.")
409
+
410
+ #st.sidebar.header("User Inputs")
411
+
412
+ with st.sidebar.expander("Data Options", expanded=True):
413
+ ticker = st.text_input(
414
+ "Ticker Symbol",
415
+ value="AAPL",
416
+ help="Example: AAPL, TSLA, GOOG, etc."
417
+ )
418
+ period_type = st.selectbox(
419
+ "Select Data Period",
420
+ options=["annual", "quarter"],
421
+ help="Choose annual or quarterly data."
422
+ )
423
+ limit_periods = st.number_input(
424
+ "Number of Periods",
425
+ min_value=1,
426
+ max_value=20,
427
+ value=10,
428
+ help="Number of consecutive periods to analyze."
429
+ )
430
+
431
+ run_analysis = st.sidebar.button("Run Analysis", help="Fetch data and generate charts.")
432
+
433
+ if run_analysis:
434
+ with st.spinner("Fetching and processing data..."):
435
+ data = fetch_all_data(ticker=ticker, period=period_type, limit=limit_periods)
436
+ inc_stmt = data["income_statement"]
437
+ bs = data["balance_sheet"]
438
+ cf = data["cash_flow"]
439
+ prices = data["end_prices"]
440
+
441
+ if inc_stmt.empty or bs.empty or cf.empty:
442
+ st.error("One or more data sets are empty. Check inputs and try again.")
443
+ else:
444
+ # DuPont Analysis
445
+ st.subheader("Advanced DuPont Analysis")
446
+ st.markdown("Breaks down ROE into tax, interest, margin, turnover, and leverage factors.")
447
+ dupont_df = dupont_analysis_over_time(inc_stmt, bs)
448
+ plot_dupont_results(dupont_df)
449
+
450
+ with st.expander("Dynamic Interpretation: DuPont Analysis", expanded=False):
451
+ try:
452
+ # Sort periods and extract the latest period's data
453
+ sorted_periods = sorted(dupont_df.columns)
454
+ latest_period = sorted_periods[-1]
455
+ latest_data = dupont_df[latest_period]
456
+ advanced_roe = latest_data.get("Advanced DuPont ROE", None)
457
+
458
+ st.markdown(f"**Latest Period:** {latest_period}")
459
+ if advanced_roe is not None:
460
+ st.markdown(f"**Advanced DuPont ROE:** {advanced_roe:.2f}")
461
+ else:
462
+ st.markdown("**Advanced DuPont ROE:** Data unavailable.")
463
+
464
+ # Year-over-year change
465
+ if len(sorted_periods) > 1:
466
+ prev_period = sorted_periods[-2]
467
+ prev_roe = dupont_df[prev_period].get("Advanced DuPont ROE", None)
468
+ if prev_roe and prev_roe != 0 and advanced_roe is not None:
469
+ yoy_change = (advanced_roe - prev_roe) / abs(prev_roe)
470
+ st.markdown(f"**Year-over-Year ROE Change:** {(yoy_change * 100):.2f}%")
471
+ else:
472
+ st.markdown("Year-over-year comparison unavailable due to missing data.")
473
+
474
+ st.markdown("##### Key Drivers for the Latest Period:")
475
+
476
+ # Tax Burden interpretation
477
+ tb = latest_data.get("Tax Burden", None)
478
+ if tb is not None:
479
+ st.markdown(f"- **Tax Burden:** {tb:.2f}")
480
+ if tb < 0.8:
481
+ st.markdown(" - A lower tax burden means the firm retains a larger share of its pre-tax income, supporting profitability.")
482
+ else:
483
+ st.markdown(" - A higher tax burden indicates significant tax expense, which may erode net profit.")
484
+ else:
485
+ st.markdown("- **Tax Burden:** Data unavailable.")
486
+
487
+ # Interest Burden interpretation
488
+ ib = latest_data.get("Interest Burden", None)
489
+ if ib is not None:
490
+ st.markdown(f"- **Interest Burden:** {ib:.2f}")
491
+ if ib >= 0.9:
492
+ st.markdown(" - An interest burden near 1 shows that interest expenses have minimal impact on pre-tax income.")
493
+ else:
494
+ st.markdown(" - A lower interest burden suggests that interest expenses significantly reduce pre-tax income.")
495
+ else:
496
+ st.markdown("- **Interest Burden:** Data unavailable.")
497
+
498
+ # Operating Margin interpretation
499
+ opm = latest_data.get("Operating Margin", None)
500
+ if opm is not None:
501
+ st.markdown(f"- **Operating Margin:** {opm:.2f}")
502
+ if opm > 0.15:
503
+ st.markdown(" - A strong operating margin reflects efficient core operations.")
504
+ else:
505
+ st.markdown(" - A low operating margin could signal operational inefficiencies.")
506
+ else:
507
+ st.markdown("- **Operating Margin:** Data unavailable.")
508
+
509
+ # Asset Turnover interpretation
510
+ at = latest_data.get("Asset Turnover", None)
511
+ if at is not None:
512
+ st.markdown(f"- **Asset Turnover:** {at:.2f}")
513
+ if at > 1:
514
+ st.markdown(" - Higher asset turnover indicates efficient utilization of assets to generate revenue.")
515
+ else:
516
+ st.markdown(" - Lower asset turnover may point to underutilized assets.")
517
+ else:
518
+ st.markdown("- **Asset Turnover:** Data unavailable.")
519
+
520
+ # Equity Multiplier interpretation
521
+ em = latest_data.get("Equity Multiplier", None)
522
+ if em is not None:
523
+ st.markdown(f"- **Equity Multiplier:** {em:.2f}")
524
+ if em > 2:
525
+ st.markdown(" - A high equity multiplier suggests that the company is leveraging debt to boost ROE.")
526
+ else:
527
+ st.markdown(" - A low equity multiplier indicates a more conservative financing structure.")
528
+ else:
529
+ st.markdown("- **Equity Multiplier:** Data unavailable.")
530
+
531
+ # Overall conclusion based on ROE
532
+ if advanced_roe is not None:
533
+ st.markdown("##### Overall Conclusion:")
534
+ if advanced_roe < 0.05:
535
+ st.markdown("The overall ROE is relatively low. This may be driven by high tax/interest burdens or operational inefficiencies.")
536
+ elif advanced_roe < 0.15:
537
+ st.markdown("The ROE is moderate. There are areas of strength, yet there remains room for improvement in efficiency or leveraging assets.")
538
+ else:
539
+ st.markdown("The ROE is strong, indicating robust operational efficiency and effective use of leverage.")
540
+ except Exception as e:
541
+ st.error("Dynamic interpretation unavailable for DuPont analysis.")
542
+
543
+
544
+ # Leverage & Equity Analysis
545
+ st.subheader("Leverage & Equity Analysis")
546
+ st.markdown("Shows how leverage metrics and equity levels change. Also links each period's stock price.")
547
+ em_df = compute_equity_multiplier_details(bs)
548
+ plot_leverage_metrics_plotly(em_df, prices)
549
+
550
+ with st.expander("Dynamic Interpretation: Leverage & Equity", expanded=False):
551
+ try:
552
+ # Sort fiscal years and extract the latest period's data
553
+ sorted_fy = sorted(em_df["Fiscal Year"])
554
+ latest_fy = sorted_fy[-1]
555
+ latest_row = em_df[em_df["Fiscal Year"] == latest_fy].iloc[0]
556
+ de = latest_row.get("Debt to Equity", None)
557
+ em_ratio = latest_row.get("Equity Multiplier", None)
558
+
559
+ st.markdown(f"**Latest Period:** {latest_fy}")
560
+ if de is not None:
561
+ st.markdown(f"**Debt to Equity:** {de:.2f}")
562
+ else:
563
+ st.markdown("**Debt to Equity:** Data unavailable.")
564
+ if em_ratio is not None:
565
+ st.markdown(f"**Equity Multiplier:** {em_ratio:.2f}")
566
+ else:
567
+ st.markdown("**Equity Multiplier:** Data unavailable.")
568
+
569
+ # Calculate Year-over-Year changes if available
570
+ if len(sorted_fy) > 1:
571
+ prev_fy = sorted_fy[-2]
572
+ prev_row = em_df[em_df["Fiscal Year"] == prev_fy].iloc[0]
573
+ prev_de = prev_row.get("Debt to Equity", None)
574
+ prev_em = prev_row.get("Equity Multiplier", None)
575
+ if prev_de and de is not None and prev_de != 0:
576
+ yoy_de_change = (de - prev_de) / abs(prev_de)
577
+ st.markdown(f"**YoY Debt to Equity Change:** {(yoy_de_change * 100):.2f}%")
578
+ else:
579
+ st.markdown("YoY Debt to Equity change unavailable.")
580
+ if prev_em and em_ratio is not None and prev_em != 0:
581
+ yoy_em_change = (em_ratio - prev_em) / abs(prev_em)
582
+ st.markdown(f"**YoY Equity Multiplier Change:** {(yoy_em_change * 100):.2f}%")
583
+ else:
584
+ st.markdown("YoY Equity Multiplier change unavailable.")
585
+
586
+ st.markdown("##### Detailed Interpretation:")
587
+ # Detailed interpretation for Debt-to-Equity
588
+ if de is not None:
589
+ if de < 1:
590
+ st.markdown("- **Low Debt to Equity:** The firm relies more on equity financing. This typically indicates lower financial risk and a conservative capital structure.")
591
+ elif 1 <= de < 2:
592
+ st.markdown("- **Moderate Debt to Equity:** The company maintains a balanced mix of debt and equity financing. This level may optimize returns while keeping risk manageable.")
593
+ else:
594
+ st.markdown("- **High Debt to Equity:** A high ratio suggests significant reliance on debt, which can amplify returns but also increases financial risk, especially in volatile market conditions.")
595
+ else:
596
+ st.markdown("- **Debt to Equity data is missing.**")
597
+
598
+ # Detailed interpretation for Equity Multiplier
599
+ if em_ratio is not None:
600
+ if em_ratio < 1.5:
601
+ st.markdown("- **Low Equity Multiplier:** Indicates limited use of debt in financing assets, reflecting a conservative approach.")
602
+ elif 1.5 <= em_ratio < 2.5:
603
+ st.markdown("- **Moderate Equity Multiplier:** Suggests a balanced approach to leveraging, combining both debt and equity to finance assets.")
604
+ else:
605
+ st.markdown("- **High Equity Multiplier:** Indicates aggressive use of debt financing. While this can enhance ROE, it also raises the firm's exposure to interest rate fluctuations and market downturns.")
606
+ else:
607
+ st.markdown("- **Equity Multiplier data is missing.**")
608
+ except Exception:
609
+ st.error("Dynamic interpretation unavailable for leverage & equity.")
610
+
611
+
612
+ # Combined Cash Flow and Profitability Metrics
613
+ st.subheader("Combined Cash Flow & ROE")
614
+ st.markdown("Shows net income, operating cash flow, CapEx, and return metrics together.")
615
+ ext_df = compute_equity_multiplier_details(bs)
616
+ ext_df = compute_additional_metrics(inc_stmt, ext_df)
617
+ ext_df = add_cash_flow_info(cf, ext_df)
618
+ plot_combined_metrics_plotly(ext_df, prices)
619
+
620
+ with st.expander("Dynamic Interpretation: Combined Metrics", expanded=False):
621
+ try:
622
+ # Get sorted fiscal years and pick the latest period
623
+ sorted_fy = sorted(ext_df["Fiscal Year"])
624
+ latest_fy = sorted_fy[-1]
625
+ latest_row = ext_df[ext_df["Fiscal Year"] == latest_fy].iloc[0]
626
+
627
+ # Extract key metrics for the latest period
628
+ net_income = latest_row.get("Net Income", None)
629
+ op_cf = latest_row.get("Operating Cash Flow", None)
630
+ capex = latest_row.get("CapEx", None)
631
+ roe = latest_row.get("ROE", None)
632
+
633
+ st.markdown(f"**Latest Period:** {latest_fy}")
634
+ if net_income is not None:
635
+ st.markdown(f"- **Net Income:** {net_income:.2f}")
636
+ else:
637
+ st.markdown("- **Net Income:** Data unavailable")
638
+ if op_cf is not None:
639
+ st.markdown(f"- **Operating Cash Flow:** {op_cf:.2f}")
640
+ else:
641
+ st.markdown("- **Operating Cash Flow:** Data unavailable")
642
+ if capex is not None:
643
+ st.markdown(f"- **CapEx:** {capex:.2f}")
644
+ else:
645
+ st.markdown("- **CapEx:** Data unavailable")
646
+ if roe is not None:
647
+ st.markdown(f"- **ROE:** {roe:.2f} _(Higher ROE typically indicates better efficiency in generating returns)_")
648
+ else:
649
+ st.markdown("- **ROE:** Data unavailable")
650
+
651
+ st.markdown("##### Detailed Analysis:")
652
+
653
+ # Compare Operating Cash Flow to Net Income
654
+ if net_income is not None and op_cf is not None:
655
+ if op_cf < net_income:
656
+ st.markdown(
657
+ "• **Operating Cash Flow is lower than Net Income.** This may indicate that non-cash items are inflating net income or that the company has challenges converting profits into cash. It might warrant a closer look at working capital management."
658
+ )
659
+ else:
660
+ st.markdown(
661
+ "• **Operating Cash Flow exceeds Net Income.** This suggests strong cash conversion from operations, which is a positive indicator of liquidity and operational efficiency."
662
+ )
663
+ else:
664
+ st.markdown("• **Operating Cash Flow vs. Net Income:** Insufficient data for comparison.")
665
+
666
+ # Evaluate CapEx relative to Net Income
667
+ if capex is not None and net_income is not None:
668
+ capex_ratio = capex / net_income if net_income != 0 else None
669
+ if capex_ratio is not None:
670
+ st.markdown(f"• **CapEx to Net Income Ratio:** {capex_ratio:.2f}")
671
+ if capex_ratio > 1:
672
+ st.markdown(
673
+ " - **High CapEx:** The company is investing heavily in fixed assets. While this can drive future growth, it may suppress short-term profitability."
674
+ " - **High CapEx:** The company is investing heavily in fixed assets. While this can drive future growth, it may suppress short-term profitability."
675
+ )
676
+ else:
677
+ st.markdown(
678
+ " - **Moderate CapEx:** Investment levels appear balanced relative to net income, which may support sustainable growth without overly impacting current profits."
679
+ )
680
+ else:
681
+ st.markdown("• **CapEx Analysis:** Unable to compute ratio due to zero or missing net income.")
682
+ else:
683
+ st.markdown("• **CapEx Analysis:** Insufficient data for evaluation.")
684
+
685
+ # Year-over-Year comparison for Net Income
686
+ if len(sorted_fy) > 1 and net_income is not None:
687
+ prev_fy = sorted_fy[-2]
688
+ prev_row = ext_df[ext_df["Fiscal Year"] == prev_fy].iloc[0]
689
+ prev_net = prev_row.get("Net Income", None)
690
+ if prev_net is not None and prev_net != 0:
691
+ net_yoy = (net_income - prev_net) / abs(prev_net)
692
+ st.markdown(f"• **Year-over-Year Net Income Change:** {(net_yoy * 100):.2f}%")
693
+ if net_yoy > 0:
694
+ st.markdown(" - Net income has increased compared to the previous period, indicating potential growth or improved efficiency.")
695
+ else:
696
+ st.markdown(" - Net income has declined compared to the previous period, which may signal operational challenges or increased expenses.")
697
+ else:
698
+ st.markdown("• **Year-over-Year Net Income Change:** Data unavailable for previous period.")
699
+ else:
700
+ st.markdown("• **Year-over-Year Comparison:** Not enough periods to compute change.")
701
+
702
+ except Exception as e:
703
+ st.error("Dynamic interpretation unavailable for combined metrics.")
704
+
705
+
706
+
707
+
708
+ else:
709
+ st.info("Set your inputs and click Run Analysis.")
710
+ hide_streamlit_style = """
711
+ <style>
712
+ #MainMenu {visibility: hidden;}
713
+ footer {visibility: hidden;}
714
+ </style>
715
+ """
716
+ st.markdown(hide_streamlit_style, unsafe_allow_html=True)