Spaces:
Running
Running
| """ | |
| Stock Metrics Calculator | |
| Combines all individual metric functions and fetches data from yfinance | |
| Returns comprehensive DataFrame with all calculated metrics | |
| """ | |
| import yfinance as yf | |
| import pandas as pd | |
| import numpy as np | |
| from typing import Dict, List, Optional, Tuple | |
| from datetime import datetime | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| # Import all metric calculation functions | |
| from fundamental_analysis.metrics import * | |
| class StockMetricsCalculator: | |
| """Calculate comprehensive metrics for a single stock""" | |
| def __init__(self, ticker: str): | |
| """ | |
| Initialize calculator for a ticker | |
| Args: | |
| ticker: Stock ticker symbol (e.g., 'AAPL') | |
| """ | |
| self.ticker = ticker.upper() | |
| self.stock = yf.Ticker(self.ticker) | |
| self.data_fetched = False | |
| self.missing_metrics = [] | |
| # Raw data containers | |
| self.info = {} | |
| self.financials = {} | |
| self.balance_sheet = {} | |
| self.cashflow = {} | |
| self.quarterly_financials = {} | |
| self.quarterly_balance = {} | |
| self.quarterly_cashflow = {} | |
| def fetch_data(self) -> bool: | |
| """ | |
| Fetch all available data from yfinance | |
| Returns: | |
| True if successful, False otherwise | |
| """ | |
| try: | |
| # Get company info | |
| self.info = self.stock.info | |
| # Get financial statements | |
| self.financials = self.stock.financials | |
| self.balance_sheet = self.stock.balance_sheet | |
| self.cashflow = self.stock.cashflow | |
| # Get quarterly statements | |
| self.quarterly_financials = self.stock.quarterly_financials | |
| self.quarterly_balance = self.stock.quarterly_balance_sheet | |
| self.quarterly_cashflow = self.stock.quarterly_cashflow | |
| self.data_fetched = True | |
| print(f"✓ Data fetched successfully for {self.ticker}") | |
| return True | |
| except Exception as e: | |
| print(f"✗ Error fetching data for {self.ticker}: {str(e)}") | |
| return False | |
| def _get_from_statement(self, statement: pd.DataFrame, key: str, period: int = 0) -> Optional[float]: | |
| """ | |
| Safely get value from financial statement | |
| Args: | |
| statement: DataFrame from yfinance | |
| key: Row name to extract | |
| period: Column index (0 = most recent) | |
| Returns: | |
| Value or None if not found | |
| """ | |
| try: | |
| if statement.empty: | |
| return None | |
| if key in statement.index: | |
| values = statement.loc[key] | |
| if not values.empty and period < len(values): | |
| val = values.iloc[period] | |
| return float(val) if pd.notna(val) else None | |
| return None | |
| except: | |
| return None | |
| def _calculate_ttm(self, quarterly_statement: pd.DataFrame, key: str) -> Optional[float]: | |
| """Calculate TTM (Trailing Twelve Months) from quarterly data""" | |
| try: | |
| if quarterly_statement.empty or key not in quarterly_statement.index: | |
| return None | |
| values = quarterly_statement.loc[key].iloc[:4] # Last 4 quarters | |
| values = values.replace({"-": None}) | |
| values = values.dropna() | |
| if len(values) == 4 and values.notna().all(): | |
| return float(values.sum()) | |
| return None | |
| except: | |
| return None | |
| def calculate_all_metrics(self) -> pd.DataFrame: | |
| """ | |
| Calculate all available metrics | |
| Returns: | |
| DataFrame with metric names, values, formulas, and status | |
| """ | |
| if not self.data_fetched: | |
| self.fetch_data() | |
| metrics_data = [] | |
| # ============================================================================ | |
| # EXTRACT RAW DATA | |
| # ============================================================================ | |
| print("\nExtracting raw financial data...") | |
| # Price and shares | |
| price = self.info.get('currentPrice') or self.info.get('regularMarketPrice') | |
| diluted_shares = self.info.get('sharesOutstanding') | |
| # Income statement (use TTM when available) | |
| revenue = self._calculate_ttm(self.quarterly_financials, 'Total Revenue') or \ | |
| self._get_from_statement(self.financials, 'Total Revenue') | |
| revenue_prior = self._get_from_statement(self.financials, 'Total Revenue', 1) | |
| cogs = self._calculate_ttm(self.quarterly_financials, 'Cost Of Revenue') or \ | |
| self._get_from_statement(self.financials, 'Cost Of Revenue') | |
| gross_profit = self._calculate_ttm(self.quarterly_financials, 'Gross Profit') or \ | |
| self._get_from_statement(self.financials, 'Gross Profit') | |
| ebit = self._calculate_ttm(self.quarterly_financials, 'EBIT') or \ | |
| self._get_from_statement(self.financials, 'EBIT') | |
| ebitda = self.info.get('ebitda') or \ | |
| self._calculate_ttm(self.quarterly_financials, 'EBITDA') or \ | |
| self._get_from_statement(self.financials, 'EBITDA') | |
| net_income = self._calculate_ttm(self.quarterly_financials, 'Net Income') or \ | |
| self._get_from_statement(self.financials, 'Net Income') | |
| net_income_prior = self._get_from_statement(self.financials, 'Net Income', 1) | |
| interest_expense = abs(self._calculate_ttm(self.quarterly_financials, 'Interest Expense') or \ | |
| self._get_from_statement(self.financials, 'Interest Expense') or 0) | |
| # EPS | |
| eps_ttm = self.info.get('trailingEps') | |
| eps_forward = self.info.get('forwardEps') | |
| eps_prior = self.info.get('trailingEps') # Would need historical data for accurate prior | |
| # Balance sheet | |
| total_assets = self._get_from_statement(self.balance_sheet, 'Total Assets') | |
| current_assets = self._get_from_statement(self.balance_sheet, 'Current Assets') | |
| current_liabilities = self._get_from_statement(self.balance_sheet, 'Current Liabilities') | |
| total_debt = self.info.get('totalDebt') or \ | |
| (self._get_from_statement(self.balance_sheet, 'Long Term Debt') or 0) + \ | |
| (self._get_from_statement(self.balance_sheet, 'Short Term Debt') or 0) | |
| cash = self._get_from_statement(self.balance_sheet, 'Cash And Cash Equivalents') or 0 | |
| cash_and_st_investments = self._get_from_statement(self.balance_sheet, 'Cash Cash Equivalents And Short Term Investments') or cash | |
| total_equity = self._get_from_statement(self.balance_sheet, 'Total Equity Gross Minority Interest') or \ | |
| self._get_from_statement(self.balance_sheet, 'Stockholders Equity') | |
| total_equity_prior = self._get_from_statement(self.balance_sheet, 'Stockholders Equity', 1) | |
| receivables = self._get_from_statement(self.balance_sheet, 'Receivables') or 0 | |
| inventory = self._get_from_statement(self.balance_sheet, 'Inventory') or 0 | |
| book_value_per_share = self.info.get('bookValue') | |
| # Cash flow | |
| cfo = self._calculate_ttm(self.quarterly_cashflow, 'Operating Cash Flow') or \ | |
| self._get_from_statement(self.cashflow, 'Operating Cash Flow') | |
| capex = abs(self._calculate_ttm(self.quarterly_cashflow, 'Capital Expenditure') or \ | |
| self._get_from_statement(self.cashflow, 'Capital Expenditure') or 0) | |
| dividends_paid = abs(self._get_from_statement(self.cashflow, 'Cash Dividends Paid') or 0) | |
| stock_repurchased = abs(self._get_from_statement(self.cashflow, 'Repurchase Of Capital Stock') or 0) | |
| # Tax rate | |
| tax_rate = self.info.get('effectiveTaxRate') or 0.21 # Default to 21% if not available | |
| # Growth rates | |
| earnings_growth = self.info.get('earningsGrowth') or 0 | |
| revenue_growth_rate = self.info.get('revenueGrowth') or 0 | |
| # ============================================================================ | |
| # CALCULATE DERIVED VALUES | |
| # ============================================================================ | |
| # Market cap and EV | |
| market_cap = calculate_market_cap(price, diluted_shares) if (price and diluted_shares) else self.info.get('marketCap') | |
| enterprise_value = calculate_enterprise_value(market_cap, total_debt, cash) if market_cap else self.info.get('enterpriseValue') | |
| # Free cash flow | |
| free_cash_flow = calculate_free_cash_flow(cfo, capex) if cfo else None | |
| # Averages for ratio calculations | |
| avg_equity = calculate_average(total_equity, total_equity_prior) if (total_equity and total_equity_prior) else total_equity | |
| # Invested capital | |
| invested_capital = calculate_invested_capital(total_equity, total_debt, cash) if (total_equity and total_debt) else None | |
| # ============================================================================ | |
| # CALCULATE ALL METRICS | |
| # ============================================================================ | |
| print("Calculating metrics...") | |
| # --- 1. VALUATION METRICS --- | |
| metrics_data.append({ | |
| 'Category': 'Valuation', | |
| 'Metric': 'Market Capitalization', | |
| 'Value': market_cap, | |
| 'Formula': 'Price × Diluted Shares', | |
| 'Status': 'Available' if market_cap else 'Missing' | |
| }) | |
| metrics_data.append({ | |
| 'Category': 'Valuation', | |
| 'Metric': 'Enterprise Value (EV)', | |
| 'Value': enterprise_value, | |
| 'Formula': 'Market Cap + Total Debt - Cash', | |
| 'Status': 'Available' if enterprise_value else 'Missing' | |
| }) | |
| pe_ratio = calculate_pe_ratio(price, eps_ttm) | |
| metrics_data.append({ | |
| 'Category': 'Valuation', | |
| 'Metric': 'P/E Ratio (TTM)', | |
| 'Value': pe_ratio, | |
| 'Formula': 'Price / EPS', | |
| 'Status': 'Available' if pe_ratio else 'Missing', | |
| 'Threshold': '< sector median = undervalued' | |
| }) | |
| pe_forward = calculate_pe_ratio(price, eps_forward) | |
| metrics_data.append({ | |
| 'Category': 'Valuation', | |
| 'Metric': 'P/E Ratio (Forward)', | |
| 'Value': pe_forward, | |
| 'Formula': 'Price / Forward EPS', | |
| 'Status': 'Available' if pe_forward else 'Missing', | |
| 'Threshold': 'Use for valuation comparisons' | |
| }) | |
| peg_ratio = calculate_peg_ratio(pe_forward or pe_ratio, earnings_growth) | |
| metrics_data.append({ | |
| 'Category': 'Valuation', | |
| 'Metric': 'PEG Ratio', | |
| 'Value': peg_ratio, | |
| 'Formula': 'P/E / (EPS Growth % × 100)', | |
| 'Status': 'Available' if peg_ratio else 'Missing', | |
| 'Threshold': '< 0.8 = BUY, 0.8-1.2 = HOLD, > 1.5 = SELL' | |
| }) | |
| ev_ebitda = calculate_ev_ebitda(enterprise_value, ebitda) | |
| metrics_data.append({ | |
| 'Category': 'Valuation', | |
| 'Metric': 'EV/EBITDA', | |
| 'Value': ev_ebitda, | |
| 'Formula': 'Enterprise Value / EBITDA', | |
| 'Status': 'Available' if ev_ebitda else 'Missing', | |
| 'Threshold': 'Compare to sector median' | |
| }) | |
| price_to_fcf = calculate_price_to_fcf(market_cap, free_cash_flow) | |
| metrics_data.append({ | |
| 'Category': 'Valuation', | |
| 'Metric': 'Price / FCF', | |
| 'Value': price_to_fcf, | |
| 'Formula': 'Market Cap / Free Cash Flow', | |
| 'Status': 'Available' if price_to_fcf else 'Missing' | |
| }) | |
| fcf_yield_eq = calculate_fcf_yield_equity(free_cash_flow, market_cap) | |
| metrics_data.append({ | |
| 'Category': 'Valuation', | |
| 'Metric': 'FCF Yield (Equity) %', | |
| 'Value': fcf_yield_eq, | |
| 'Formula': '(FCF / Market Cap) × 100', | |
| 'Status': 'Available' if fcf_yield_eq else 'Missing', | |
| 'Threshold': '> 6% = BUY, 4-6% = HOLD, < 3% = SELL', | |
| 'Priority': 'HIGHEST' | |
| }) | |
| fcf_yield_ev = calculate_fcf_yield_enterprise(free_cash_flow, enterprise_value) | |
| metrics_data.append({ | |
| 'Category': 'Valuation', | |
| 'Metric': 'FCF Yield (Enterprise) %', | |
| 'Value': fcf_yield_ev, | |
| 'Formula': '(FCF / EV) × 100', | |
| 'Status': 'Available' if fcf_yield_ev else 'Missing', | |
| 'Threshold': '> 6% = BUY (preferred metric)', | |
| 'Priority': 'HIGHEST' | |
| }) | |
| pb_ratio = calculate_price_to_book(price, book_value_per_share) | |
| metrics_data.append({ | |
| 'Category': 'Valuation', | |
| 'Metric': 'Price / Book', | |
| 'Value': pb_ratio, | |
| 'Formula': 'Price / Book Value per Share', | |
| 'Status': 'Available' if pb_ratio else 'Missing' | |
| }) | |
| # --- 2. PROFITABILITY & MARGINS --- | |
| gross_margin = calculate_gross_margin(revenue, cogs) | |
| metrics_data.append({ | |
| 'Category': 'Profitability', | |
| 'Metric': 'Gross Margin %', | |
| 'Value': gross_margin, | |
| 'Formula': '((Revenue - COGS) / Revenue) × 100', | |
| 'Status': 'Available' if gross_margin else 'Missing', | |
| 'Threshold': '> 40% good, > 60% excellent' | |
| }) | |
| ebitda_margin = calculate_ebitda_margin(ebitda, revenue) | |
| metrics_data.append({ | |
| 'Category': 'Profitability', | |
| 'Metric': 'EBITDA Margin %', | |
| 'Value': ebitda_margin, | |
| 'Formula': '(EBITDA / Revenue) × 100', | |
| 'Status': 'Available' if ebitda_margin else 'Missing', | |
| 'Threshold': '> 20% excellent' | |
| }) | |
| ebit_margin = calculate_ebit_margin(ebit, revenue) | |
| metrics_data.append({ | |
| 'Category': 'Profitability', | |
| 'Metric': 'EBIT Margin %', | |
| 'Value': ebit_margin, | |
| 'Formula': '(EBIT / Revenue) × 100', | |
| 'Status': 'Available' if ebit_margin else 'Missing' | |
| }) | |
| net_margin = calculate_net_margin(net_income, revenue) | |
| metrics_data.append({ | |
| 'Category': 'Profitability', | |
| 'Metric': 'Net Margin %', | |
| 'Value': net_margin, | |
| 'Formula': '(Net Income / Revenue) × 100', | |
| 'Status': 'Available' if net_margin else 'Missing', | |
| 'Threshold': '> 10% good' | |
| }) | |
| # --- 3. CASH FLOW METRICS --- | |
| metrics_data.append({ | |
| 'Category': 'Cash Flow', | |
| 'Metric': 'Free Cash Flow', | |
| 'Value': free_cash_flow, | |
| 'Formula': 'CFO - CapEx', | |
| 'Status': 'Available' if free_cash_flow else 'Missing', | |
| 'Threshold': 'Must be positive', | |
| 'Priority': 'CRITICAL' | |
| }) | |
| fcf_per_share = calculate_fcf_per_share(free_cash_flow, diluted_shares) | |
| metrics_data.append({ | |
| 'Category': 'Cash Flow', | |
| 'Metric': 'FCF per Share', | |
| 'Value': fcf_per_share, | |
| 'Formula': 'FCF / Diluted Shares', | |
| 'Status': 'Available' if fcf_per_share else 'Missing' | |
| }) | |
| cash_conversion = calculate_cash_conversion(cfo, net_income) | |
| metrics_data.append({ | |
| 'Category': 'Cash Flow', | |
| 'Metric': 'Cash Conversion Ratio', | |
| 'Value': cash_conversion, | |
| 'Formula': 'CFO / Net Income', | |
| 'Status': 'Available' if cash_conversion else 'Missing', | |
| 'Threshold': '> 1.0 = quality earnings, < 1.0 RED FLAG', | |
| 'Priority': 'HIGH' | |
| }) | |
| # --- 4. LIQUIDITY & SOLVENCY --- | |
| current_ratio = calculate_current_ratio(current_assets, current_liabilities) | |
| metrics_data.append({ | |
| 'Category': 'Liquidity', | |
| 'Metric': 'Current Ratio', | |
| 'Value': current_ratio, | |
| 'Formula': 'Current Assets / Current Liabilities', | |
| 'Status': 'Available' if current_ratio else 'Missing', | |
| 'Threshold': '> 1.5 good' | |
| }) | |
| quick_ratio = calculate_quick_ratio(cash, 0, receivables, current_liabilities) | |
| metrics_data.append({ | |
| 'Category': 'Liquidity', | |
| 'Metric': 'Quick Ratio', | |
| 'Value': quick_ratio, | |
| 'Formula': '(Cash + Receivables) / Current Liabilities', | |
| 'Status': 'Available' if quick_ratio else 'Missing' | |
| }) | |
| net_debt_ebitda = calculate_net_debt_to_ebitda(total_debt, cash, ebitda) | |
| metrics_data.append({ | |
| 'Category': 'Solvency', | |
| 'Metric': 'Net Debt / EBITDA', | |
| 'Value': net_debt_ebitda, | |
| 'Formula': '(Total Debt - Cash) / EBITDA', | |
| 'Status': 'Available' if net_debt_ebitda else 'Missing', | |
| 'Threshold': '< 1 = Low risk, 1-3 = Moderate, > 3 = High risk', | |
| 'Priority': 'HIGH' | |
| }) | |
| interest_cov = calculate_interest_coverage(ebit, interest_expense) | |
| metrics_data.append({ | |
| 'Category': 'Solvency', | |
| 'Metric': 'Interest Coverage', | |
| 'Value': interest_cov, | |
| 'Formula': 'EBIT / Interest Expense', | |
| 'Status': 'Available' if interest_cov else 'Missing', | |
| 'Threshold': '> 3x safe, < 2x risky' | |
| }) | |
| debt_to_equity = calculate_debt_to_equity(total_debt, total_equity) | |
| metrics_data.append({ | |
| 'Category': 'Solvency', | |
| 'Metric': 'Debt / Equity', | |
| 'Value': debt_to_equity, | |
| 'Formula': 'Total Debt / Total Equity', | |
| 'Status': 'Available' if debt_to_equity else 'Missing' | |
| }) | |
| # --- 5. RETURNS & EFFICIENCY --- | |
| roe = calculate_roe(net_income, avg_equity) | |
| metrics_data.append({ | |
| 'Category': 'Returns', | |
| 'Metric': 'Return on Equity (ROE) %', | |
| 'Value': roe, | |
| 'Formula': '(Net Income / Avg Equity) × 100', | |
| 'Status': 'Available' if roe else 'Missing', | |
| 'Threshold': '> 15% good, > 20% excellent', | |
| 'Priority': 'VERY HIGH' | |
| }) | |
| roa = calculate_roa(net_income, total_assets) | |
| metrics_data.append({ | |
| 'Category': 'Returns', | |
| 'Metric': 'Return on Assets (ROA) %', | |
| 'Value': roa, | |
| 'Formula': '(Net Income / Total Assets) × 100', | |
| 'Status': 'Available' if roa else 'Missing' | |
| }) | |
| roic = calculate_roic(ebit, tax_rate, invested_capital) | |
| metrics_data.append({ | |
| 'Category': 'Returns', | |
| 'Metric': 'Return on Invested Capital (ROIC) %', | |
| 'Value': roic, | |
| 'Formula': '(EBIT × (1 - Tax Rate) / Invested Capital) × 100', | |
| 'Status': 'Available' if roic else 'Missing', | |
| 'Threshold': '> 10% good, > 15% excellent', | |
| 'Priority': 'VERY HIGH - Best quality indicator' | |
| }) | |
| # --- 6. GROWTH METRICS --- | |
| rev_growth = calculate_revenue_growth(revenue, revenue_prior) | |
| metrics_data.append({ | |
| 'Category': 'Growth', | |
| 'Metric': 'Revenue Growth (YoY) %', | |
| 'Value': rev_growth or (revenue_growth_rate * 100), | |
| 'Formula': '((Current Rev - Prior Rev) / Prior Rev) × 100', | |
| 'Status': 'Available' if (rev_growth or revenue_growth_rate) else 'Missing', | |
| 'Threshold': '> 10% good, > 20% excellent' | |
| }) | |
| eps_growth_calc = calculate_eps_growth(eps_ttm, eps_prior) | |
| metrics_data.append({ | |
| 'Category': 'Growth', | |
| 'Metric': 'EPS Growth (YoY) %', | |
| 'Value': eps_growth_calc or (earnings_growth * 100), | |
| 'Formula': '((Current EPS - Prior EPS) / Prior EPS) × 100', | |
| 'Status': 'Available' if (eps_growth_calc or earnings_growth) else 'Missing', | |
| 'Priority': 'HIGH' | |
| }) | |
| # --- 7. CAPITAL ALLOCATION --- | |
| payout_ratio = calculate_payout_ratio(dividends_paid, net_income) | |
| metrics_data.append({ | |
| 'Category': 'Capital Allocation', | |
| 'Metric': 'Payout Ratio %', | |
| 'Value': payout_ratio, | |
| 'Formula': '(Dividends / Net Income) × 100', | |
| 'Status': 'Available' if payout_ratio else 'Missing', | |
| 'Threshold': '< 60% sustainable' | |
| }) | |
| buyback_yield = calculate_buyback_yield(stock_repurchased, market_cap) | |
| metrics_data.append({ | |
| 'Category': 'Capital Allocation', | |
| 'Metric': 'Buyback Yield %', | |
| 'Value': buyback_yield, | |
| 'Formula': '(Buyback Cash / Market Cap) × 100', | |
| 'Status': 'Available' if buyback_yield else 'Missing' | |
| }) | |
| total_payout = calculate_total_payout_ratio(dividends_paid, stock_repurchased, net_income) | |
| metrics_data.append({ | |
| 'Category': 'Capital Allocation', | |
| 'Metric': 'Total Payout Ratio %', | |
| 'Value': total_payout, | |
| 'Formula': '((Dividends + Buybacks) / Net Income) × 100', | |
| 'Status': 'Available' if total_payout else 'Missing' | |
| }) | |
| # Create DataFrame | |
| df = pd.DataFrame(metrics_data) | |
| # Track missing metrics | |
| self.missing_metrics = df[df['Status'] == 'Missing']['Metric'].tolist() | |
| print(f"\n✓ Calculated {len(df)} metrics") | |
| print(f"✓ Available: {len(df[df['Status'] == 'Available'])}") | |
| print(f"✗ Missing: {len(self.missing_metrics)}") | |
| return df | |
| def get_summary_statistics(self, df: pd.DataFrame) -> Dict: | |
| """Generate summary statistics about the metrics""" | |
| total = len(df) | |
| available = len(df[df['Status'] == 'Available']) | |
| missing = total - available | |
| return { | |
| 'ticker': self.ticker, | |
| 'total_metrics': total, | |
| 'available_metrics': available, | |
| 'missing_metrics': missing, | |
| 'coverage_percentage': (available / total) * 100 if total > 0 else 0, | |
| 'missing_metric_list': self.missing_metrics | |
| } | |
| def calculate_metrics_for_ticker(ticker: str) -> Tuple[pd.DataFrame, Dict]: | |
| """ | |
| Main function to calculate all metrics for a ticker | |
| Args: | |
| ticker: Stock ticker symbol | |
| Returns: | |
| Tuple of (metrics_dataframe, summary_statistics) | |
| """ | |
| calculator = StockMetricsCalculator(ticker) | |
| if not calculator.fetch_data(): | |
| return pd.DataFrame(), {} | |
| metrics_df = calculator.calculate_all_metrics() | |
| summary = calculator.get_summary_statistics(metrics_df) | |
| return metrics_df, summary | |
| if __name__ == "__main__": | |
| # Test with a sample ticker | |
| test_ticker = "AAPL" | |
| print(f"Testing with {test_ticker}...") | |
| print("=" * 80) | |
| metrics_df, summary = calculate_metrics_for_ticker(test_ticker) | |
| if not metrics_df.empty: | |
| print("\n" + "=" * 80) | |
| print("SUMMARY STATISTICS") | |
| print("=" * 80) | |
| for key, value in summary.items(): | |
| if key != 'missing_metric_list': | |
| print(f"{key}: {value}") | |
| print("\n" + "=" * 80) | |
| print("SAMPLE METRICS (First 10)") | |
| print("=" * 80) | |
| print(metrics_df[['Category', 'Metric', 'Value', 'Status']].head(10).to_string(index=False)) | |
| print("\n" + "=" * 80) | |
| print("HIGH PRIORITY METRICS") | |
| print("=" * 80) | |
| priority_metrics = metrics_df[metrics_df['Priority'].notna()][['Metric', 'Value', 'Threshold', 'Priority']] | |
| print(priority_metrics.to_string(index=False)) | |
| if summary['missing_metrics'] > 0: | |
| print("\n" + "=" * 80) | |
| print("MISSING METRICS") | |
| print("=" * 80) | |
| for metric in summary['missing_metric_list']: | |
| print(f" - {metric}") | |
| # Save to CSV | |
| output_file = f"{test_ticker}_metrics.csv" | |
| metrics_df.to_csv(output_file, index=False) | |
| print(f"\n✓ Metrics saved to {output_file}") | |