Build campaign-analytics, financial-analyst, customer-success-manager, sales-engineer, and revenue-operations skills using the Claude Skills Factory workflow. Each skill includes SKILL.md, Python CLI tools, reference guides, and asset templates. All 16 Python scripts use standard library only with --format json/text support. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
433 lines
16 KiB
Python
433 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Financial Ratio Calculator
|
|
|
|
Calculates and interprets financial ratios across 5 categories:
|
|
profitability, liquidity, leverage, efficiency, and valuation.
|
|
|
|
Usage:
|
|
python ratio_calculator.py financial_data.json
|
|
python ratio_calculator.py financial_data.json --format json
|
|
python ratio_calculator.py financial_data.json --category profitability
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from typing import Any, Dict, List, Optional, Tuple
|
|
|
|
|
|
def safe_divide(numerator: float, denominator: float, default: float = 0.0) -> float:
|
|
"""Safely divide two numbers, returning default if denominator is zero."""
|
|
if denominator == 0 or denominator is None:
|
|
return default
|
|
return numerator / denominator
|
|
|
|
|
|
class FinancialRatioCalculator:
|
|
"""Calculate and interpret financial ratios from statement data."""
|
|
|
|
# Industry benchmark ranges: (low, typical, high)
|
|
BENCHMARKS: Dict[str, Tuple[float, float, float]] = {
|
|
"roe": (0.08, 0.15, 0.25),
|
|
"roa": (0.03, 0.06, 0.12),
|
|
"gross_margin": (0.25, 0.40, 0.60),
|
|
"operating_margin": (0.05, 0.15, 0.25),
|
|
"net_margin": (0.03, 0.10, 0.20),
|
|
"current_ratio": (1.0, 1.5, 3.0),
|
|
"quick_ratio": (0.8, 1.0, 2.0),
|
|
"cash_ratio": (0.2, 0.5, 1.0),
|
|
"debt_to_equity": (0.3, 0.8, 2.0),
|
|
"interest_coverage": (2.0, 5.0, 10.0),
|
|
"dscr": (1.0, 1.5, 2.5),
|
|
"asset_turnover": (0.5, 1.0, 2.0),
|
|
"inventory_turnover": (4.0, 8.0, 12.0),
|
|
"receivables_turnover": (6.0, 10.0, 15.0),
|
|
"dso": (30.0, 45.0, 60.0),
|
|
"pe_ratio": (10.0, 20.0, 35.0),
|
|
"pb_ratio": (1.0, 2.5, 5.0),
|
|
"ps_ratio": (1.0, 3.0, 8.0),
|
|
"ev_ebitda": (6.0, 12.0, 20.0),
|
|
"peg_ratio": (0.5, 1.0, 2.0),
|
|
}
|
|
|
|
def __init__(self, data: Dict[str, Any]) -> None:
|
|
"""Initialize with financial statement data."""
|
|
self.income = data.get("income_statement", {})
|
|
self.balance = data.get("balance_sheet", {})
|
|
self.cash_flow = data.get("cash_flow", {})
|
|
self.market = data.get("market_data", {})
|
|
self.results: Dict[str, Dict[str, Any]] = {}
|
|
|
|
def calculate_profitability(self) -> Dict[str, Any]:
|
|
"""Calculate profitability ratios."""
|
|
revenue = self.income.get("revenue", 0)
|
|
cogs = self.income.get("cost_of_goods_sold", 0)
|
|
operating_income = self.income.get("operating_income", 0)
|
|
net_income = self.income.get("net_income", 0)
|
|
total_equity = self.balance.get("total_equity", 0)
|
|
total_assets = self.balance.get("total_assets", 0)
|
|
|
|
gross_profit = revenue - cogs
|
|
|
|
ratios = {
|
|
"roe": {
|
|
"value": safe_divide(net_income, total_equity),
|
|
"formula": "Net Income / Total Equity",
|
|
"name": "Return on Equity",
|
|
},
|
|
"roa": {
|
|
"value": safe_divide(net_income, total_assets),
|
|
"formula": "Net Income / Total Assets",
|
|
"name": "Return on Assets",
|
|
},
|
|
"gross_margin": {
|
|
"value": safe_divide(gross_profit, revenue),
|
|
"formula": "(Revenue - COGS) / Revenue",
|
|
"name": "Gross Margin",
|
|
},
|
|
"operating_margin": {
|
|
"value": safe_divide(operating_income, revenue),
|
|
"formula": "Operating Income / Revenue",
|
|
"name": "Operating Margin",
|
|
},
|
|
"net_margin": {
|
|
"value": safe_divide(net_income, revenue),
|
|
"formula": "Net Income / Revenue",
|
|
"name": "Net Margin",
|
|
},
|
|
}
|
|
|
|
for key, ratio in ratios.items():
|
|
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
|
|
|
|
self.results["profitability"] = ratios
|
|
return ratios
|
|
|
|
def calculate_liquidity(self) -> Dict[str, Any]:
|
|
"""Calculate liquidity ratios."""
|
|
current_assets = self.balance.get("current_assets", 0)
|
|
current_liabilities = self.balance.get("current_liabilities", 0)
|
|
inventory = self.balance.get("inventory", 0)
|
|
cash = self.balance.get("cash_and_equivalents", 0)
|
|
|
|
ratios = {
|
|
"current_ratio": {
|
|
"value": safe_divide(current_assets, current_liabilities),
|
|
"formula": "Current Assets / Current Liabilities",
|
|
"name": "Current Ratio",
|
|
},
|
|
"quick_ratio": {
|
|
"value": safe_divide(
|
|
current_assets - inventory, current_liabilities
|
|
),
|
|
"formula": "(Current Assets - Inventory) / Current Liabilities",
|
|
"name": "Quick Ratio",
|
|
},
|
|
"cash_ratio": {
|
|
"value": safe_divide(cash, current_liabilities),
|
|
"formula": "Cash & Equivalents / Current Liabilities",
|
|
"name": "Cash Ratio",
|
|
},
|
|
}
|
|
|
|
for key, ratio in ratios.items():
|
|
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
|
|
|
|
self.results["liquidity"] = ratios
|
|
return ratios
|
|
|
|
def calculate_leverage(self) -> Dict[str, Any]:
|
|
"""Calculate leverage ratios."""
|
|
total_debt = self.balance.get("total_debt", 0)
|
|
total_equity = self.balance.get("total_equity", 0)
|
|
operating_income = self.income.get("operating_income", 0)
|
|
interest_expense = self.income.get("interest_expense", 0)
|
|
operating_cash_flow = self.cash_flow.get("operating_cash_flow", 0)
|
|
total_debt_service = self.cash_flow.get(
|
|
"total_debt_service", interest_expense
|
|
)
|
|
|
|
ratios = {
|
|
"debt_to_equity": {
|
|
"value": safe_divide(total_debt, total_equity),
|
|
"formula": "Total Debt / Total Equity",
|
|
"name": "Debt-to-Equity Ratio",
|
|
},
|
|
"interest_coverage": {
|
|
"value": safe_divide(operating_income, interest_expense),
|
|
"formula": "Operating Income / Interest Expense",
|
|
"name": "Interest Coverage Ratio",
|
|
},
|
|
"dscr": {
|
|
"value": safe_divide(operating_cash_flow, total_debt_service),
|
|
"formula": "Operating Cash Flow / Total Debt Service",
|
|
"name": "Debt Service Coverage Ratio",
|
|
},
|
|
}
|
|
|
|
for key, ratio in ratios.items():
|
|
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
|
|
|
|
self.results["leverage"] = ratios
|
|
return ratios
|
|
|
|
def calculate_efficiency(self) -> Dict[str, Any]:
|
|
"""Calculate efficiency ratios."""
|
|
revenue = self.income.get("revenue", 0)
|
|
cogs = self.income.get("cost_of_goods_sold", 0)
|
|
total_assets = self.balance.get("total_assets", 0)
|
|
inventory = self.balance.get("inventory", 0)
|
|
accounts_receivable = self.balance.get("accounts_receivable", 0)
|
|
|
|
receivables_turnover_val = safe_divide(revenue, accounts_receivable)
|
|
|
|
ratios = {
|
|
"asset_turnover": {
|
|
"value": safe_divide(revenue, total_assets),
|
|
"formula": "Revenue / Total Assets",
|
|
"name": "Asset Turnover",
|
|
},
|
|
"inventory_turnover": {
|
|
"value": safe_divide(cogs, inventory),
|
|
"formula": "COGS / Inventory",
|
|
"name": "Inventory Turnover",
|
|
},
|
|
"receivables_turnover": {
|
|
"value": receivables_turnover_val,
|
|
"formula": "Revenue / Accounts Receivable",
|
|
"name": "Receivables Turnover",
|
|
},
|
|
"dso": {
|
|
"value": safe_divide(365, receivables_turnover_val)
|
|
if receivables_turnover_val > 0
|
|
else 0.0,
|
|
"formula": "365 / Receivables Turnover",
|
|
"name": "Days Sales Outstanding",
|
|
},
|
|
}
|
|
|
|
for key, ratio in ratios.items():
|
|
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
|
|
|
|
self.results["efficiency"] = ratios
|
|
return ratios
|
|
|
|
def calculate_valuation(self) -> Dict[str, Any]:
|
|
"""Calculate valuation ratios (requires market data)."""
|
|
market_cap = self.market.get("market_cap", 0)
|
|
share_price = self.market.get("share_price", 0)
|
|
shares_outstanding = self.market.get("shares_outstanding", 0)
|
|
earnings_growth_rate = self.market.get("earnings_growth_rate", 0)
|
|
|
|
net_income = self.income.get("net_income", 0)
|
|
revenue = self.income.get("revenue", 0)
|
|
total_equity = self.balance.get("total_equity", 0)
|
|
total_debt = self.balance.get("total_debt", 0)
|
|
cash = self.balance.get("cash_and_equivalents", 0)
|
|
ebitda = self.income.get("ebitda", 0)
|
|
|
|
if market_cap == 0 and share_price > 0 and shares_outstanding > 0:
|
|
market_cap = share_price * shares_outstanding
|
|
|
|
eps = safe_divide(net_income, shares_outstanding)
|
|
book_value_per_share = safe_divide(total_equity, shares_outstanding)
|
|
enterprise_value = market_cap + total_debt - cash
|
|
pe = safe_divide(share_price, eps)
|
|
|
|
ratios = {
|
|
"pe_ratio": {
|
|
"value": pe,
|
|
"formula": "Share Price / Earnings Per Share",
|
|
"name": "Price-to-Earnings Ratio",
|
|
},
|
|
"pb_ratio": {
|
|
"value": safe_divide(share_price, book_value_per_share),
|
|
"formula": "Share Price / Book Value Per Share",
|
|
"name": "Price-to-Book Ratio",
|
|
},
|
|
"ps_ratio": {
|
|
"value": safe_divide(
|
|
market_cap, revenue
|
|
),
|
|
"formula": "Market Cap / Revenue",
|
|
"name": "Price-to-Sales Ratio",
|
|
},
|
|
"ev_ebitda": {
|
|
"value": safe_divide(enterprise_value, ebitda),
|
|
"formula": "Enterprise Value / EBITDA",
|
|
"name": "EV/EBITDA",
|
|
},
|
|
"peg_ratio": {
|
|
"value": safe_divide(pe, earnings_growth_rate * 100)
|
|
if earnings_growth_rate > 0
|
|
else 0.0,
|
|
"formula": "P/E Ratio / Earnings Growth Rate (%)",
|
|
"name": "PEG Ratio",
|
|
},
|
|
}
|
|
|
|
for key, ratio in ratios.items():
|
|
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
|
|
|
|
self.results["valuation"] = ratios
|
|
return ratios
|
|
|
|
def calculate_all(self) -> Dict[str, Dict[str, Any]]:
|
|
"""Calculate all ratio categories."""
|
|
self.calculate_profitability()
|
|
self.calculate_liquidity()
|
|
self.calculate_leverage()
|
|
self.calculate_efficiency()
|
|
self.calculate_valuation()
|
|
return self.results
|
|
|
|
def interpret_ratio(self, ratio_key: str, value: float) -> str:
|
|
"""Interpret a ratio value against benchmarks."""
|
|
if value == 0.0:
|
|
return "Insufficient data to calculate"
|
|
|
|
benchmarks = self.BENCHMARKS.get(ratio_key)
|
|
if not benchmarks:
|
|
return "No benchmark available"
|
|
|
|
low, typical, high = benchmarks
|
|
|
|
# DSO is inverse - lower is better
|
|
if ratio_key == "dso":
|
|
if value <= low:
|
|
return "Excellent - collections well above average"
|
|
elif value <= typical:
|
|
return "Good - collections within normal range"
|
|
elif value <= high:
|
|
return "Acceptable - monitor collection trends"
|
|
else:
|
|
return "Concern - collections significantly slower than peers"
|
|
|
|
# Debt-to-equity - lower generally better (but context matters)
|
|
if ratio_key == "debt_to_equity":
|
|
if value <= low:
|
|
return "Conservative leverage - strong equity position"
|
|
elif value <= typical:
|
|
return "Moderate leverage - well balanced"
|
|
elif value <= high:
|
|
return "Elevated leverage - monitor debt levels"
|
|
else:
|
|
return "High leverage - potential financial risk"
|
|
|
|
# Standard interpretation (higher is better for most ratios)
|
|
if value < low:
|
|
return "Below average - needs improvement"
|
|
elif value <= typical:
|
|
return "Acceptable - within normal range"
|
|
elif value <= high:
|
|
return "Good - above average performance"
|
|
else:
|
|
return "Excellent - significantly above peers"
|
|
|
|
@staticmethod
|
|
def format_ratio(value: float, is_percentage: bool = False) -> str:
|
|
"""Format a ratio value for display."""
|
|
if is_percentage:
|
|
return f"{value * 100:.1f}%"
|
|
return f"{value:.2f}"
|
|
|
|
def format_text(self, category: Optional[str] = None) -> str:
|
|
"""Format results as human-readable text."""
|
|
lines: List[str] = []
|
|
lines.append("=" * 70)
|
|
lines.append("FINANCIAL RATIO ANALYSIS")
|
|
lines.append("=" * 70)
|
|
|
|
categories = (
|
|
{category: self.results[category]}
|
|
if category and category in self.results
|
|
else self.results
|
|
)
|
|
|
|
percentage_ratios = {
|
|
"roe", "roa", "gross_margin", "operating_margin", "net_margin"
|
|
}
|
|
|
|
for cat_name, ratios in categories.items():
|
|
lines.append(f"\n--- {cat_name.upper()} ---")
|
|
for key, ratio in ratios.items():
|
|
is_pct = key in percentage_ratios
|
|
formatted = self.format_ratio(ratio["value"], is_pct)
|
|
lines.append(f" {ratio['name']}: {formatted}")
|
|
lines.append(f" Formula: {ratio['formula']}")
|
|
lines.append(f" Assessment: {ratio['interpretation']}")
|
|
|
|
lines.append("\n" + "=" * 70)
|
|
return "\n".join(lines)
|
|
|
|
def to_json(self, category: Optional[str] = None) -> Dict[str, Any]:
|
|
"""Return results as JSON-serializable dict."""
|
|
if category and category in self.results:
|
|
return {"category": category, "ratios": self.results[category]}
|
|
return {"categories": self.results}
|
|
|
|
|
|
def main() -> None:
|
|
"""Main entry point."""
|
|
parser = argparse.ArgumentParser(
|
|
description="Calculate and interpret financial ratios"
|
|
)
|
|
parser.add_argument(
|
|
"input_file",
|
|
help="Path to JSON file with financial statement data",
|
|
)
|
|
parser.add_argument(
|
|
"--format",
|
|
choices=["text", "json"],
|
|
default="text",
|
|
help="Output format (default: text)",
|
|
)
|
|
parser.add_argument(
|
|
"--category",
|
|
choices=[
|
|
"profitability",
|
|
"liquidity",
|
|
"leverage",
|
|
"efficiency",
|
|
"valuation",
|
|
],
|
|
default=None,
|
|
help="Calculate only a specific ratio category",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
with open(args.input_file, "r") as f:
|
|
data = json.load(f)
|
|
except FileNotFoundError:
|
|
print(f"Error: File '{args.input_file}' not found.", file=sys.stderr)
|
|
sys.exit(1)
|
|
except json.JSONDecodeError as e:
|
|
print(f"Error: Invalid JSON in '{args.input_file}': {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
calculator = FinancialRatioCalculator(data)
|
|
|
|
if args.category:
|
|
method_map = {
|
|
"profitability": calculator.calculate_profitability,
|
|
"liquidity": calculator.calculate_liquidity,
|
|
"leverage": calculator.calculate_leverage,
|
|
"efficiency": calculator.calculate_efficiency,
|
|
"valuation": calculator.calculate_valuation,
|
|
}
|
|
method_map[args.category]()
|
|
else:
|
|
calculator.calculate_all()
|
|
|
|
if args.format == "json":
|
|
print(json.dumps(calculator.to_json(args.category), indent=2))
|
|
else:
|
|
print(calculator.format_text(args.category))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|