Spaces:
Running
Running
File size: 16,578 Bytes
3fe0726 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
"""
Financial analysis engine for fundamental metrics.
Calculates growth, margins, returns, and cash flow metrics.
"""
import pandas as pd
import numpy as np
from typing import Dict, Optional, Tuple, List
from datetime import datetime
class FinancialAnalyzer:
"""Analyzes financial statements and calculates key metrics"""
def __init__(self, financial_data: Dict):
"""
Initialize analyzer with fetched financial data
Args:
financial_data: Complete dataset from FinancialDataFetcher
"""
self.data = financial_data
self.ticker = financial_data.get('ticker')
self.metrics = financial_data.get('metrics', {})
self.statements = financial_data.get('financial_statements', {})
self.company_info = financial_data.get('company_info', {})
def analyze_growth(self) -> Dict:
"""
Analyze revenue and earnings growth trends
Returns:
Dictionary with growth metrics
"""
results = {
'revenue_growth_ttm': self.metrics.get('revenue_growth'),
'earnings_growth_ttm': self.metrics.get('earnings_growth'),
}
# Calculate historical growth from income statement
try:
income_stmt = self.statements.get('income_statement')
if income_stmt is not None and not income_stmt.empty:
# Revenue growth (most recent vs 1 year ago)
if 'Total Revenue' in income_stmt.index:
revenues = income_stmt.loc['Total Revenue'].values
if len(revenues) >= 2:
results['revenue_growth_yoy'] = ((revenues[0] - revenues[1]) / abs(revenues[1])) if revenues[1] != 0 else None
# 3-year CAGR if available
if len(revenues) >= 3:
years = min(len(revenues) - 1, 3)
cagr = (revenues[0] / revenues[years]) ** (1/years) - 1 if revenues[years] != 0 else None
results['revenue_cagr_3y'] = cagr
# Net income growth
if 'Net Income' in income_stmt.index:
net_incomes = income_stmt.loc['Net Income'].values
if len(net_incomes) >= 2:
# Handle negative values
if net_incomes[1] != 0:
results['net_income_growth_yoy'] = ((net_incomes[0] - net_incomes[1]) / abs(net_incomes[1]))
except Exception as e:
print(f"Error calculating historical growth: {e}")
# Growth assessment
revenue_growth = results.get('revenue_growth_yoy') or results.get('revenue_growth_ttm')
earnings_growth = results.get('net_income_growth_yoy') or results.get('earnings_growth_ttm')
results['growth_quality'] = self._assess_growth_quality(revenue_growth, earnings_growth)
return results
def analyze_margins(self) -> Dict:
"""
Analyze profitability margins and trends
Returns:
Dictionary with margin metrics
"""
results = {
'gross_margin': self.metrics.get('gross_margin'),
'operating_margin': self.metrics.get('operating_margin'),
'profit_margin': self.metrics.get('profit_margin'),
'ebitda_margin': self.metrics.get('ebitda_margin'),
}
# Calculate margin trends from income statement
try:
income_stmt = self.statements.get('income_statement')
if income_stmt is not None and not income_stmt.empty:
# Calculate margins for multiple periods
if 'Total Revenue' in income_stmt.index:
revenues = income_stmt.loc['Total Revenue'].values
# Gross margin trend
if 'Gross Profit' in income_stmt.index and len(revenues) >= 2:
gross_profits = income_stmt.loc['Gross Profit'].values
margins = [gp / rev if rev != 0 else None for gp, rev in zip(gross_profits, revenues)]
margins = [m for m in margins if m is not None]
if len(margins) >= 2:
results['gross_margin_current'] = margins[0]
results['gross_margin_trend'] = margins[0] - margins[1]
results['gross_margin_stable'] = abs(margins[0] - margins[1]) < 0.02 # Within 2%
# Operating margin trend
if 'Operating Income' in income_stmt.index and len(revenues) >= 2:
op_incomes = income_stmt.loc['Operating Income'].values
margins = [oi / rev if rev != 0 else None for oi, rev in zip(op_incomes, revenues)]
margins = [m for m in margins if m is not None]
if len(margins) >= 2:
results['operating_margin_current'] = margins[0]
results['operating_margin_trend'] = margins[0] - margins[1]
results['operating_leverage'] = margins[0] > margins[1] # Expanding margins
# Net margin trend
if 'Net Income' in income_stmt.index and len(revenues) >= 2:
net_incomes = income_stmt.loc['Net Income'].values
margins = [ni / rev if rev != 0 else None for ni, rev in zip(net_incomes, revenues)]
margins = [m for m in margins if m is not None]
if len(margins) >= 2:
results['net_margin_current'] = margins[0]
results['net_margin_trend'] = margins[0] - margins[1]
except Exception as e:
print(f"Error calculating margin trends: {e}")
# Margin assessment
results['margin_quality'] = self._assess_margin_quality(results)
return results
def analyze_returns(self) -> Dict:
"""
Analyze return on capital metrics
Returns:
Dictionary with return metrics
"""
results = {
'roe': self.metrics.get('return_on_equity'),
'roa': self.metrics.get('return_on_assets'),
}
# Calculate ROIC (Return on Invested Capital)
try:
income_stmt = self.statements.get('income_statement')
balance_sheet = self.statements.get('balance_sheet')
if income_stmt is not None and balance_sheet is not None:
if not income_stmt.empty and not balance_sheet.empty:
# NOPAT = Net Operating Profit After Tax
if 'Operating Income' in income_stmt.index and 'Tax Provision' in income_stmt.index:
op_income = income_stmt.loc['Operating Income'].iloc[0]
total_revenue = income_stmt.loc['Total Revenue'].iloc[0] if 'Total Revenue' in income_stmt.index else 1
tax_provision = income_stmt.loc['Tax Provision'].iloc[0]
# Estimate tax rate
pretax_income = income_stmt.loc['Pretax Income'].iloc[0] if 'Pretax Income' in income_stmt.index else op_income
tax_rate = abs(tax_provision / pretax_income) if pretax_income != 0 else 0.21
nopat = op_income * (1 - tax_rate)
# Invested Capital = Total Debt + Total Equity - Cash
total_debt = self.metrics.get('total_debt', 0)
total_assets = balance_sheet.loc['Total Assets'].iloc[0] if 'Total Assets' in balance_sheet.index else 0
total_liabilities = balance_sheet.loc['Total Liabilities Net Minority Interest'].iloc[0] if 'Total Liabilities Net Minority Interest' in balance_sheet.index else 0
equity = total_assets - total_liabilities
cash = self.metrics.get('total_cash', 0)
invested_capital = total_debt + equity - cash
if invested_capital > 0:
results['roic'] = nopat / invested_capital
except Exception as e:
print(f"Error calculating ROIC: {e}")
# Returns assessment
results['returns_quality'] = self._assess_returns_quality(results)
return results
def analyze_cash_flow(self) -> Dict:
"""
Analyze cash flow metrics
Returns:
Dictionary with cash flow metrics
"""
results = {
'operating_cash_flow': self.metrics.get('operating_cash_flow', 0),
'free_cash_flow': self.metrics.get('free_cash_flow', 0),
}
# Calculate FCF margin and conversion
try:
income_stmt = self.statements.get('income_statement')
cashflow_stmt = self.statements.get('cash_flow')
if income_stmt is not None and not income_stmt.empty:
revenue = income_stmt.loc['Total Revenue'].iloc[0] if 'Total Revenue' in income_stmt.index else 0
net_income = income_stmt.loc['Net Income'].iloc[0] if 'Net Income' in income_stmt.index else 0
if revenue > 0:
results['fcf_margin'] = results['free_cash_flow'] / revenue
results['ocf_margin'] = results['operating_cash_flow'] / revenue
if net_income != 0:
results['fcf_conversion'] = results['free_cash_flow'] / net_income
# Cash flow trend
if cashflow_stmt is not None and not cashflow_stmt.empty:
if 'Free Cash Flow' in cashflow_stmt.index:
fcf_values = cashflow_stmt.loc['Free Cash Flow'].values
if len(fcf_values) >= 2:
results['fcf_growth'] = ((fcf_values[0] - fcf_values[1]) / abs(fcf_values[1])) if fcf_values[1] != 0 else None
results['fcf_positive_trend'] = fcf_values[0] > fcf_values[1]
except Exception as e:
print(f"Error calculating cash flow metrics: {e}")
# Cash flow assessment
results['cash_flow_quality'] = self._assess_cash_flow_quality(results)
return results
def analyze_financial_health(self) -> Dict:
"""
Analyze financial health and leverage
Returns:
Dictionary with financial health metrics
"""
results = {
'total_cash': self.metrics.get('total_cash', 0),
'total_debt': self.metrics.get('total_debt', 0),
'debt_to_equity': self.metrics.get('debt_to_equity'),
'current_ratio': self.metrics.get('current_ratio'),
'quick_ratio': self.metrics.get('quick_ratio'),
}
# Calculate net debt
results['net_debt'] = results['total_debt'] - results['total_cash']
# Calculate interest coverage
try:
income_stmt = self.statements.get('income_statement')
if income_stmt is not None and not income_stmt.empty:
if 'Operating Income' in income_stmt.index and 'Interest Expense' in income_stmt.index:
op_income = income_stmt.loc['Operating Income'].iloc[0]
interest = abs(income_stmt.loc['Interest Expense'].iloc[0])
if interest > 0:
results['interest_coverage'] = op_income / interest
except Exception as e:
print(f"Error calculating interest coverage: {e}")
# Health assessment
results['financial_health_quality'] = self._assess_financial_health(results)
return results
def _assess_growth_quality(self, revenue_growth: Optional[float], earnings_growth: Optional[float]) -> str:
"""Assess growth quality"""
if revenue_growth is None or earnings_growth is None:
return "Insufficient Data"
if revenue_growth > 0.15 and earnings_growth > 0.15:
return "Strong"
elif revenue_growth > 0.08 and earnings_growth > 0.08:
return "Good"
elif revenue_growth > 0 and earnings_growth > 0:
return "Moderate"
else:
return "Weak"
def _assess_margin_quality(self, margins: Dict) -> str:
"""Assess margin quality"""
operating_margin = margins.get('operating_margin_current') or margins.get('operating_margin')
margin_trend = margins.get('operating_margin_trend')
if operating_margin is None:
return "Insufficient Data"
if operating_margin > 0.20 and (margin_trend is None or margin_trend >= 0):
return "Excellent"
elif operating_margin > 0.10 and (margin_trend is None or margin_trend >= 0):
return "Good"
elif operating_margin > 0.05:
return "Moderate"
else:
return "Weak"
def _assess_returns_quality(self, returns: Dict) -> str:
"""Assess returns quality"""
roe = returns.get('roe')
roic = returns.get('roic')
primary_return = roic if roic is not None else roe
if primary_return is None:
return "Insufficient Data"
if primary_return > 0.20:
return "Excellent"
elif primary_return > 0.15:
return "Good"
elif primary_return > 0.10:
return "Moderate"
else:
return "Weak"
def _assess_cash_flow_quality(self, cash_flow: Dict) -> str:
"""Assess cash flow quality"""
fcf = cash_flow.get('free_cash_flow', 0)
fcf_conversion = cash_flow.get('fcf_conversion')
if fcf <= 0:
return "Negative"
if fcf_conversion and fcf_conversion > 1.0:
return "Excellent"
elif fcf_conversion and fcf_conversion > 0.8:
return "Good"
elif fcf > 0:
return "Moderate"
else:
return "Weak"
def _assess_financial_health(self, health: Dict) -> str:
"""Assess financial health"""
net_debt = health.get('net_debt', 0)
current_ratio = health.get('current_ratio')
interest_coverage = health.get('interest_coverage')
# Net cash position is excellent
if net_debt < 0:
return "Excellent"
# Check liquidity and debt coverage
healthy_liquidity = current_ratio and current_ratio > 1.5
healthy_coverage = interest_coverage and interest_coverage > 3
if healthy_liquidity and healthy_coverage:
return "Good"
elif (current_ratio and current_ratio > 1.0) or (interest_coverage and interest_coverage > 1.5):
return "Moderate"
else:
return "Weak"
def generate_summary(self) -> Dict:
"""
Generate comprehensive analysis summary
Returns:
Complete analysis results
"""
return {
'ticker': self.ticker,
'company_name': self.company_info.get('company_name', self.ticker),
'sector': self.company_info.get('sector', 'Unknown'),
'industry': self.company_info.get('industry', 'Unknown'),
'analysis_date': datetime.now().isoformat(),
'growth_analysis': self.analyze_growth(),
'margin_analysis': self.analyze_margins(),
'returns_analysis': self.analyze_returns(),
'cash_flow_analysis': self.analyze_cash_flow(),
'financial_health': self.analyze_financial_health()
}
if __name__ == "__main__":
# Test with sample data
print("This module is meant to be imported and used with data from data_fetcher.py")
|