Files

393 lines
19 KiB
Python
Raw Permalink Normal View History

"""
Calculate all financial metrics from base numbers
Implements all formulas from Step 4 of README
"""
import json
import os
from typing import Dict, Any, Optional
class FinancialMetricsCalculator:
"""Calculate financial metrics from raw financial statements"""
def __init__(self):
self.metrics = {}
def parse_yahoo_value(self, value_str: str) -> float:
"""Parse Yahoo Finance value strings (e.g., '416.16B', '26.92%')"""
if not value_str or value_str == 'N/A':
return 0
value_str = str(value_str).strip()
# Handle percentages
if '%' in value_str:
return float(value_str.replace('%', '').replace(',', '')) / 100
# Handle large numbers with suffixes
multipliers = {'K': 1e3, 'M': 1e6, 'B': 1e9, 'T': 1e12}
for suffix, multiplier in multipliers.items():
if value_str.endswith(suffix):
return float(value_str[:-1].replace(',', '')) * multiplier
# Regular number
try:
return float(value_str.replace(',', ''))
except:
return 0
def convert_yahoo_data(self, yahoo_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Convert Yahoo Finance scraped data to calculator format
"""
stats = yahoo_data.get('statistics', {})
profile = yahoo_data.get('profile', {})
# Parse all the available data
converted = {
'price': profile.get('current_price', 0),
'shares_outstanding': self.parse_yahoo_value(stats.get('shares_outstanding_5', 0)),
# Income Statement (TTM)
'revenue': self.parse_yahoo_value(stats.get('revenue_(ttm)', 0)),
'gross_profit': self.parse_yahoo_value(stats.get('gross_profit_(ttm)', 0)),
'net_income': self.parse_yahoo_value(stats.get('net_income_avi_to_common_(ttm)', 0)),
'eps': self.parse_yahoo_value(stats.get('diluted_eps_(ttm)', 0)),
'ebitda': self.parse_yahoo_value(stats.get('ebitda', 0)),
# Calculate COGS from revenue and gross profit
'cogs': 0, # Will calculate below
# Balance Sheet (MRQ)
'cash': self.parse_yahoo_value(stats.get('total_cash_(mrq)', 0)),
'total_debt': self.parse_yahoo_value(stats.get('total_debt_(mrq)', 0)),
'shareholders_equity': 0, # Will calculate below
# Cash Flow (TTM)
'operating_cash_flow': self.parse_yahoo_value(stats.get('operating_cash_flow_(ttm)', 0)),
'free_cash_flow': self.parse_yahoo_value(stats.get('levered_free_cash_flow_(ttm)', 0)),
# Dividends
'dividends_per_share': self.parse_yahoo_value(stats.get('trailing_annual_dividend_rate_3', 0)),
# Growth rates (already in percentage form)
'revenue_growth_yoy': self.parse_yahoo_value(stats.get('quarterly_revenue_growth_(yoy)', 0)),
'eps_growth_yoy': self.parse_yahoo_value(stats.get('quarterly_earnings_growth_(yoy)', 0)),
# Ratios already calculated by Yahoo
'profit_margin': self.parse_yahoo_value(stats.get('profit_margin', 0)),
'operating_margin': self.parse_yahoo_value(stats.get('operating_margin_(ttm)', 0)),
'return_on_assets': self.parse_yahoo_value(stats.get('return_on_assets_(ttm)', 0)),
'return_on_equity': self.parse_yahoo_value(stats.get('return_on_equity_(ttm)', 0)),
'current_ratio': self.parse_yahoo_value(stats.get('current_ratio_(mrq)', 0)),
'book_value_per_share': self.parse_yahoo_value(stats.get('book_value_per_share_(mrq)', 0)),
# Additional balance sheet items from Yahoo
'current_liabilities': 0, # Will be calculated from current ratio
'current_assets': 0, # Will be calculated from current ratio
}
# Calculate derived values
revenue = converted['revenue']
gross_profit = converted['gross_profit']
converted['cogs'] = revenue - gross_profit if revenue > 0 and gross_profit > 0 else 0
# Calculate shareholders equity from book value per share
shares = converted['shares_outstanding']
book_value_per_share = converted['book_value_per_share']
converted['shareholders_equity'] = book_value_per_share * shares if shares > 0 else 0
# Calculate operating income from operating margin
operating_margin = converted['operating_margin']
converted['operating_income'] = revenue * operating_margin if revenue > 0 and operating_margin > 0 else 0
converted['ebit'] = converted['operating_income']
# Estimate assets and liabilities
if converted['total_debt'] > 0 and converted['shareholders_equity'] > 0:
converted['total_liabilities'] = converted['total_debt']
converted['total_assets'] = converted['shareholders_equity'] + converted['total_liabilities']
# Calculate current assets and liabilities from current ratio
# Current Ratio = Current Assets / Current Liabilities
# We know: Current Ratio and Cash
# Estimate: if current ratio is available, use cash as baseline
current_ratio = converted.get('current_ratio', 0)
cash = converted.get('cash', 0)
if current_ratio > 0 and cash > 0:
# Rough estimate: assume cash is ~50% of current assets for tech companies
estimated_current_assets = cash * 2
converted['current_assets'] = estimated_current_assets
converted['current_liabilities'] = estimated_current_assets / current_ratio
return converted
def calculate_all_metrics(self, financial_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate all financial metrics from base financial data
Args:
financial_data: Dictionary containing:
- price: Current stock price
- shares_outstanding: Number of shares
- income_statement: Revenue, COGS, Operating Income, Net Income, etc.
- balance_sheet: Assets, Liabilities, Equity, Cash, Debt, etc.
- cash_flow: Operating CF, Investing CF, Financing CF, etc.
Returns:
Dictionary with all calculated metrics
"""
metrics = {}
# Extract base data
price = financial_data.get('price', 0)
shares = financial_data.get('shares_outstanding', 0)
# Income Statement
revenue = financial_data.get('revenue', 0)
cogs = financial_data.get('cogs', 0)
gross_profit = financial_data.get('gross_profit', revenue - cogs)
operating_income = financial_data.get('operating_income', 0)
net_income = financial_data.get('net_income', 0)
eps = financial_data.get('eps', net_income / shares if shares > 0 else 0)
ebit = financial_data.get('ebit', operating_income)
ebitda = financial_data.get('ebitda', 0)
interest_expense = financial_data.get('interest_expense', 0)
taxes = financial_data.get('taxes', 0)
# Balance Sheet
total_assets = financial_data.get('total_assets', 0)
current_assets = financial_data.get('current_assets', 0)
total_liabilities = financial_data.get('total_liabilities', 0)
current_liabilities = financial_data.get('current_liabilities', 0)
total_debt = financial_data.get('total_debt', 0)
long_term_debt = financial_data.get('long_term_debt', 0)
shareholders_equity = financial_data.get('shareholders_equity', 0)
cash = financial_data.get('cash', 0)
accounts_receivable = financial_data.get('accounts_receivable', 0)
inventory = financial_data.get('inventory', 0)
accounts_payable = financial_data.get('accounts_payable', 0)
retained_earnings = financial_data.get('retained_earnings', 0)
# Cash Flow
operating_cf = financial_data.get('operating_cash_flow', 0)
investing_cf = financial_data.get('investing_cash_flow', 0)
financing_cf = financial_data.get('financing_cash_flow', 0)
capex = financial_data.get('capex', 0)
free_cash_flow = financial_data.get('free_cash_flow', operating_cf - capex)
# Other
dividends_per_share = financial_data.get('dividends_per_share', 0)
book_value_per_share = shareholders_equity / shares if shares > 0 else 0
# Calculate Market Cap and Enterprise Value
market_cap = price * shares
enterprise_value = market_cap + total_debt - cash
# === VALUATION RATIOS ===
metrics['pe_ratio'] = price / eps if eps > 0 else None
metrics['pb_ratio'] = price / book_value_per_share if book_value_per_share > 0 else None
metrics['ps_ratio'] = market_cap / revenue if revenue > 0 else None
metrics['price_to_cash_flow'] = price / (operating_cf / shares) if operating_cf > 0 and shares > 0 else None
metrics['ev_ebitda'] = enterprise_value / ebitda if ebitda > 0 else None
metrics['ev_ebit'] = enterprise_value / ebit if ebit > 0 else None
metrics['dividend_yield'] = dividends_per_share / price if price > 0 else None
metrics['price_to_fcf'] = price / (free_cash_flow / shares) if free_cash_flow > 0 and shares > 0 else None
metrics['ev_to_sales'] = enterprise_value / revenue if revenue > 0 else None
# PEG Ratio (requires growth rate from historical data)
eps_growth = financial_data.get('eps_growth_yoy', 0)
pe_ratio = metrics['pe_ratio']
metrics['peg_ratio'] = pe_ratio / (eps_growth * 100) if pe_ratio and eps_growth > 0 else None
# === PROFITABILITY RATIOS ===
metrics['gross_margin'] = (revenue - cogs) / revenue if revenue > 0 else None
metrics['operating_margin'] = operating_income / revenue if revenue > 0 else None
metrics['net_margin'] = net_income / revenue if revenue > 0 else None
metrics['roe'] = net_income / shareholders_equity if shareholders_equity > 0 else None
metrics['roa'] = net_income / total_assets if total_assets > 0 else None
metrics['roce'] = ebit / (total_assets - current_liabilities) if (total_assets - current_liabilities) > 0 else None
# ROIC = NOPAT / Invested Capital
tax_rate = taxes / (net_income + taxes) if (net_income + taxes) > 0 else 0.25
nopat = ebit * (1 - tax_rate)
invested_capital = shareholders_equity + total_debt
metrics['roic'] = nopat / invested_capital if invested_capital > 0 else None
metrics['ebitda_margin'] = ebitda / revenue if revenue > 0 else None
# === LEVERAGE RATIOS ===
metrics['debt_to_equity'] = total_liabilities / shareholders_equity if shareholders_equity > 0 else None
metrics['debt_to_assets'] = total_debt / total_assets if total_assets > 0 else None
metrics['interest_coverage'] = ebit / interest_expense if interest_expense > 0 else None
metrics['financial_leverage'] = total_assets / shareholders_equity if shareholders_equity > 0 else None
# === LIQUIDITY RATIOS ===
metrics['current_ratio'] = current_assets / current_liabilities if current_liabilities > 0 else None
quick_assets = cash + accounts_receivable
metrics['quick_ratio'] = quick_assets / current_liabilities if current_liabilities > 0 else None
metrics['cash_ratio'] = cash / current_liabilities if current_liabilities > 0 else None
working_capital = current_assets - current_liabilities
metrics['working_capital_ratio'] = working_capital / revenue if revenue > 0 else None
# === EFFICIENCY RATIOS ===
metrics['inventory_turnover'] = cogs / inventory if inventory > 0 else None
metrics['asset_turnover'] = revenue / total_assets if total_assets > 0 else None
metrics['receivables_turnover'] = revenue / accounts_receivable if accounts_receivable > 0 else None
metrics['payables_turnover'] = cogs / accounts_payable if accounts_payable > 0 else None
metrics['days_sales_outstanding'] = (accounts_receivable / revenue) * 365 if revenue > 0 else None
metrics['days_inventory_outstanding'] = (inventory / cogs) * 365 if cogs > 0 else None
metrics['days_payable_outstanding'] = (accounts_payable / cogs) * 365 if cogs > 0 else None
# === GROWTH METRICS === (require historical data)
metrics['revenue_growth_yoy'] = financial_data.get('revenue_growth_yoy')
metrics['eps_growth_yoy'] = financial_data.get('eps_growth_yoy')
metrics['net_income_growth_yoy'] = financial_data.get('net_income_growth_yoy')
metrics['book_value_growth_yoy'] = financial_data.get('book_value_growth_yoy')
# === CASH FLOW METRICS ===
metrics['fcf_yield'] = free_cash_flow / market_cap if market_cap > 0 else None
metrics['operating_cf_ratio'] = operating_cf / current_liabilities if current_liabilities > 0 else None
metrics['capex_ratio'] = capex / operating_cf if operating_cf > 0 else None
# Add base values for reference
metrics['market_cap'] = market_cap
metrics['enterprise_value'] = enterprise_value
metrics['shares_outstanding'] = shares
metrics['book_value_per_share'] = book_value_per_share
return metrics
def calculate_growth_rates(self, current_data: Dict, historical_data: Dict) -> Dict[str, float]:
"""Calculate year-over-year growth rates"""
growth_rates = {}
# Revenue growth
current_rev = current_data.get('revenue', 0)
prev_rev = historical_data.get('revenue', 0)
if prev_rev > 0:
growth_rates['revenue_growth_yoy'] = (current_rev - prev_rev) / prev_rev
# EPS growth
current_eps = current_data.get('eps', 0)
prev_eps = historical_data.get('eps', 0)
if prev_eps != 0:
growth_rates['eps_growth_yoy'] = (current_eps - prev_eps) / abs(prev_eps)
# Net income growth
current_ni = current_data.get('net_income', 0)
prev_ni = historical_data.get('net_income', 0)
if prev_ni != 0:
growth_rates['net_income_growth_yoy'] = (current_ni - prev_ni) / abs(prev_ni)
# Book value growth
current_bv = current_data.get('shareholders_equity', 0)
prev_bv = historical_data.get('shareholders_equity', 0)
if prev_bv > 0:
growth_rates['book_value_growth_yoy'] = (current_bv - prev_bv) / prev_bv
return growth_rates
def format_metrics_for_display(self, metrics: Dict[str, Any]) -> str:
"""Format metrics for human-readable display"""
output = []
output.append("=" * 70)
output.append("FINANCIAL METRICS")
output.append("=" * 70)
# Valuation Ratios
output.append("\n[VALUATION RATIOS]")
output.append(f" P/E Ratio: {self._format_number(metrics.get('pe_ratio'))}")
output.append(f" PEG Ratio: {self._format_number(metrics.get('peg_ratio'))}")
output.append(f" P/B Ratio: {self._format_number(metrics.get('pb_ratio'))}")
output.append(f" P/S Ratio: {self._format_number(metrics.get('ps_ratio'))}")
output.append(f" EV/EBITDA: {self._format_number(metrics.get('ev_ebitda'))}")
output.append(f" Dividend Yield: {self._format_percent(metrics.get('dividend_yield'))}")
# Profitability Ratios
output.append("\n[PROFITABILITY RATIOS]")
output.append(f" Gross Margin: {self._format_percent(metrics.get('gross_margin'))}")
output.append(f" Operating Margin: {self._format_percent(metrics.get('operating_margin'))}")
output.append(f" Net Margin: {self._format_percent(metrics.get('net_margin'))}")
output.append(f" ROE: {self._format_percent(metrics.get('roe'))}")
output.append(f" ROA: {self._format_percent(metrics.get('roa'))}")
output.append(f" ROIC: {self._format_percent(metrics.get('roic'))}")
# Leverage Ratios
output.append("\n[LEVERAGE RATIOS]")
output.append(f" Debt/Equity: {self._format_number(metrics.get('debt_to_equity'))}")
output.append(f" Debt/Assets: {self._format_number(metrics.get('debt_to_assets'))}")
output.append(f" Interest Coverage: {self._format_number(metrics.get('interest_coverage'))}")
# Liquidity Ratios
output.append("\n[LIQUIDITY RATIOS]")
output.append(f" Current Ratio: {self._format_number(metrics.get('current_ratio'))}")
output.append(f" Quick Ratio: {self._format_number(metrics.get('quick_ratio'))}")
output.append(f" Cash Ratio: {self._format_number(metrics.get('cash_ratio'))}")
# Growth Metrics
output.append("\n[GROWTH METRICS (YoY)]")
output.append(f" Revenue Growth: {self._format_percent(metrics.get('revenue_growth_yoy'))}")
output.append(f" EPS Growth: {self._format_percent(metrics.get('eps_growth_yoy'))}")
output.append(f" Net Income Growth: {self._format_percent(metrics.get('net_income_growth_yoy'))}")
return "\n".join(output)
def _format_number(self, value: Optional[float], decimals: int = 2) -> str:
"""Format number for display"""
if value is None:
return "N/A"
return f"{value:.{decimals}f}"
def _format_percent(self, value: Optional[float], decimals: int = 2) -> str:
"""Format percentage for display"""
if value is None:
return "N/A"
return f"{value * 100:.{decimals}f}%"
def example_usage():
"""Example of how to use the calculator"""
# Example financial data
financial_data = {
'price': 50.00,
'shares_outstanding': 10_000_000,
'revenue': 100_000_000,
'cogs': 60_000_000,
'operating_income': 15_000_000,
'net_income': 10_000_000,
'eps': 1.00,
'ebit': 15_000_000,
'ebitda': 20_000_000,
'total_assets': 200_000_000,
'current_assets': 50_000_000,
'total_liabilities': 80_000_000,
'current_liabilities': 30_000_000,
'total_debt': 40_000_000,
'shareholders_equity': 120_000_000,
'cash': 20_000_000,
'operating_cash_flow': 18_000_000,
'capex': 5_000_000,
'free_cash_flow': 13_000_000,
'dividends_per_share': 0.50,
'eps_growth_yoy': 0.15,
'revenue_growth_yoy': 0.10
}
calculator = FinancialMetricsCalculator()
metrics = calculator.calculate_all_metrics(financial_data)
print(calculator.format_metrics_for_display(metrics))
# Save to JSON
with open('example_metrics.json', 'w') as f:
json.dump(metrics, f, indent=2)
if __name__ == "__main__":
example_usage()