Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| import plotly.express as px | |
| import requests | |
| import numpy as np | |
| import yfinance as yf | |
| import os | |
| # Global API key | |
| API_KEY = os.getenv("FMP_API_KEY") | |
| # ---------------------------- | |
| # Page configuration | |
| # ---------------------------- | |
| st.set_page_config(page_title="Financial Ratios Dashboard", layout="wide") | |
| # ---------------------------- | |
| # Helper function to interpret ratios | |
| # ---------------------------- | |
| def interpret_ratios(df, metric_list, section_title): | |
| existing_cols = [m for m in metric_list if m in df.columns] | |
| if not existing_cols or df.empty: | |
| return f"**{section_title}**: Data is not available." | |
| df_valid = df[['date'] + existing_cols].dropna(subset=existing_cols, how='all') | |
| if df_valid.empty: | |
| return f"**{section_title}**: No valid data entries." | |
| df_valid = df_valid.sort_values("date") | |
| latest_row = df_valid.iloc[-1] | |
| latest_date = latest_row['date'] | |
| if len(df_valid) > 1: | |
| prior_row = df_valid.iloc[-2] | |
| prior_date = prior_row['date'] | |
| else: | |
| prior_row = None | |
| prior_date = None | |
| values_only = df_valid[existing_cols].astype(float) | |
| mean_vals = values_only.mean() | |
| min_vals = values_only.min() | |
| max_vals = values_only.max() | |
| std_vals = values_only.std() | |
| text = f"### {section_title}\n\n" | |
| text += "**Recent Data:**\n" | |
| text += f"- **Latest Record Date:** {latest_date.date()}\n" | |
| for col in existing_cols: | |
| latest_val = latest_row[col] | |
| if pd.isna(latest_val): | |
| text += f"- **{col}:** Data missing.\n" | |
| else: | |
| text += f"- **{col}:** {latest_val:.2f}\n" | |
| if prior_row is not None: | |
| text += f"\n**Comparison with previous record ({prior_date.date()}):**\n" | |
| for col in existing_cols: | |
| latest_val = latest_row[col] | |
| prior_val = prior_row[col] | |
| if pd.isna(latest_val) or pd.isna(prior_val): | |
| text += f"- **{col}:** Comparison not possible.\n" | |
| else: | |
| diff = latest_val - prior_val | |
| if diff > 0: | |
| text += f"- **{col}:** Increased by {diff:.2f}.\n" | |
| elif diff < 0: | |
| text += f"- **{col}:** Decreased by {abs(diff):.2f}.\n" | |
| else: | |
| text += f"- **{col}:** Remained the same.\n" | |
| text += "\n**Historical Summary:**\n" | |
| for col in existing_cols: | |
| text += (f"- **{col}:** Mean = {mean_vals[col]:.2f}, " | |
| f"Min = {min_vals[col]:.2f}, Max = {max_vals[col]:.2f}, " | |
| f"Std Dev = {std_vals[col]:.2f}.\n") | |
| text += "\n**Final Interpretation:**\n" | |
| if section_title == "Liquidity Ratios": | |
| text += ( | |
| "- High liquidity ratios (current, quick, cash) suggest the company can meet short-term obligations.\n" | |
| "- Low ratios may raise concerns about paying bills on time.\n" | |
| "- The cash ratio shows how much cash is on hand relative to liabilities.\n" | |
| ) | |
| elif section_title == "Efficiency & Turnover Ratios": | |
| text += ( | |
| "- Longer days of sales or inventory may signal slower turnover.\n" | |
| "- Faster turnover indicates efficient use of assets.\n" | |
| "- Extreme values call for a closer look at operations.\n" | |
| ) | |
| elif section_title == "Profitability Ratios": | |
| text += ( | |
| "- Higher margins and returns point to strong profit generation.\n" | |
| "- Lower margins may indicate rising costs or pricing pressure.\n" | |
| "- Tax rates combined with margins offer insight into net profitability.\n" | |
| ) | |
| elif section_title == "Debt & Coverage Ratios": | |
| text += ( | |
| "- Lower debt ratios and higher interest coverage suggest safer leverage.\n" | |
| "- High debt or low coverage ratios may increase financial risk.\n" | |
| "- These ratios help assess the firm's capacity to cover its debts.\n" | |
| ) | |
| elif section_title == "Valuation Ratios": | |
| text += ( | |
| "- Lower valuation ratios may hint at an undervalued stock.\n" | |
| "- High ratios could point to overvaluation or high growth expectations.\n" | |
| "- Comparing these with the stock price provides context for market sentiment.\n" | |
| ) | |
| elif section_title == "Per Share & Distribution Ratios": | |
| text += ( | |
| "- Higher cash flow per share numbers are positive for shareholders.\n" | |
| "- A high payout or dividend payout ratio means more earnings are returned as dividends.\n" | |
| "- These ratios help gauge share performance and distribution policies.\n" | |
| ) | |
| else: | |
| text += "- Review these trends to understand their impact.\n" | |
| return text | |
| # ---------------------------- | |
| # App header and description | |
| # ---------------------------- | |
| st.title("Key Financial Ratios") | |
| st.markdown(""" | |
| This dashboard shows key financial ratios for companies. | |
| The ratios are grouped into sections for comparison. | |
| Use the sidebar to set inputs and click **Run Analysis**. | |
| """) | |
| # ---------------------------- | |
| # Sidebar inputs | |
| # ---------------------------- | |
| st.sidebar.header("Inputs") | |
| with st.sidebar.expander("Settings", expanded=True): | |
| symbol = st.text_input("Company Symbol", value="AAPL", help="Enter the company's stock symbol (e.g., AAPL).") | |
| period = st.selectbox("Period", options=["annual", "quarter"], help="Select annual or quarterly data.") | |
| run_button = st.sidebar.button("Run Analysis") | |
| # ---------------------------- | |
| # Main Analysis and Visualization | |
| # ---------------------------- | |
| if run_button: | |
| try: | |
| # Fetch ratios data | |
| url = f"https://financialmodelingprep.com/api/v3/ratios/{symbol}?period={period}&apikey={API_KEY}" | |
| response = requests.get(url) | |
| response.raise_for_status() | |
| data = response.json() | |
| if not data: | |
| st.error("No data returned. Check the symbol or period.") | |
| else: | |
| df = pd.DataFrame(data) | |
| if "date" in df.columns: | |
| df['date'] = pd.to_datetime(df['date'], errors='coerce') | |
| df.sort_values("date", inplace=True) | |
| # Fetch historical stock price data | |
| ticker = yf.Ticker(symbol) | |
| price_df = ticker.history(period="max")[["Close"]].reset_index() | |
| price_df.rename(columns={"Date": "date"}, inplace=True) | |
| price_df['date'] = pd.to_datetime(price_df['date']) | |
| price_df['date'] = price_df['date'].dt.tz_localize(None) | |
| price_df.sort_values("date", inplace=True) | |
| # Merge stock price data with ratios data using merge_asof | |
| df = pd.merge_asof(df.sort_values("date"), price_df.sort_values("date"), on="date", direction="backward") | |
| st.success("Data loaded successfully!") | |
| st.write("Each section shows a chart, an interpretation, and the data.") | |
| # Section 1: Liquidity Ratios | |
| st.subheader("1. Liquidity Ratios") | |
| liquidity_vars = ["currentRatio", "quickRatio", "cashRatio"] | |
| try: | |
| fig1 = px.line(df, x="date", y=liquidity_vars, title="Liquidity Ratios") | |
| fig1.update_layout(xaxis_title="Date", yaxis_title="Ratio", legend_title="Metric") | |
| st.plotly_chart(fig1, use_container_width=True) | |
| except Exception: | |
| st.error("Error generating the Liquidity Ratios chart.") | |
| with st.expander("Interpretation"): | |
| interp_text = interpret_ratios(df, liquidity_vars, "Liquidity Ratios") | |
| st.markdown(interp_text) | |
| with st.expander("DataFrame"): | |
| st.dataframe(df[["date"] + liquidity_vars]) | |
| # Section 2: Efficiency & Turnover Ratios | |
| st.subheader("2. Efficiency & Turnover Ratios") | |
| efficiency_vars = [ | |
| "daysOfSalesOutstanding", | |
| "daysOfInventoryOutstanding", | |
| "operatingCycle", | |
| "daysOfPayablesOutstanding", | |
| "cashConversionCycle", | |
| "receivablesTurnover", | |
| "payablesTurnover", | |
| "inventoryTurnover", | |
| "fixedAssetTurnover", | |
| "assetTurnover" | |
| ] | |
| try: | |
| fig2 = px.line(df, x="date", y=efficiency_vars, title="Efficiency & Turnover Ratios") | |
| fig2.update_layout(xaxis_title="Date", yaxis_title="Ratio / Days", legend_title="Metric") | |
| st.plotly_chart(fig2, use_container_width=True) | |
| except Exception: | |
| st.error("Error generating the Efficiency & Turnover Ratios chart.") | |
| with st.expander("Interpretation"): | |
| interp_text = interpret_ratios(df, efficiency_vars, "Efficiency & Turnover Ratios") | |
| st.markdown(interp_text) | |
| with st.expander("DataFrame"): | |
| st.dataframe(df[["date"] + efficiency_vars]) | |
| # Section 3: Profitability Ratios | |
| st.subheader("3. Profitability Ratios") | |
| profitability_vars = [ | |
| "grossProfitMargin", | |
| "operatingProfitMargin", | |
| "pretaxProfitMargin", | |
| "netProfitMargin", | |
| "effectiveTaxRate", | |
| "returnOnAssets", | |
| "returnOnEquity", | |
| "returnOnCapitalEmployed", | |
| "netIncomePerEBT", | |
| "ebtPerEbit", | |
| "ebitPerRevenue" | |
| ] | |
| try: | |
| fig3 = px.line(df, x="date", y=profitability_vars, title="Profitability Ratios") | |
| fig3.update_layout(xaxis_title="Date", yaxis_title="Percentage / Ratio", legend_title="Metric") | |
| st.plotly_chart(fig3, use_container_width=True) | |
| except Exception: | |
| st.error("Error generating the Profitability Ratios chart.") | |
| with st.expander("Interpretation"): | |
| interp_text = interpret_ratios(df, profitability_vars, "Profitability Ratios") | |
| st.markdown(interp_text) | |
| with st.expander("DataFrame"): | |
| st.dataframe(df[["date"] + profitability_vars]) | |
| # Section 4: Debt & Coverage Ratios | |
| st.subheader("4. Debt & Coverage Ratios") | |
| debt_vars = [ | |
| "debtRatio", | |
| "debtEquityRatio", | |
| "longTermDebtToCapitalization", | |
| "totalDebtToCapitalization", | |
| "interestCoverage", | |
| "cashFlowToDebtRatio", | |
| "companyEquityMultiplier", | |
| "shortTermCoverageRatios", | |
| "cashFlowCoverageRatios", | |
| "capitalExpenditureCoverageRatio", | |
| "dividendPaidAndCapexCoverageRatio" | |
| ] | |
| try: | |
| fig4 = px.line(df, x="date", y=debt_vars, title="Debt & Coverage Ratios") | |
| fig4.update_layout(xaxis_title="Date", yaxis_title="Ratio", legend_title="Metric") | |
| st.plotly_chart(fig4, use_container_width=True) | |
| except Exception: | |
| st.error("Error generating the Debt & Coverage Ratios chart.") | |
| with st.expander("Interpretation"): | |
| interp_text = interpret_ratios(df, debt_vars, "Debt & Coverage Ratios") | |
| st.markdown(interp_text) | |
| with st.expander("DataFrame"): | |
| st.dataframe(df[["date"] + debt_vars]) | |
| # Section 5: Valuation Ratios (with Stock Price) | |
| st.subheader("5. Valuation Ratios") | |
| valuation_vars = [ | |
| "priceBookValueRatio", | |
| "priceToBookRatio", | |
| "priceToSalesRatio", | |
| "priceEarningsRatio", | |
| "priceToFreeCashFlowsRatio", | |
| "priceToOperatingCashFlowsRatio", | |
| "priceCashFlowRatio", | |
| "priceEarningsToGrowthRatio", | |
| "priceSalesRatio", | |
| "dividendYield", | |
| "enterpriseValueMultiple", | |
| "priceFairValue" | |
| ] | |
| try: | |
| from plotly.subplots import make_subplots | |
| import plotly.graph_objects as go | |
| fig5 = make_subplots(specs=[[{"secondary_y": True}]]) | |
| for col in valuation_vars: | |
| fig5.add_trace( | |
| go.Scatter(x=df['date'], y=df[col], mode='lines', name=col), | |
| secondary_y=False | |
| ) | |
| # Add stock price on secondary y-axis | |
| fig5.add_trace( | |
| go.Scatter(x=df['date'], y=df['Close'], mode='lines', name="Stock Price"), | |
| secondary_y=True | |
| ) | |
| fig5.update_layout(title_text="Valuation Ratios & Stock Price") | |
| fig5.update_xaxes(title_text="Date") | |
| fig5.update_yaxes(title_text="Valuation Ratios", secondary_y=False) | |
| fig5.update_yaxes(title_text="Stock Price", secondary_y=True) | |
| st.plotly_chart(fig5, use_container_width=True) | |
| except Exception: | |
| st.error("Error generating the Valuation Ratios chart.") | |
| with st.expander("Interpretation"): | |
| interp_text = interpret_ratios(df, valuation_vars, "Valuation Ratios") | |
| st.markdown(interp_text) | |
| with st.expander("DataFrame"): | |
| st.dataframe(df[["date"] + valuation_vars + ["Close"]]) | |
| # Section 6: Per Share & Distribution Ratios | |
| st.subheader("6. Per Share & Distribution Ratios") | |
| share_vars = [ | |
| "operatingCashFlowPerShare", | |
| "freeCashFlowPerShare", | |
| "cashPerShare", | |
| "payoutRatio", | |
| "operatingCashFlowSalesRatio", | |
| "freeCashFlowOperatingCashFlowRatio", | |
| "dividendPayoutRatio" | |
| ] | |
| try: | |
| fig6 = px.line(df, x="date", y=share_vars, title="Per Share & Distribution Ratios") | |
| fig6.update_layout(xaxis_title="Date", yaxis_title="Ratio", legend_title="Metric") | |
| st.plotly_chart(fig6, use_container_width=True) | |
| except Exception: | |
| st.error("Error generating the Per Share & Distribution Ratios chart.") | |
| with st.expander("Interpretation"): | |
| interp_text = interpret_ratios(df, share_vars, "Per Share & Distribution Ratios") | |
| st.markdown(interp_text) | |
| with st.expander("DataFrame"): | |
| st.dataframe(df[["date"] + share_vars]) | |
| except Exception: | |
| st.error("Error fetching data. Check your connection and inputs.") | |
| # Hide Streamlit default style | |
| hide_streamlit_style = """ | |
| <style> | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| </style> | |
| """ | |
| st.markdown(hide_streamlit_style, unsafe_allow_html=True) | |