Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import requests | |
| import pandas as pd | |
| import plotly.graph_objs as go | |
| from plotly.subplots import make_subplots | |
| import os | |
| # --------------------------- | |
| # Global API Configuration (hidden) | |
| # --------------------------- | |
| API_KEY = os.getenv("FMP_API_KEY") | |
| BASE_URL = "https://financialmodelingprep.com/api/v3/" | |
| # --------------------------- | |
| # Data Fetching Functions | |
| # --------------------------- | |
| def fetch_fmp_data(endpoint, ticker, period="annual", limit=10): | |
| url = f"{BASE_URL}{endpoint}/{ticker}" | |
| params = {"period": period, "limit": limit, "apikey": API_KEY} | |
| r = requests.get(url, params=params) | |
| if r.status_code == 200: | |
| return pd.DataFrame(r.json()) | |
| else: | |
| st.error("Data retrieval error. Check inputs or try again later.") | |
| return pd.DataFrame() | |
| def process_statement_df(df, date_col="date"): | |
| if df.empty: | |
| return df | |
| df[date_col] = pd.to_datetime(df[date_col]) | |
| df.set_index(date_col, inplace=True) | |
| return df.transpose() | |
| def fetch_historical_prices(ticker): | |
| url = f"{BASE_URL}historical-price-full/{ticker}" | |
| params = {"serietype": "line", "apikey": API_KEY} | |
| r = requests.get(url, params=params) | |
| if r.status_code == 200: | |
| data = r.json() | |
| if "historical" in data: | |
| hist_df = pd.DataFrame(data["historical"]) | |
| hist_df["date"] = pd.to_datetime(hist_df["date"]) | |
| hist_df.sort_values("date", inplace=True) | |
| return hist_df | |
| st.error("Unable to fetch historical prices. Try again later.") | |
| return pd.DataFrame() | |
| def get_closing_price_on_or_before(hist_df, date): | |
| df = hist_df[hist_df["date"] <= date] | |
| if not df.empty: | |
| return df.iloc[-1]["close"] | |
| return None | |
| def fetch_all_data(ticker="AAPL", period="annual", limit=10): | |
| inc = fetch_fmp_data("income-statement", ticker, period, limit) | |
| bs = fetch_fmp_data("balance-sheet-statement", ticker, period, limit) | |
| cf = fetch_fmp_data("cash-flow-statement", ticker, period, limit) | |
| income_statement = process_statement_df(inc) | |
| balance_sheet = process_statement_df(bs) | |
| cash_flow = process_statement_df(cf) | |
| hist = fetch_historical_prices(ticker) | |
| dates = set(income_statement.columns) | set(balance_sheet.columns) | set(cash_flow.columns) | |
| end_prices = {} | |
| for d in dates: | |
| try: | |
| dt = pd.to_datetime(d) | |
| except Exception: | |
| continue | |
| price = get_closing_price_on_or_before(hist, dt) | |
| end_prices[d] = price | |
| return { | |
| "income_statement": income_statement, | |
| "balance_sheet": balance_sheet, | |
| "cash_flow": cash_flow, | |
| "end_prices": end_prices | |
| } | |
| # --------------------------- | |
| # Analysis Functions | |
| # --------------------------- | |
| def extract_financial_data_for_column(income_statement, balance_sheet, col_label): | |
| labels = { | |
| "Net Income": ["netIncome"], | |
| "EBT": ["incomeBeforeTax"], | |
| "EBIT": ["operatingIncome"], | |
| "Revenue": ["revenue"], | |
| "Total Assets": ["totalAssets"], | |
| "Total Equity": ["totalStockholdersEquity", "totalEquity"] | |
| } | |
| data = {} | |
| for key, keys_list in labels.items(): | |
| value = None | |
| if key in ["Net Income", "EBT", "EBIT", "Revenue"]: | |
| for k in keys_list: | |
| if k in income_statement.index and col_label in income_statement.columns: | |
| value = income_statement.loc[k, col_label] | |
| break | |
| else: | |
| for k in keys_list: | |
| if k in balance_sheet.index and col_label in balance_sheet.columns: | |
| value = balance_sheet.loc[k, col_label] | |
| break | |
| data[key] = value | |
| return data | |
| def compute_advanced_dupont_roe(fin_data): | |
| net_income = fin_data.get("Net Income") | |
| ebt = fin_data.get("EBT") | |
| ebit = fin_data.get("EBIT") | |
| revenue = fin_data.get("Revenue") | |
| total_assets = fin_data.get("Total Assets") | |
| total_equity = fin_data.get("Total Equity") | |
| def safe_div(n, d): | |
| return n / d if d and d != 0 else None | |
| tax_burden = safe_div(net_income, ebt) | |
| interest_burden = safe_div(ebt, ebit) | |
| op_margin = safe_div(ebit, revenue) | |
| asset_turnover = safe_div(revenue, total_assets) | |
| equity_multiplier = safe_div(total_assets, total_equity) | |
| if None in (tax_burden, interest_burden, op_margin, asset_turnover, equity_multiplier): | |
| dupont_roe = None | |
| else: | |
| dupont_roe = tax_burden * interest_burden * op_margin * asset_turnover * equity_multiplier | |
| return { | |
| "Tax Burden": tax_burden, | |
| "Interest Burden": interest_burden, | |
| "Operating Margin": op_margin, | |
| "Asset Turnover": asset_turnover, | |
| "Equity Multiplier": equity_multiplier, | |
| "Advanced DuPont ROE": dupont_roe | |
| } | |
| def dupont_analysis_over_time(income_statement, balance_sheet): | |
| results = {} | |
| for col in income_statement.columns: | |
| fin_data = extract_financial_data_for_column(income_statement, balance_sheet, col) | |
| results[col] = compute_advanced_dupont_roe(fin_data) | |
| return pd.DataFrame(results) | |
| def compute_equity_multiplier_details(balance_sheet): | |
| asset_keys = ["totalAssets"] | |
| equity_keys = ["totalStockholdersEquity", "totalEquity"] | |
| liability_keys = ["totalLiabilities"] | |
| def find_label(keys_list, df): | |
| for k in keys_list: | |
| if k in df.index: | |
| return k | |
| return None | |
| asset_row = find_label(asset_keys, balance_sheet) | |
| equity_row = find_label(equity_keys, balance_sheet) | |
| liability_row = find_label(liability_keys, balance_sheet) | |
| cols = balance_sheet.columns.tolist() | |
| results = { | |
| "Fiscal Year": [], | |
| "Total Assets": [], | |
| "Total Equity": [], | |
| "Total Liabilities": [], | |
| "Equity Multiplier": [], | |
| "Debt to Equity": [], | |
| "Assets YoY Change": [], | |
| "Equity YoY Change": [], | |
| "EM YoY Change": [], | |
| "Debt/Equity YoY Change": [] | |
| } | |
| prev_assets = prev_equity = prev_em = prev_de = None | |
| def yoy_change(curr, prev): | |
| if prev is None or pd.isna(curr) or pd.isna(prev) or prev == 0: | |
| return None | |
| return (curr - prev) / abs(prev) | |
| for col in cols: | |
| assets = balance_sheet.loc[asset_row, col] if asset_row and col in balance_sheet.columns else None | |
| equity = balance_sheet.loc[equity_row, col] if equity_row and col in balance_sheet.columns else None | |
| if liability_row and col in balance_sheet.columns: | |
| liabilities = balance_sheet.loc[liability_row, col] | |
| elif assets and equity: | |
| liabilities = assets - equity | |
| else: | |
| liabilities = None | |
| em = assets / equity if equity and equity != 0 else None | |
| de = liabilities / equity if equity and equity != 0 else None | |
| results["Fiscal Year"].append(col) | |
| results["Total Assets"].append(assets) | |
| results["Total Equity"].append(equity) | |
| results["Total Liabilities"].append(liabilities) | |
| results["Equity Multiplier"].append(em) | |
| results["Debt to Equity"].append(de) | |
| results["Assets YoY Change"].append(yoy_change(assets, prev_assets)) | |
| results["Equity YoY Change"].append(yoy_change(equity, prev_equity)) | |
| results["EM YoY Change"].append(yoy_change(em, prev_em)) | |
| results["Debt/Equity YoY Change"].append(yoy_change(de, prev_de)) | |
| prev_assets, prev_equity, prev_em, prev_de = assets, equity, em, de | |
| return pd.DataFrame(results) | |
| def compute_additional_metrics(income_statement, em_df): | |
| df = em_df.copy() | |
| df["Net Income"] = None | |
| df["Interest Coverage Ratio"] = None | |
| df["ROE"] = None | |
| for idx, row in df.iterrows(): | |
| fy = row["Fiscal Year"] | |
| net_income = None | |
| if "netIncome" in income_statement.index and fy in income_statement.columns: | |
| net_income = income_statement.loc["netIncome", fy] | |
| ebit = None | |
| if "operatingIncome" in income_statement.index and fy in income_statement.columns: | |
| ebit = income_statement.loc["operatingIncome", fy] | |
| interest_exp = None | |
| if "interestExpense" in income_statement.index and fy in income_statement.columns: | |
| interest_exp = income_statement.loc["interestExpense", fy] | |
| icr = None | |
| if ebit and interest_exp and interest_exp != 0: | |
| icr = ebit / interest_exp | |
| roe = None | |
| if net_income and row["Total Equity"] and row["Total Equity"] != 0: | |
| roe = net_income / row["Total Equity"] | |
| df.at[idx, "Net Income"] = net_income | |
| df.at[idx, "Interest Coverage Ratio"] = icr | |
| df.at[idx, "ROE"] = roe | |
| return df | |
| def add_cash_flow_info(cash_flow_df, ext_df): | |
| df = ext_df.copy() | |
| df["Operating Cash Flow"] = None | |
| df["CapEx"] = None | |
| ocf_key = None | |
| for key in ["totalCashFromOperatingActivities", "operatingCashFlow", "cashFlowFromOperatingActivities"]: | |
| if key in cash_flow_df.index: | |
| ocf_key = key | |
| break | |
| capex_key = None | |
| for key in ["capitalExpenditure", "capitalExpenditures", "capex", "investingCapEx"]: | |
| if key in cash_flow_df.index: | |
| capex_key = key | |
| break | |
| for idx, row in df.iterrows(): | |
| fy = row["Fiscal Year"] | |
| ocf = None | |
| if ocf_key and fy in cash_flow_df.columns: | |
| ocf = cash_flow_df.loc[ocf_key, fy] | |
| capex = None | |
| if capex_key and fy in cash_flow_df.columns: | |
| capex = cash_flow_df.loc[capex_key, fy] | |
| df.at[idx, "Operating Cash Flow"] = ocf | |
| df.at[idx, "CapEx"] = capex | |
| return df | |
| # --------------------------- | |
| # Plotting Functions | |
| # --------------------------- | |
| def plot_dupont_results(dupont_df): | |
| df = dupont_df.transpose() | |
| df.index = pd.to_datetime(df.index) | |
| df.sort_index(inplace=True) | |
| dates = df.index.strftime('%Y-%m-%d') | |
| components = ["Tax Burden", "Interest Burden", "Operating Margin", "Asset Turnover", "Equity Multiplier"] | |
| fig = make_subplots(specs=[[{"secondary_y": True}]]) | |
| for comp in components: | |
| fig.add_trace(go.Bar(x=dates, y=df[comp], name=comp), secondary_y=False) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=dates, | |
| y=df["Advanced DuPont ROE"], | |
| mode="lines+markers", | |
| name="Advanced DuPont ROE" | |
| ), | |
| secondary_y=True | |
| ) | |
| fig.update_layout( | |
| title="DuPont Components Over Time", | |
| xaxis_title="Fiscal Period", | |
| barmode="group" | |
| ) | |
| fig.update_yaxes(title_text="Advanced DuPont ROE", secondary_y=True) | |
| st.plotly_chart(fig, use_container_width=True) | |
| def plot_leverage_metrics_plotly(em_df, end_prices): | |
| em_df["Date"] = pd.to_datetime(em_df["Fiscal Year"]) | |
| em_df.sort_values("Date", inplace=True) | |
| dates = em_df["Date"].dt.strftime('%Y-%m-%d') | |
| trace_assets = go.Scatter(x=dates, y=em_df["Total Assets"], mode='lines+markers', name="Total Assets") | |
| trace_equity = go.Scatter(x=dates, y=em_df["Total Equity"], mode='lines+markers', name="Total Equity") | |
| trace_em = go.Scatter(x=dates, y=em_df["Equity Multiplier"], mode='lines+markers', name="Equity Multiplier", yaxis="y2") | |
| trace_de = go.Scatter(x=dates, y=em_df["Debt to Equity"], mode='lines+markers', name="Debt to Equity", yaxis="y2") | |
| stock_prices = [end_prices.get(fy, None) for fy in em_df["Fiscal Year"]] | |
| trace_sp = go.Scatter(x=dates, y=stock_prices, mode='lines+markers', name="Stock Price", yaxis="y3") | |
| fig = make_subplots(specs=[[{"secondary_y": True}]]) | |
| fig.add_trace(trace_assets) | |
| fig.add_trace(trace_equity) | |
| fig.add_trace(trace_em, secondary_y=True) | |
| fig.add_trace(trace_de, secondary_y=True) | |
| fig.add_trace(trace_sp) | |
| fig.update_layout( | |
| title="Leverage & Stock Price", | |
| xaxis_title="Fiscal Year" | |
| ) | |
| fig.update_yaxes(title_text="Assets & Equity", secondary_y=False) | |
| fig.update_yaxes(title_text="Leverage Ratios", secondary_y=True) | |
| fig.update_layout( | |
| yaxis3=dict( | |
| title="Stock Price (USD)", | |
| overlaying="y", | |
| side="right", | |
| position=0.95 | |
| ) | |
| ) | |
| fig.data[-1].update(yaxis="y3") | |
| st.plotly_chart(fig, use_container_width=True) | |
| def plot_combined_metrics_plotly(ext_df, end_prices): | |
| ext_df["Date"] = pd.to_datetime(ext_df["Fiscal Year"]) | |
| ext_df.sort_values("Date", inplace=True) | |
| dates = ext_df["Date"].dt.strftime('%Y-%m-%d') | |
| trace_net = go.Bar(x=dates, y=ext_df["Net Income"], name="Net Income") | |
| trace_ocf = go.Bar(x=dates, y=ext_df["Operating Cash Flow"], name="Op. Cash Flow") | |
| trace_capex = go.Bar(x=dates, y=ext_df["CapEx"], name="CapEx") | |
| trace_roe = go.Scatter(x=dates, y=ext_df["ROE"], mode='lines+markers', name="ROE", yaxis="y2") | |
| trace_icr = go.Scatter(x=dates, y=ext_df["Interest Coverage Ratio"], mode='lines+markers', name="ICR", yaxis="y2") | |
| stock_prices = [end_prices.get(fy, None) for fy in ext_df["Fiscal Year"]] | |
| trace_sp = go.Scatter(x=dates, y=stock_prices, mode='lines+markers', name="Stock Price", yaxis="y3") | |
| fig = make_subplots(specs=[[{"secondary_y": True}]]) | |
| fig.add_trace(trace_net, secondary_y=False) | |
| fig.add_trace(trace_ocf, secondary_y=False) | |
| fig.add_trace(trace_capex, secondary_y=False) | |
| fig.add_trace(trace_roe, secondary_y=True) | |
| fig.add_trace(trace_icr, secondary_y=True) | |
| fig.add_trace(trace_sp) | |
| fig.update_layout( | |
| title="Net Income, Cash Flow & ROE", | |
| xaxis_title="Fiscal Year", | |
| barmode="group" | |
| ) | |
| fig.update_yaxes(title_text="Values (USD)", secondary_y=False) | |
| fig.update_yaxes(title_text="Ratios", secondary_y=True) | |
| fig.update_layout( | |
| yaxis3=dict( | |
| title="Stock Price (USD)", | |
| overlaying="y", | |
| side="right", | |
| position=0.95 | |
| ) | |
| ) | |
| fig.data[-1].update(yaxis="y3") | |
| st.plotly_chart(fig, use_container_width=True) | |
| # --------------------------- | |
| # Streamlit App Layout & Sidebar | |
| # --------------------------- | |
| st.set_page_config(layout="wide", page_title="ROE Decomposition Dashboard") | |
| st.title("ROE Decomposition") | |
| st.markdown(""" | |
| This application deconstructs return on equity using an advanced DuPont analysis approach. | |
| It examines how profitability, leverage, and operational efficiency contribute to ROE over time. | |
| """) | |
| with st.expander("Advanced DuPont Analysis Explanation", expanded=False): | |
| st.markdown("The Advanced DuPont Analysis breaks down ROE into multiple components:") | |
| st.latex(r"\text{ROE} = \text{Tax Burden} \times \text{Interest Burden} \times \text{Operating Margin} \times \text{Asset Turnover} \times \text{Equity Multiplier}") | |
| st.markdown("Where:") | |
| st.latex(r"\text{Tax Burden} = \frac{\text{Net Income}}{\text{EBT}}") | |
| st.latex(r"\text{Interest Burden} = \frac{\text{EBT}}{\text{EBIT}}") | |
| st.latex(r"\text{Operating Margin} = \frac{\text{EBIT}}{\text{Revenue}}") | |
| st.latex(r"\text{Asset Turnover} = \frac{\text{Revenue}}{\text{Total Assets}}") | |
| st.latex(r"\text{Equity Multiplier} = \frac{\text{Total Assets}}{\text{Total Equity}}") | |
| st.markdown("This breakdown allows analysts to pinpoint whether changes in ROE are driven by tax factors, operating performance, asset efficiency, or financial leverage.") | |
| #st.sidebar.header("User Inputs") | |
| with st.sidebar.expander("Data Options", expanded=True): | |
| ticker = st.text_input( | |
| "Ticker Symbol", | |
| value="AAPL", | |
| help="Example: AAPL, TSLA, GOOG, etc." | |
| ) | |
| period_type = st.selectbox( | |
| "Select Data Period", | |
| options=["annual", "quarter"], | |
| help="Choose annual or quarterly data." | |
| ) | |
| limit_periods = st.number_input( | |
| "Number of Periods", | |
| min_value=1, | |
| max_value=20, | |
| value=10, | |
| help="Number of consecutive periods to analyze." | |
| ) | |
| run_analysis = st.sidebar.button("Run Analysis", help="Fetch data and generate charts.") | |
| if run_analysis: | |
| with st.spinner("Fetching and processing data..."): | |
| data = fetch_all_data(ticker=ticker, period=period_type, limit=limit_periods) | |
| inc_stmt = data["income_statement"] | |
| bs = data["balance_sheet"] | |
| cf = data["cash_flow"] | |
| prices = data["end_prices"] | |
| if inc_stmt.empty or bs.empty or cf.empty: | |
| st.error("One or more data sets are empty. Check inputs and try again.") | |
| else: | |
| # DuPont Analysis | |
| st.subheader("Advanced DuPont Analysis") | |
| st.markdown("Breaks down ROE into tax, interest, margin, turnover, and leverage factors.") | |
| dupont_df = dupont_analysis_over_time(inc_stmt, bs) | |
| plot_dupont_results(dupont_df) | |
| with st.expander("Dynamic Interpretation: DuPont Analysis", expanded=False): | |
| try: | |
| # Sort periods and extract the latest period's data | |
| sorted_periods = sorted(dupont_df.columns) | |
| latest_period = sorted_periods[-1] | |
| latest_data = dupont_df[latest_period] | |
| advanced_roe = latest_data.get("Advanced DuPont ROE", None) | |
| st.markdown(f"**Latest Period:** {latest_period}") | |
| if advanced_roe is not None: | |
| st.markdown(f"**Advanced DuPont ROE:** {advanced_roe:.2f}") | |
| else: | |
| st.markdown("**Advanced DuPont ROE:** Data unavailable.") | |
| # Year-over-year change | |
| if len(sorted_periods) > 1: | |
| prev_period = sorted_periods[-2] | |
| prev_roe = dupont_df[prev_period].get("Advanced DuPont ROE", None) | |
| if prev_roe and prev_roe != 0 and advanced_roe is not None: | |
| yoy_change = (advanced_roe - prev_roe) / abs(prev_roe) | |
| st.markdown(f"**Year-over-Year ROE Change:** {(yoy_change * 100):.2f}%") | |
| else: | |
| st.markdown("Year-over-year comparison unavailable due to missing data.") | |
| st.markdown("##### Key Drivers for the Latest Period:") | |
| # Tax Burden interpretation | |
| tb = latest_data.get("Tax Burden", None) | |
| if tb is not None: | |
| st.markdown(f"- **Tax Burden:** {tb:.2f}") | |
| if tb < 0.8: | |
| st.markdown(" - A lower tax burden means the firm retains a larger share of its pre-tax income, supporting profitability.") | |
| else: | |
| st.markdown(" - A higher tax burden indicates significant tax expense, which may erode net profit.") | |
| else: | |
| st.markdown("- **Tax Burden:** Data unavailable.") | |
| # Interest Burden interpretation | |
| ib = latest_data.get("Interest Burden", None) | |
| if ib is not None: | |
| st.markdown(f"- **Interest Burden:** {ib:.2f}") | |
| if ib >= 0.9: | |
| st.markdown(" - An interest burden near 1 shows that interest expenses have minimal impact on pre-tax income.") | |
| else: | |
| st.markdown(" - A lower interest burden suggests that interest expenses significantly reduce pre-tax income.") | |
| else: | |
| st.markdown("- **Interest Burden:** Data unavailable.") | |
| # Operating Margin interpretation | |
| opm = latest_data.get("Operating Margin", None) | |
| if opm is not None: | |
| st.markdown(f"- **Operating Margin:** {opm:.2f}") | |
| if opm > 0.15: | |
| st.markdown(" - A strong operating margin reflects efficient core operations.") | |
| else: | |
| st.markdown(" - A low operating margin could signal operational inefficiencies.") | |
| else: | |
| st.markdown("- **Operating Margin:** Data unavailable.") | |
| # Asset Turnover interpretation | |
| at = latest_data.get("Asset Turnover", None) | |
| if at is not None: | |
| st.markdown(f"- **Asset Turnover:** {at:.2f}") | |
| if at > 1: | |
| st.markdown(" - Higher asset turnover indicates efficient utilization of assets to generate revenue.") | |
| else: | |
| st.markdown(" - Lower asset turnover may point to underutilized assets.") | |
| else: | |
| st.markdown("- **Asset Turnover:** Data unavailable.") | |
| # Equity Multiplier interpretation | |
| em = latest_data.get("Equity Multiplier", None) | |
| if em is not None: | |
| st.markdown(f"- **Equity Multiplier:** {em:.2f}") | |
| if em > 2: | |
| st.markdown(" - A high equity multiplier suggests that the company is leveraging debt to boost ROE.") | |
| else: | |
| st.markdown(" - A low equity multiplier indicates a more conservative financing structure.") | |
| else: | |
| st.markdown("- **Equity Multiplier:** Data unavailable.") | |
| # Overall conclusion based on ROE | |
| if advanced_roe is not None: | |
| st.markdown("##### Overall Conclusion:") | |
| if advanced_roe < 0.05: | |
| st.markdown("The overall ROE is relatively low. This may be driven by high tax/interest burdens or operational inefficiencies.") | |
| elif advanced_roe < 0.15: | |
| st.markdown("The ROE is moderate. There are areas of strength, yet there remains room for improvement in efficiency or leveraging assets.") | |
| else: | |
| st.markdown("The ROE is strong, indicating robust operational efficiency and effective use of leverage.") | |
| except Exception as e: | |
| st.error("Dynamic interpretation unavailable for DuPont analysis.") | |
| # Leverage & Equity Analysis | |
| st.subheader("Leverage & Equity Analysis") | |
| st.markdown("Shows how leverage metrics and equity levels change. Also links each period's stock price.") | |
| em_df = compute_equity_multiplier_details(bs) | |
| plot_leverage_metrics_plotly(em_df, prices) | |
| with st.expander("Dynamic Interpretation: Leverage & Equity", expanded=False): | |
| try: | |
| # Sort fiscal years and extract the latest period's data | |
| sorted_fy = sorted(em_df["Fiscal Year"]) | |
| latest_fy = sorted_fy[-1] | |
| latest_row = em_df[em_df["Fiscal Year"] == latest_fy].iloc[0] | |
| de = latest_row.get("Debt to Equity", None) | |
| em_ratio = latest_row.get("Equity Multiplier", None) | |
| st.markdown(f"**Latest Period:** {latest_fy}") | |
| if de is not None: | |
| st.markdown(f"**Debt to Equity:** {de:.2f}") | |
| else: | |
| st.markdown("**Debt to Equity:** Data unavailable.") | |
| if em_ratio is not None: | |
| st.markdown(f"**Equity Multiplier:** {em_ratio:.2f}") | |
| else: | |
| st.markdown("**Equity Multiplier:** Data unavailable.") | |
| # Calculate Year-over-Year changes if available | |
| if len(sorted_fy) > 1: | |
| prev_fy = sorted_fy[-2] | |
| prev_row = em_df[em_df["Fiscal Year"] == prev_fy].iloc[0] | |
| prev_de = prev_row.get("Debt to Equity", None) | |
| prev_em = prev_row.get("Equity Multiplier", None) | |
| if prev_de and de is not None and prev_de != 0: | |
| yoy_de_change = (de - prev_de) / abs(prev_de) | |
| st.markdown(f"**YoY Debt to Equity Change:** {(yoy_de_change * 100):.2f}%") | |
| else: | |
| st.markdown("YoY Debt to Equity change unavailable.") | |
| if prev_em and em_ratio is not None and prev_em != 0: | |
| yoy_em_change = (em_ratio - prev_em) / abs(prev_em) | |
| st.markdown(f"**YoY Equity Multiplier Change:** {(yoy_em_change * 100):.2f}%") | |
| else: | |
| st.markdown("YoY Equity Multiplier change unavailable.") | |
| st.markdown("##### Detailed Interpretation:") | |
| # Detailed interpretation for Debt-to-Equity | |
| if de is not None: | |
| if de < 1: | |
| st.markdown("- **Low Debt to Equity:** The firm relies more on equity financing. This typically indicates lower financial risk and a conservative capital structure.") | |
| elif 1 <= de < 2: | |
| 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.") | |
| else: | |
| 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.") | |
| else: | |
| st.markdown("- **Debt to Equity data is missing.**") | |
| # Detailed interpretation for Equity Multiplier | |
| if em_ratio is not None: | |
| if em_ratio < 1.5: | |
| st.markdown("- **Low Equity Multiplier:** Indicates limited use of debt in financing assets, reflecting a conservative approach.") | |
| elif 1.5 <= em_ratio < 2.5: | |
| st.markdown("- **Moderate Equity Multiplier:** Suggests a balanced approach to leveraging, combining both debt and equity to finance assets.") | |
| else: | |
| 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.") | |
| else: | |
| st.markdown("- **Equity Multiplier data is missing.**") | |
| except Exception: | |
| st.error("Dynamic interpretation unavailable for leverage & equity.") | |
| # Combined Cash Flow and Profitability Metrics | |
| st.subheader("Combined Cash Flow & ROE") | |
| st.markdown("Shows net income, operating cash flow, CapEx, and return metrics together.") | |
| ext_df = compute_equity_multiplier_details(bs) | |
| ext_df = compute_additional_metrics(inc_stmt, ext_df) | |
| ext_df = add_cash_flow_info(cf, ext_df) | |
| plot_combined_metrics_plotly(ext_df, prices) | |
| with st.expander("Dynamic Interpretation: Combined Metrics", expanded=False): | |
| try: | |
| # Get sorted fiscal years and pick the latest period | |
| sorted_fy = sorted(ext_df["Fiscal Year"]) | |
| latest_fy = sorted_fy[-1] | |
| latest_row = ext_df[ext_df["Fiscal Year"] == latest_fy].iloc[0] | |
| # Extract key metrics for the latest period | |
| net_income = latest_row.get("Net Income", None) | |
| op_cf = latest_row.get("Operating Cash Flow", None) | |
| capex = latest_row.get("CapEx", None) | |
| roe = latest_row.get("ROE", None) | |
| st.markdown(f"**Latest Period:** {latest_fy}") | |
| if net_income is not None: | |
| st.markdown(f"- **Net Income:** {net_income:.2f}") | |
| else: | |
| st.markdown("- **Net Income:** Data unavailable") | |
| if op_cf is not None: | |
| st.markdown(f"- **Operating Cash Flow:** {op_cf:.2f}") | |
| else: | |
| st.markdown("- **Operating Cash Flow:** Data unavailable") | |
| if capex is not None: | |
| st.markdown(f"- **CapEx:** {capex:.2f}") | |
| else: | |
| st.markdown("- **CapEx:** Data unavailable") | |
| if roe is not None: | |
| st.markdown(f"- **ROE:** {roe:.2f} _(Higher ROE typically indicates better efficiency in generating returns)_") | |
| else: | |
| st.markdown("- **ROE:** Data unavailable") | |
| st.markdown("##### Detailed Analysis:") | |
| # Compare Operating Cash Flow to Net Income | |
| if net_income is not None and op_cf is not None: | |
| if op_cf < net_income: | |
| st.markdown( | |
| "• **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." | |
| ) | |
| else: | |
| st.markdown( | |
| "• **Operating Cash Flow exceeds Net Income.** This suggests strong cash conversion from operations, which is a positive indicator of liquidity and operational efficiency." | |
| ) | |
| else: | |
| st.markdown("• **Operating Cash Flow vs. Net Income:** Insufficient data for comparison.") | |
| # Evaluate CapEx relative to Net Income | |
| if capex is not None and net_income is not None: | |
| capex_ratio = capex / net_income if net_income != 0 else None | |
| if capex_ratio is not None: | |
| st.markdown(f"• **CapEx to Net Income Ratio:** {capex_ratio:.2f}") | |
| if capex_ratio > 1: | |
| st.markdown( | |
| " - **High CapEx:** The company is investing heavily in fixed assets. While this can drive future growth, it may suppress short-term profitability." | |
| " - **High CapEx:** The company is investing heavily in fixed assets. While this can drive future growth, it may suppress short-term profitability." | |
| ) | |
| else: | |
| st.markdown( | |
| " - **Moderate CapEx:** Investment levels appear balanced relative to net income, which may support sustainable growth without overly impacting current profits." | |
| ) | |
| else: | |
| st.markdown("• **CapEx Analysis:** Unable to compute ratio due to zero or missing net income.") | |
| else: | |
| st.markdown("• **CapEx Analysis:** Insufficient data for evaluation.") | |
| # Year-over-Year comparison for Net Income | |
| if len(sorted_fy) > 1 and net_income is not None: | |
| prev_fy = sorted_fy[-2] | |
| prev_row = ext_df[ext_df["Fiscal Year"] == prev_fy].iloc[0] | |
| prev_net = prev_row.get("Net Income", None) | |
| if prev_net is not None and prev_net != 0: | |
| net_yoy = (net_income - prev_net) / abs(prev_net) | |
| st.markdown(f"• **Year-over-Year Net Income Change:** {(net_yoy * 100):.2f}%") | |
| if net_yoy > 0: | |
| st.markdown(" - Net income has increased compared to the previous period, indicating potential growth or improved efficiency.") | |
| else: | |
| st.markdown(" - Net income has declined compared to the previous period, which may signal operational challenges or increased expenses.") | |
| else: | |
| st.markdown("• **Year-over-Year Net Income Change:** Data unavailable for previous period.") | |
| else: | |
| st.markdown("• **Year-over-Year Comparison:** Not enough periods to compute change.") | |
| except Exception as e: | |
| st.error("Dynamic interpretation unavailable for combined metrics.") | |
| else: | |
| st.info("Set your inputs and click Run Analysis.") | |
| hide_streamlit_style = """ | |
| <style> | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| </style> | |
| """ | |
| st.markdown(hide_streamlit_style, unsafe_allow_html=True) |