bohmian's picture
Update src/streamlit_app.py
968e18a verified
import os
import requests
import pandas as pd
import plotly.graph_objects as go
import streamlit as st
st.set_page_config(layout="wide")
# you need to sign up at FMP API to get the API key, then put it in the respective environment variable
apikey = os.environ["FMP_API_KEY"]
def parse_json(url):
resp = requests.get(url)
resp.raise_for_status()
return pd.DataFrame(resp.json())
def fmt(val):
if abs(val) >= 1e9: return f"${val/1e9:.1f}B"
if abs(val) >= 1e6: return f"${val/1e6:.1f}M"
if abs(val) >= 1e3: return f"${val/1e3:.0f}K"
return f"${val:.0f}"
def draw_balance_sankey(balance_sheet, symbol, height, font_size):
# define all Sankey flows (left is source, right is target)
flows = [
# Current Assets
("Cash and Cash Equivalents", "Total Current Assets", balance_sheet["cashAndCashEquivalents"]),
("Short-Term Investments", "Total Current Assets", balance_sheet["shortTermInvestments"]),
("Net Receivables", "Total Current Assets", balance_sheet["netReceivables"]),
("Inventory", "Total Current Assets", balance_sheet["inventory"]),
("Prepaids", "Total Current Assets", balance_sheet["prepaids"]),
# NOTE: Other Current Assets may sometimes overlap with some of the above lines
("Other Current Assets", "Total Current Assets", balance_sheet["otherCurrentAssets"]),
# Non-Current Assets
("Property, Plant and Equipment, Net", "Total Non-Current Assets", balance_sheet["propertyPlantEquipmentNet"]),
("Goodwill", "Total Non-Current Assets", balance_sheet["goodwill"]),
("Intangible Assets", "Total Non-Current Assets", balance_sheet["intangibleAssets"]),
("Long-Term Investments", "Total Non-Current Assets", balance_sheet["longTermInvestments"]),
("Tax Assets", "Total Non-Current Assets", balance_sheet["taxAssets"]),
("Other Non-Current Assets", "Total Non-Current Assets", balance_sheet["otherNonCurrentAssets"]),
# ... to Total Assets
("Total Current Assets", "Total Assets", balance_sheet["totalCurrentAssets"]),
("Total Non-Current Assets", "Total Assets", balance_sheet["totalNonCurrentAssets"]),
# Total Assets to ...
("Total Assets", "Total Liabilities", balance_sheet["totalLiabilities"]),
("Total Assets", "Total Stockholders' Equity", balance_sheet["totalStockholdersEquity"]),
# Current Liabilities
("Total Liabilities", "Total Current Liabilities", balance_sheet["totalCurrentLiabilities"]),
("Total Current Liabilities", "Tax Payables", balance_sheet["taxPayables"]),
("Total Current Liabilities", "Short-Term Debt", balance_sheet["shortTermDebt"]),
("Total Current Liabilities", "Capital Lease Obligations (Current)", balance_sheet["capitalLeaseObligationsCurrent"]),
("Total Current Liabilities", "Deferred Revenue (Current)", balance_sheet["deferredRevenue"]),
("Total Current Liabilities", "Other Current Liabilities", balance_sheet["otherCurrentLiabilities"]),
("Total Current Liabilities", "Accounts Payable", balance_sheet["accountPayables"]),
("Total Current Liabilities", "Other Payables", balance_sheet["otherPayables"]),
("Total Current Liabilities", "Accrued Expenses", balance_sheet["accruedExpenses"]),
# Non-Current Liabilities
("Total Liabilities", "Total Non-Current Liabilities", balance_sheet["totalNonCurrentLiabilities"]),
("Total Non-Current Liabilities", "Long-Term Debt", balance_sheet["longTermDebt"]),
("Total Non-Current Liabilities", "Capital Lease Obligations (Non-Current)", balance_sheet["capitalLeaseObligationsNonCurrent"]),
("Total Non-Current Liabilities", "Deferred Revenue (Non-Current)", balance_sheet["deferredRevenueNonCurrent"]),
("Total Non-Current Liabilities", "Deferred Tax Liabilities (Non-Current)", balance_sheet["deferredTaxLiabilitiesNonCurrent"]),
("Total Non-Current Liabilities", "Other Non-Current Liabilities", balance_sheet["otherNonCurrentLiabilities"]),
# Equity
("Total Stockholders' Equity", "Common Stock", balance_sheet["commonStock"]),
("Total Stockholders' Equity", "Retained Earnings", balance_sheet["retainedEarnings"]),
("Total Stockholders' Equity", "Accumulated Other Comprehensive Income (Loss)", balance_sheet["accumulatedOtherComprehensiveIncomeLoss"]),
("Total Stockholders' Equity", "Additional Paid-In Capital", balance_sheet["additionalPaidInCapital"]),
("Total Stockholders' Equity", "Other Stockholders' Equity", balance_sheet["otherTotalStockholdersEquity"]),
]
# need to adjust flow to make negative values easier to read
adjusted_flows = []
for src, tgt, val in flows:
if val >= 0:
# positive: keep direction, color green
adjusted_flows.append((src, tgt, val, 'rgba(50,200,50,0.6)'))
else:
# negative: reverse direction, color red
adjusted_flows.append((tgt, src, -val, 'rgba(200,50,50,0.6)'))
# for labelling later, first we store the source and target names
labels = []
for src, tgt, _ in flows:
if src not in labels: labels.append(src)
if tgt not in labels: labels.append(tgt)
# map label to its actual balance‐sheet value for annotation
node_values = {
"Cash and Cash Equivalents": balance_sheet["cashAndCashEquivalents"],
"Short-Term Investments": balance_sheet["shortTermInvestments"],
"Net Receivables": balance_sheet["netReceivables"],
"Inventory": balance_sheet["inventory"],
"Prepaids": balance_sheet["prepaids"],
"Other Current Assets": balance_sheet["otherCurrentAssets"],
"Total Current Assets": balance_sheet["totalCurrentAssets"],
"Property, Plant and Equipment, Net": balance_sheet["propertyPlantEquipmentNet"],
"Goodwill": balance_sheet["goodwill"],
"Intangible Assets": balance_sheet["intangibleAssets"],
"Long-Term Investments": balance_sheet["longTermInvestments"],
"Tax Assets": balance_sheet["taxAssets"],
"Other Non-Current Assets": balance_sheet["otherNonCurrentAssets"],
"Total Non-Current Assets": balance_sheet["totalNonCurrentAssets"],
"Total Assets": balance_sheet["totalAssets"],
"Total Liabilities": balance_sheet["totalLiabilities"],
"Total Current Liabilities": balance_sheet["totalCurrentLiabilities"],
"Tax Payables": balance_sheet["taxPayables"],
"Short-Term Debt": balance_sheet["shortTermDebt"],
"Capital Lease Obligations (Current)": balance_sheet["capitalLeaseObligationsCurrent"],
"Deferred Revenue (Current)": balance_sheet["deferredRevenue"],
"Other Current Liabilities": balance_sheet["otherCurrentLiabilities"],
"Accounts Payable": balance_sheet["accountPayables"],
"Other Payables": balance_sheet["otherPayables"],
"Accrued Expenses": balance_sheet["accruedExpenses"],
"Total Non-Current Liabilities": balance_sheet["totalNonCurrentLiabilities"],
"Long-Term Debt": balance_sheet["longTermDebt"],
"Capital Lease Obligations (Non-Current)": balance_sheet["capitalLeaseObligationsNonCurrent"],
"Deferred Revenue (Non-Current)": balance_sheet["deferredRevenueNonCurrent"],
"Deferred Tax Liabilities (Non-Current)": balance_sheet["deferredTaxLiabilitiesNonCurrent"],
"Other Non-Current Liabilities": balance_sheet["otherNonCurrentLiabilities"],
"Total Stockholders' Equity": balance_sheet["totalStockholdersEquity"],
"Common Stock": balance_sheet["commonStock"],
"Retained Earnings": balance_sheet["retainedEarnings"],
"Accumulated Other Comprehensive Income (Loss)": balance_sheet["accumulatedOtherComprehensiveIncomeLoss"],
"Additional Paid-In Capital": balance_sheet["additionalPaidInCapital"],
"Other Stockholders' Equity": balance_sheet["otherTotalStockholdersEquity"],
}
# for formatting, annotate labels with $ amounts and take care of billions, millions, thousands
def fmt(val):
if abs(val) >= 1e9: return f"${val/1e9:.1f}B"
if abs(val) >= 1e6: return f"${val/1e6:.1f}M"
if abs(val) >= 1e3: return f"${val/1e3:.0f}K"
return f"${val:.0f}"
# put the sorce and target values in labels
labels = []
for s, t, _, _ in adjusted_flows:
if s not in labels: labels.append(s)
if t not in labels: labels.append(t)
idx = {label:i for i,label in enumerate(labels)}
source = [ idx[s] for s, t, _, _ in adjusted_flows ] # index of sources for sankey input
target = [ idx[t] for s, t, _, _ in adjusted_flows ] # index of target for sankey input
value = [ v for _, _, v, _ in adjusted_flows ]
colors = [ c for _, _, _, c in adjusted_flows ]
label_with_values = []
for label in labels:
val = node_values[label]
base = label.replace(" (Current)", "")\
.replace(" (Non-Current)", "") # saves some printing space
if val < 0:
base += " [NEGATIVE]" # just to make negatives more obvious in the label
label_with_values.append(f"{base} ({fmt(val)})")
fig = go.Figure(go.Sankey(
arrangement="snap",
node = dict(label=label_with_values, pad=15, thickness=20),
link = dict(source=source, target=target, value=value, color=colors)
))
fig.update_layout(
title_text=f"Balance Sheet Sankey β€” {symbol}",
height=height,
font_size=font_size
)
return fig
def draw_income_sankey(income_statement, symbol, height, font_size):
flows = [
# Revenue─
("Revenue", "Cost of Revenue", income_statement["costOfRevenue"]),
("Revenue", "Gross Profit", income_statement["grossProfit"]),
# Gross Profit
("Gross Profit", "Operating Income", income_statement["operatingIncome"]),
("Gross Profit", "Operating Expenses", income_statement["operatingExpenses"]),
# Operating Expenses
("Operating Expenses", "Research & Development Expenses", income_statement["researchAndDevelopmentExpenses"]),
# ("Operating Expenses", "General & Administrative Expenses", income_statement["generalAndAdministrativeExpenses"]), # already in SG&A
# ("Operating Expenses", "Selling & Marketing Expenses", income_statement["sellingAndMarketingExpenses"]), # already in SG&A
("Operating Expenses", "SG&A Expenses", income_statement["sellingGeneralAndAdministrativeExpenses"]),
("Operating Expenses", "Other Operating Expenses", income_statement["otherExpenses"]),
# Pretax Income
("Pretax Income", "Income Tax Expense", income_statement["incomeTaxExpense"]),
("Pretax Income", "Net Income", income_statement["netIncome"]),
("Pretax Income", "Interest Expense", income_statement["interestExpense"]),
# this value is recorded as negative in API, but we do not need to reverse the flow like in balance sheet
# because it decreases the pretax income so we put it together at the same side with all the tax expenses
("Pretax Income", "Non-Operating Income Excl. Interest", -income_statement["nonOperatingIncomeExcludingInterest"]),
("Pretax Income", "Total Other Income & Expenses Net", income_statement["totalOtherIncomeExpensesNet"]),
("Pretax Income", "Other Adjustments to Net Income", income_statement["otherAdjustmentsToNetIncome"]),
# Other Income that goes into Pretax Income
("Operating Income", "Pretax Income", income_statement["operatingIncome"]),
("Net Interest Income", "Pretax Income", income_statement["netInterestIncome"]),
("Interest Income", "Pretax Income", income_statement["interestIncome"]),
]
# need to adjust flow to make negative values easier to read
adjusted_flows = []
for src, tgt, val in flows:
if val >= 0:
# positive: keep direction, color green
adjusted_flows.append((src, tgt, val, 'rgba(50,200,50,0.6)'))
else:
# negative: reverse direction, color red
adjusted_flows.append((tgt, src, -val, 'rgba(200,50,50,0.6)'))
# for labelling later, first we store the source and target names
labels = []
for src, tgt, _ in flows:
if src not in labels: labels.append(src)
if tgt not in labels: labels.append(tgt)
# map label to its actual balance‐sheet value for annotation
node_values = {
"Revenue": income_statement["revenue"],
"Cost of Revenue": income_statement["costOfRevenue"],
"Gross Profit": income_statement["grossProfit"],
"Operating Income": income_statement["operatingIncome"],
"Operating Expenses": income_statement["operatingExpenses"],
"Research & Development Expenses": income_statement["researchAndDevelopmentExpenses"],
#"General & Administrative Expenses": income_statement["generalAndAdministrativeExpenses"], # already in SG&A
#"Selling & Marketing Expenses": income_statement["sellingAndMarketingExpenses"], # already in SG&A
"SG&A Expenses": income_statement["sellingGeneralAndAdministrativeExpenses"],
"Other Operating Expenses": income_statement["otherExpenses"],
"Net Interest Income": income_statement["netInterestIncome"],
"Interest Income": income_statement["interestIncome"],
"Interest Expense": income_statement["interestExpense"],
"Non-Operating Income Excl. Interest":-income_statement["nonOperatingIncomeExcludingInterest"],
"Total Other Income & Expenses Net": income_statement["totalOtherIncomeExpensesNet"],
"Pretax Income": income_statement["incomeBeforeTax"],
"Income Tax Expense": income_statement["incomeTaxExpense"],
"Net Income": income_statement["netIncome"],
"Other Adjustments to Net Income": income_statement["otherAdjustmentsToNetIncome"],
"Bottom Line Net Income": income_statement["bottomLineNetIncome"],
}
# for formatting, annotate labels with $ amounts and take care of billions, millions, thousands
def fmt(val):
if abs(val) >= 1e9: return f"${val/1e9:.1f}B"
if abs(val) >= 1e6: return f"${val/1e6:.1f}M"
if abs(val) >= 1e3: return f"${val/1e3:.0f}K"
return f"${val:.0f}"
# put the sorce and target values in labels
labels = []
for s, t, _, _ in adjusted_flows:
if s not in labels: labels.append(s)
if t not in labels: labels.append(t)
idx = {label:i for i,label in enumerate(labels)}
source = [ idx[s] for s, t, _, _ in adjusted_flows ] # index of sources for sankey input
target = [ idx[t] for s, t, _, _ in adjusted_flows ] # index of target for sankey input
value = [ v for _, _, v, _ in adjusted_flows ]
colors = [ c for _, _, _, c in adjusted_flows ]
label_with_values = []
for label in labels:
val = node_values[label]
base = label.replace(" (Current)", "")\
.replace(" (Non-Current)", "") # saves some printing space
if val < 0:
base += " [NEGATIVE]" # just to make negatives more obvious in the label
label_with_values.append(f"{base} ({fmt(val)})")
fig = go.Figure(go.Sankey(
arrangement="snap",
node = dict(label=label_with_values, pad=15, thickness=20),
link = dict(source=source, target=target, value=value, color=colors)
))
fig.update_layout(
title_text=f"Income Statement Sankey β€” {symbol}",
height=height,
font_size=font_size
)
return fig
def draw_cashflow_sankey(cash_flow, symbol, height, font_size):
flows = [
# Operating Activities (Inflow)
("Net Income", "Operating Activities", cash_flow["netIncome"]),
("Depreciation & Amortization", "Operating Activities", cash_flow["depreciationAndAmortization"]),
("Deferred Income Tax", "Operating Activities", cash_flow["deferredIncomeTax"]),
("Stock-Based Compensation", "Operating Activities", cash_flow["stockBasedCompensation"]),
("Change in Working Capital", "Operating Activities", cash_flow["changeInWorkingCapital"]),
("Accounts Receivables Ξ”", "Operating Activities", cash_flow["accountsReceivables"]),
("Inventory Ξ”", "Operating Activities", cash_flow["inventory"]),
("Accounts Payable Ξ”", "Operating Activities", cash_flow["accountsPayables"]),
#("Other Working Capital Ξ”", "Operating Activities", cash_flow["otherWorkingCapital"]), # overlap with some lines
("Other Non-Cash Items", "Operating Activities", cash_flow["otherNonCashItems"]),
("Operating Activities", "Net Cash Inflow (Operating)", cash_flow["netCashProvidedByOperatingActivities"]),
# Investing Activities (Outflow)
("Investments in PP&E", "Investing Activities", cash_flow["investmentsInPropertyPlantAndEquipment"]),
("Acquisitions, Net", "Investing Activities", cash_flow["acquisitionsNet"]),
("Purchases of Investments", "Investing Activities", cash_flow["purchasesOfInvestments"]),
("Sales/Maturities of Investments", "Investing Activities", cash_flow["salesMaturitiesOfInvestments"]),
("Other Investing Activities", "Investing Activities", cash_flow["otherInvestingActivities"]),
("Investing Activities", "Net Cash Outflow (Investing)", cash_flow["netCashProvidedByInvestingActivities"]),
# Financing Activities (Outflow)
("Net Debt Issuance", "Financing Activities", cash_flow["netDebtIssuance"]),
#("Long-Term Net Debt Issuance", "Financing Activities", cash_flow["longTermNetDebtIssuance"]), # already under net debt
#("Short-Term Net Debt Issuance", "Financing Activities", cash_flow["shortTermNetDebtIssuance"]), # already under net debt
#("Net Stock Issuance", "Financing Activities", cash_flow["netStockIssuance"]),
("Net Common Stock Issuance", "Financing Activities", cash_flow["netCommonStockIssuance"]),
#("Common Stock Issuance", "Financing Activities", cash_flow["commonStockIssuance"]), # already under net common stock issuance
#("Common Stock Repurchased", "Financing Activities", cash_flow["commonStockRepurchased"]), # already under net common stock issuance
("Net Preferred Stock Issuance", "Financing Activities", cash_flow["netPreferredStockIssuance"]),
("Net Dividends Paid", "Financing Activities", cash_flow["netDividendsPaid"]),
#("Common Dividends Paid", "Financing Activities", cash_flow["commonDividendsPaid"]), # already under net dividends paid
#("Preferred Dividends Paid", "Financing Activities", cash_flow["preferredDividendsPaid"]), # already under net dividends paid
("Other Financing Activities", "Financing Activities", cash_flow["otherFinancingActivities"]),
("Financing Activities", "Net Cash Outflow (Financing)", cash_flow["netCashProvidedByFinancingActivities"]),
# Combine In and Out Flows
("Net Cash Inflow (Operating)", "Net Change in Cash", cash_flow["netCashProvidedByOperatingActivities"]),
("Net Cash Outflow (Investing)", "Net Change in Cash", cash_flow["netCashProvidedByInvestingActivities"]),
("Net Cash Outflow (Financing)", "Net Change in Cash", cash_flow["netCashProvidedByFinancingActivities"]),
("Effect of Forex on Cash", "Net Change in Cash", cash_flow["effectOfForexChangesOnCash"]),
# Beginning & Change β†’ Ending Balance
("Cash at Beginning of Period", "Cash at End of Period", cash_flow["cashAtBeginningOfPeriod"]),
("Net Change in Cash", "Cash at End of Period", cash_flow["netChangeInCash"]),
]
adjusted_flows = []
for src, tgt, val in flows:
if val >= 0:
adjusted_flows.append((src, tgt, val, 'rgba(50,200,50,0.6)'))
else:
# reverse direction for readability
adjusted_flows.append((tgt, src, -val, 'rgba(200,50,50,0.6)'))
labels = []
for s, t, _, _ in adjusted_flows:
if s not in labels: labels.append(s)
if t not in labels: labels.append(t)
node_values = { lbl: cash_flow[
{
"Net Income": "netIncome",
"Depreciation & Amortization": "depreciationAndAmortization",
"Deferred Income Tax": "deferredIncomeTax",
"Stock-Based Compensation": "stockBasedCompensation",
"Change in Working Capital": "changeInWorkingCapital",
"Accounts Receivables Ξ”": "accountsReceivables",
"Inventory Ξ”": "inventory",
"Accounts Payable Ξ”": "accountsPayables",
"Other Working Capital Ξ”": "otherWorkingCapital",
"Other Non-Cash Items": "otherNonCashItems",
"Operating Activities": "netCashProvidedByOperatingActivities",
"Investments in PP&E": "investmentsInPropertyPlantAndEquipment",
"Acquisitions, Net": "acquisitionsNet",
"Purchases of Investments": "purchasesOfInvestments",
"Sales/Maturities of Investments": "salesMaturitiesOfInvestments",
"Other Investing Activities": "otherInvestingActivities",
"Investing Activities": "netCashProvidedByInvestingActivities",
"Net Debt Issuance": "netDebtIssuance",
"Long-Term Net Debt Issuance": "longTermNetDebtIssuance",
"Short-Term Net Debt Issuance": "shortTermNetDebtIssuance",
"Net Stock Issuance": "netStockIssuance",
"Net Common Stock Issuance": "netCommonStockIssuance",
"Common Stock Issuance": "commonStockIssuance",
"Common Stock Repurchased": "commonStockRepurchased",
"Net Preferred Stock Issuance": "netPreferredStockIssuance",
"Net Dividends Paid": "netDividendsPaid",
"Common Dividends Paid": "commonDividendsPaid",
"Preferred Dividends Paid": "preferredDividendsPaid",
"Other Financing Activities": "otherFinancingActivities",
"Financing Activities": "netCashProvidedByFinancingActivities",
"Net Cash Inflow (Operating)": "netCashProvidedByOperatingActivities",
"Net Cash Outflow (Investing)": "netCashProvidedByInvestingActivities",
"Net Cash Outflow (Financing)": "netCashProvidedByFinancingActivities",
"Effect of Forex on Cash": "effectOfForexChangesOnCash",
"Net Change in Cash": "netChangeInCash",
"Cash at Beginning of Period": "cashAtBeginningOfPeriod",
"Cash at End of Period": "cashAtEndOfPeriod",
}[lbl]
] for lbl in labels }
def fmt(val):
if abs(val) >= 1e9: return f"${val/1e9:.1f}B"
if abs(val) >= 1e6: return f"${val/1e6:.1f}M"
if abs(val) >= 1e3: return f"${val/1e3:.0f}K"
return f"${val:.0f}"
idx = { lbl:i for i,lbl in enumerate(labels) }
source = [ idx[s] for s, t, _, _ in adjusted_flows ]
target = [ idx[t] for s, t, _, _ in adjusted_flows ]
value = [ v for _, _, v, _ in adjusted_flows ]
colors = [ c for _, _, _, c in adjusted_flows ]
label_with_values = []
for lbl in labels:
val = node_values[lbl]
base = lbl
if val < 0: base += " [NEGATIVE]"
label_with_values.append(f"{base} ({fmt(val)})")
fig = go.Figure(go.Sankey(
arrangement="snap",
node = dict(label=label_with_values, pad=15, thickness=20),
link = dict(source=source, target=target, value=value, color=colors)
))
fig.update_layout(
title_text=f"Cash Flow Statement Sankey β€” {symbol}",
height=height,
font_size=font_size
)
return fig
st.title("Financial Sankeys")
symbol = st.sidebar.text_input("Ticker symbol", "AMZN").upper()
# sidebar controls
bs_height = st.sidebar.slider("Balance Sheet height", 500, 1500, 800)
bs_font = st.sidebar.slider("Balance Sheet font size", 5, 15, 10)
is_height = st.sidebar.slider("Income Statement height", 500, 1500, 600)
is_font = st.sidebar.slider("Income Statement font size", 5, 15, 10)
cf_height = st.sidebar.slider("Cash Flow Statement height", 500, 1500, 800)
cf_font = st.sidebar.slider("Cash Flow Statement font size", 5, 15, 10)
# where the data came from
st.sidebar.markdown("## [Financial Modeling Prep API](https://site.financialmodelingprep.com/?utm_source=medium&utm_medium=medium&utm_campaign=damian8)\
\n\nFinancial statements are obtained from the FinancialModelingPrep API, feel free to sign up\
[here](https://site.financialmodelingprep.com/?utm_source=medium&utm_medium=medium&utm_campaign=damian8)\
if you wish.")
if symbol:
# Balance Sheet
st.header(f"Balance Sheet β€” {symbol}")
try:
df_bs = parse_json(f"https://financialmodelingprep.com/stable/balance-sheet-statement?symbol={symbol}&apikey={apikey}")
balance_sheet = df_bs.iloc[0]
fig_bs = draw_balance_sankey(balance_sheet, symbol.upper(), bs_height, bs_font)
st.plotly_chart(fig_bs, use_container_width=True)
except Exception as e:
st.error(f"{e}")
# Income Statement
st.header(f"Income Statement β€” {symbol}")
try:
df_is = parse_json(f"https://financialmodelingprep.com/stable/income-statement?symbol={symbol}&apikey={apikey}")
income_statement = df_is.iloc[0]
fig_is = draw_income_sankey(income_statement, symbol.upper(), is_height, is_font)
st.plotly_chart(fig_is, use_container_width=True)
except Exception as e:
st.error(f"{e}")
# Cash Flow Statement
st.header(f"Cash Flow Statement β€” {symbol}")
try:
df_cf = parse_json(f"https://financialmodelingprep.com/stable/cash-flow-statement?symbol={symbol}&apikey={apikey}")
cash_flow = df_cf.iloc[0]
fig_cf = draw_cashflow_sankey(cash_flow, symbol.upper(), cf_height, cf_font)
st.plotly_chart(fig_cf, use_container_width=True)
except Exception as e:
st.error(f"{e}")