Financial Analyst
DCF valuation, budgeting, forecasting, and SaaS metrics analysis — financial modeling and analysis from a professional analyst perspective.
What this skill does
Transform raw financial data into actionable strategic plans using professional-grade valuation models and budget forecasts. Generate executive summaries, analyze performance variances, and build rolling forecasts to support key business decisions. Use this when assessing company health, preparing investor reports, or planning future financial scenarios.
name: “financial-analyst” description: Performs financial ratio analysis, DCF valuation, budget variance analysis, and rolling forecast construction for strategic decision-making. Use when analyzing financial statements, building valuation models, assessing budget variances, or constructing financial projections and forecasts. Also applicable when users mention financial modeling, cash flow analysis, company valuation, financial projections, or spreadsheet analysis.
Financial Analyst Skill
Overview
Production-ready financial analysis toolkit providing ratio analysis, DCF valuation, budget variance analysis, and rolling forecast construction. Designed for financial modeling, forecasting & budgeting, management reporting, business performance analysis, and investment analysis.
5-Phase Workflow
Phase 1: Scoping
- Define analysis objectives and stakeholder requirements
- Identify data sources and time periods
- Establish materiality thresholds and accuracy targets
- Select appropriate analytical frameworks
Phase 2: Data Analysis & Modeling
- Collect and validate financial data (income statement, balance sheet, cash flow)
- Validate input data completeness before running ratio calculations (check for missing fields, nulls, or implausible values)
- Calculate financial ratios across 5 categories (profitability, liquidity, leverage, efficiency, valuation)
- Build DCF models with WACC and terminal value calculations; cross-check DCF outputs against sanity bounds (e.g., implied multiples vs. comparables)
- Construct budget variance analyses with favorable/unfavorable classification
- Develop driver-based forecasts with scenario modeling
Phase 3: Insight Generation
- Interpret ratio trends and benchmark against industry standards
- Identify material variances and root causes
- Assess valuation ranges through sensitivity analysis
- Evaluate forecast scenarios (base/bull/bear) for decision support
Phase 4: Reporting
- Generate executive summaries with key findings
- Produce detailed variance reports by department and category
- Deliver DCF valuation reports with sensitivity tables
- Present rolling forecasts with trend analysis
Phase 5: Follow-up
- Track forecast accuracy (target: +/-5% revenue, +/-3% expenses)
- Monitor report delivery timeliness (target: 100% on time)
- Update models with actuals as they become available
- Refine assumptions based on variance analysis
Tools
1. Ratio Calculator (scripts/ratio_calculator.py)
Calculate and interpret financial ratios from financial statement data.
Ratio Categories:
- Profitability: ROE, ROA, Gross Margin, Operating Margin, Net Margin
- Liquidity: Current Ratio, Quick Ratio, Cash Ratio
- Leverage: Debt-to-Equity, Interest Coverage, DSCR
- Efficiency: Asset Turnover, Inventory Turnover, Receivables Turnover, DSO
- Valuation: P/E, P/B, P/S, EV/EBITDA, PEG Ratio
python scripts/ratio_calculator.py sample_financial_data.json
python scripts/ratio_calculator.py sample_financial_data.json --format json
python scripts/ratio_calculator.py sample_financial_data.json --category profitability
2. DCF Valuation (scripts/dcf_valuation.py)
Discounted Cash Flow enterprise and equity valuation with sensitivity analysis.
Features:
- WACC calculation via CAPM
- Revenue and free cash flow projections (5-year default)
- Terminal value via perpetuity growth and exit multiple methods
- Enterprise value and equity value derivation
- Two-way sensitivity analysis (discount rate vs growth rate)
python scripts/dcf_valuation.py valuation_data.json
python scripts/dcf_valuation.py valuation_data.json --format json
python scripts/dcf_valuation.py valuation_data.json --projection-years 7
3. Budget Variance Analyzer (scripts/budget_variance_analyzer.py)
Analyze actual vs budget vs prior year performance with materiality filtering.
Features:
- Dollar and percentage variance calculation
- Materiality threshold filtering (default: 10% or $50K)
- Favorable/unfavorable classification with revenue/expense logic
- Department and category breakdown
- Executive summary generation
python scripts/budget_variance_analyzer.py budget_data.json
python scripts/budget_variance_analyzer.py budget_data.json --format json
python scripts/budget_variance_analyzer.py budget_data.json --threshold-pct 5 --threshold-amt 25000
4. Forecast Builder (scripts/forecast_builder.py)
Driver-based revenue forecasting with rolling cash flow projection and scenario modeling.
Features:
- Driver-based revenue forecast model
- 13-week rolling cash flow projection
- Scenario modeling (base/bull/bear cases)
- Trend analysis using simple linear regression (standard library)
python scripts/forecast_builder.py forecast_data.json
python scripts/forecast_builder.py forecast_data.json --format json
python scripts/forecast_builder.py forecast_data.json --scenarios base,bull,bear
Knowledge Bases
| Reference | Purpose |
|---|---|
references/financial-ratios-guide.md | Ratio formulas, interpretation, industry benchmarks |
references/valuation-methodology.md | DCF methodology, WACC, terminal value, comps |
references/forecasting-best-practices.md | Driver-based forecasting, rolling forecasts, accuracy |
references/industry-adaptations.md | Sector-specific metrics and considerations (SaaS, Retail, Manufacturing, Financial Services, Healthcare) |
Templates
| Template | Purpose |
|---|---|
assets/variance_report_template.md | Budget variance report template |
assets/dcf_analysis_template.md | DCF valuation analysis template |
assets/forecast_report_template.md | Revenue forecast report template |
Key Metrics & Targets
| Metric | Target |
|---|---|
| Forecast accuracy (revenue) | +/-5% |
| Forecast accuracy (expenses) | +/-3% |
| Report delivery | 100% on time |
| Model documentation | Complete for all assumptions |
| Variance explanation | 100% of material variances |
Input Data Format
All scripts accept JSON input files. See assets/sample_financial_data.json for the complete input schema covering all four tools.
Dependencies
None - All scripts use Python standard library only (math, statistics, json, argparse, datetime). No numpy, pandas, or scipy required.
DCF Valuation Analysis
Report Header
| Field | Value |
|---|---|
| Company | [Company Name] |
| Ticker | [Ticker Symbol] |
| Analysis Date | [Date] |
| Prepared By | [Analyst Name] |
| Current Share Price | $[X] |
| Shares Outstanding | [X]M |
Executive Summary
[2-3 sentence overview of the valuation conclusion, including the implied value range per share compared to the current market price, and whether the stock appears undervalued, fairly valued, or overvalued.]
Valuation Summary
| Method | Enterprise Value | Equity Value | Value Per Share | vs Current Price |
|---|---|---|---|---|
| DCF (Perpetuity Growth) | $[X]M | $[X]M | $[X] | [X]% |
| DCF (Exit Multiple) | $[X]M | $[X]M | $[X] | [X]% |
| Comparable Companies | $[X]M | $[X]M | $[X] | [X]% |
| Blended Estimate | $[X]M | $[X]M | $[X] | [X]% |
Investment Thesis
[Summary of the investment case, including key strengths, risks, and catalysts.]
Historical Financial Summary
| ($M) | FY-4 | FY-3 | FY-2 | FY-1 | LTM |
|---|---|---|---|---|---|
| Revenue | [X] | [X] | [X] | [X] | [X] |
| Revenue Growth | [X]% | [X]% | [X]% | [X]% | [X]% |
| Gross Profit | [X] | [X] | [X] | [X] | [X] |
| Gross Margin | [X]% | [X]% | [X]% | [X]% | [X]% |
| EBITDA | [X] | [X] | [X] | [X] | [X] |
| EBITDA Margin | [X]% | [X]% | [X]% | [X]% | [X]% |
| Net Income | [X] | [X] | [X] | [X] | [X] |
| Free Cash Flow | [X] | [X] | [X] | [X] | [X] |
WACC Calculation
Cost of Equity (CAPM)
| Component | Value | Source |
|---|---|---|
| Risk-Free Rate | [X]% | [10-Year Treasury] |
| Equity Risk Premium | [X]% | [Damodaran / internal] |
| Beta (Levered) | [X] | [Bloomberg / regression] |
| Size Premium | [X]% | [Duff & Phelps] |
| Company-Specific Risk | [X]% | [Analyst judgment] |
| Cost of Equity | [X]% |
Cost of Debt
| Component | Value |
|---|---|
| Pre-Tax Cost of Debt | [X]% |
| Tax Rate | [X]% |
| After-Tax Cost of Debt | [X]% |
Capital Structure
| Component | Market Value ($M) | Weight |
|---|---|---|
| Equity | [X] | [X]% |
| Debt | [X] | [X]% |
| Total Capital | [X] | 100% |
WACC Result: [X]%
Revenue Projections
| ($M) | Year 1 | Year 2 | Year 3 | Year 4 | Year 5 |
|---|---|---|---|---|---|
| Revenue | [X] | [X] | [X] | [X] | [X] |
| Growth Rate | [X]% | [X]% | [X]% | [X]% | [X]% |
Key Revenue Assumptions:
- [Assumption 1 with supporting rationale]
- [Assumption 2 with supporting rationale]
- [Assumption 3 with supporting rationale]
Free Cash Flow Projections
| ($M) | Year 1 | Year 2 | Year 3 | Year 4 | Year 5 |
|---|---|---|---|---|---|
| Revenue | [X] | [X] | [X] | [X] | [X] |
| EBIT | [X] | [X] | [X] | [X] | [X] |
| Taxes on EBIT | ([X]) | ([X]) | ([X]) | ([X]) | ([X]) |
| NOPAT | [X] | [X] | [X] | [X] | [X] |
| D&A | [X] | [X] | [X] | [X] | [X] |
| CapEx | ([X]) | ([X]) | ([X]) | ([X]) | ([X]) |
| Change in NWC | ([X]) | ([X]) | ([X]) | ([X]) | ([X]) |
| Unlevered FCF | [X] | [X] | [X] | [X] | [X] |
| FCF Margin | [X]% | [X]% | [X]% | [X]% | [X]% |
Terminal Value
Perpetuity Growth Method
| Component | Value |
|---|---|
| Terminal FCF | $[X]M |
| Terminal Growth Rate | [X]% |
| WACC | [X]% |
| Terminal Value | $[X]M |
| TV as % of EV | [X]% |
Exit Multiple Method
| Component | Value |
|---|---|
| Terminal EBITDA | $[X]M |
| Exit EV/EBITDA Multiple | [X]x |
| Terminal Value | $[X]M |
| TV as % of EV | [X]% |
Enterprise Value Bridge
| Component | Perpetuity Growth | Exit Multiple |
|---|---|---|
| PV of Projected FCFs | $[X]M | $[X]M |
| PV of Terminal Value | $[X]M | $[X]M |
| Enterprise Value | $[X]M | $[X]M |
| Less: Net Debt | ($[X]M) | ($[X]M) |
| Less: Minority Interest | ($[X]M) | ($[X]M) |
| Equity Value | $[X]M | $[X]M |
| Diluted Shares (M) | [X] | [X] |
| Value Per Share | $[X] | $[X] |
Sensitivity Analysis
WACC vs Terminal Growth Rate (Enterprise Value, $M)
| WACC \ Growth | [g-2]% | [g-1]% | [g]% | [g+1]% | [g+2]% |
|---|---|---|---|---|---|
| [WACC-2]% | [X] | [X] | [X] | [X] | [X] |
| [WACC-1]% | [X] | [X] | [X] | [X] | [X] |
| [WACC]% | [X] | [X] | [X] | [X] | [X] |
| [WACC+1]% | [X] | [X] | [X] | [X] | [X] |
| [WACC+2]% | [X] | [X] | [X] | [X] | [X] |
Implied Share Price Range
| Scenario | Share Price | vs Current | Upside/Downside |
|---|---|---|---|
| Bear Case (WACC+2%, g-2%) | $[X] | [X]% | [X]% |
| Base Case | $[X] | [X]% | [X]% |
| Bull Case (WACC-2%, g+2%) | $[X] | [X]% | [X]% |
Key Risks to Valuation
- [Risk 1] - [Description and potential impact on value]
- [Risk 2] - [Description and potential impact on value]
- [Risk 3] - [Description and potential impact on value]
Comparable Company Analysis
| Company | EV/Revenue | EV/EBITDA | P/E | Growth | Margin |
|---|---|---|---|---|---|
| [Comp 1] | [X]x | [X]x | [X]x | [X]% | [X]% |
| [Comp 2] | [X]x | [X]x | [X]x | [X]% | [X]% |
| [Comp 3] | [X]x | [X]x | [X]x | [X]% | [X]% |
| [Comp 4] | [X]x | [X]x | [X]x | [X]% | [X]% |
| Median | [X]x | [X]x | [X]x | [X]% | [X]% |
| [Target] | [X]x | [X]x | [X]x | [X]% | [X]% |
Conclusion and Recommendation
Valuation Range: $[Low] - $[High] per share
Current Price: $[X]
Recommendation: [Buy / Hold / Sell]
[Final paragraph with investment recommendation rationale, key upside catalysts, and primary risks to monitor.]
Analysis generated using Financial Analyst Skill - DCF Valuation Model
{
"_description": "Expected output structure for all 4 scripts. Values are illustrative to show data format.",
"ratio_calculator_output": {
"categories": {
"profitability": {
"roe": {
"value": 0.25,
"formula": "Net Income / Total Equity",
"name": "Return on Equity",
"interpretation": "Good - above average performance"
},
"roa": {
"value": 0.1375,
"formula": "Net Income / Total Assets",
"name": "Return on Assets",
"interpretation": "Excellent - significantly above peers"
},
"gross_margin": {
"value": 0.40,
"formula": "(Revenue - COGS) / Revenue",
"name": "Gross Margin",
"interpretation": "Acceptable - within normal range"
},
"operating_margin": {
"value": 0.16,
"formula": "Operating Income / Revenue",
"name": "Operating Margin",
"interpretation": "Good - above average performance"
},
"net_margin": {
"value": 0.11,
"formula": "Net Income / Revenue",
"name": "Net Margin",
"interpretation": "Good - above average performance"
}
},
"liquidity": {
"current_ratio": {"value": 1.875, "name": "Current Ratio"},
"quick_ratio": {"value": 1.4375, "name": "Quick Ratio"},
"cash_ratio": {"value": 0.625, "name": "Cash Ratio"}
},
"leverage": {
"debt_to_equity": {"value": 0.545, "name": "Debt-to-Equity Ratio"},
"interest_coverage": {"value": 6.67, "name": "Interest Coverage Ratio"},
"dscr": {"value": 2.50, "name": "Debt Service Coverage Ratio"}
},
"efficiency": {
"asset_turnover": {"value": 1.25, "name": "Asset Turnover"},
"inventory_turnover": {"value": 8.57, "name": "Inventory Turnover"},
"receivables_turnover": {"value": 8.33, "name": "Receivables Turnover"},
"dso": {"value": 43.8, "name": "Days Sales Outstanding"}
},
"valuation": {
"pe_ratio": {"value": 81.82, "name": "Price-to-Earnings Ratio"},
"pb_ratio": {"value": 20.45, "name": "Price-to-Book Ratio"},
"ps_ratio": {"value": 9.0, "name": "Price-to-Sales Ratio"},
"ev_ebitda": {"value": 45.7, "name": "EV/EBITDA"},
"peg_ratio": {"value": 6.82, "name": "PEG Ratio"}
}
}
},
"dcf_valuation_output": {
"wacc": 0.085,
"projected_revenue": [55000000, 59950000, 64746000, 69278220, 73434953],
"projected_fcf": [6600000, 7793500, 8416980, 9698951, 10280893],
"terminal_value": {
"perpetuity_growth": 175382225,
"exit_multiple": 176243484
},
"enterprise_value": {
"perpetuity_growth": 149500000,
"exit_multiple": 150100000
},
"equity_value": {
"perpetuity_growth": 142500000,
"exit_multiple": 143100000
},
"value_per_share": {
"perpetuity_growth": 14.25,
"exit_multiple": 14.31
},
"sensitivity_analysis": {
"wacc_values": [0.065, 0.075, 0.085, 0.095, 0.105],
"growth_values": [0.015, 0.020, 0.025, 0.030, 0.035],
"enterprise_value_table": "5x5 nested list of enterprise values",
"share_price_table": "5x5 nested list of share prices"
}
},
"budget_variance_output": {
"executive_summary": {
"period": "Q4 2025",
"company": "Acme Corp",
"total_line_items": 10,
"material_variances_count": 3,
"favorable_count": 4,
"unfavorable_count": 6,
"revenue": {
"actual": 15700000,
"budget": 15500000,
"variance_amount": 200000,
"variance_pct": 1.29
},
"expenses": {
"actual": 13255000,
"budget": 12520000,
"variance_amount": 735000,
"variance_pct": 5.87
},
"net_impact": -535000
},
"material_variances": [
{
"name": "Cost of Goods Sold",
"budget_variance_amount": 600000,
"budget_variance_pct": 8.33,
"favorability": "Unfavorable"
}
],
"department_summary": {
"Sales": {"total_variance": 0, "variance_pct": 0},
"Operations": {"total_variance": 0, "variance_pct": 0}
},
"category_summary": {
"Revenue": {"total_variance": 0, "variance_pct": 0},
"COGS": {"total_variance": 0, "variance_pct": 0}
}
},
"forecast_builder_output": {
"trend_analysis": {
"trend": {
"slope": 650000,
"intercept": 9500000,
"r_squared": 0.98,
"direction": "upward"
},
"average_growth_rate": 0.06,
"seasonality_index": [0.92, 0.97, 1.01, 1.10]
},
"scenario_comparison": {
"comparison": [
{"scenario": "base", "total_revenue": 185000000, "growth_rate": 0.08},
{"scenario": "bull", "total_revenue": 210000000, "growth_rate": 0.12},
{"scenario": "bear", "total_revenue": 165000000, "growth_rate": 0.05}
]
},
"rolling_cash_flow": {
"weeks": 13,
"opening_balance": 2500000,
"closing_balance": 2800000,
"total_inflows": 4200000,
"total_outflows": 3900000,
"minimum_balance": 2100000,
"minimum_balance_week": 4,
"cash_runway_weeks": 12
}
}
}
Revenue Forecast Report
Report Header
| Field | Value |
|---|---|
| Company | [Company Name] |
| Forecast Period | [Start] to [End] |
| Prepared By | [Analyst Name] |
| Date | [Report Date] |
| Forecast Type | [Driver-Based / Trend-Based / Blended] |
Executive Summary
[2-3 sentence overview of the revenue forecast, key assumptions, and confidence level. Highlight the base case total revenue, expected growth rate, and any significant departures from prior forecast or budget.]
Key Metrics at a Glance
| Metric | Value |
|---|---|
| Base Case Total Revenue | $[X]M |
| Expected Growth Rate | [X]% |
| Forecast Confidence | [High / Medium / Low] |
| Revenue Range (Bear to Bull) | $[X]M - $[X]M |
| Primary Revenue Driver | [Driver description] |
Historical Trend Analysis
Revenue Trend
| Period | Revenue | Growth Rate | Gross Margin |
|---|---|---|---|
| [Q/Year-4] | $[X]M | - | [X]% |
| [Q/Year-3] | $[X]M | [X]% | [X]% |
| [Q/Year-2] | $[X]M | [X]% | [X]% |
| [Q/Year-1] | $[X]M | [X]% | [X]% |
| [Current] | $[X]M | [X]% | [X]% |
Trend Statistics
| Metric | Value |
|---|---|
| Average Growth Rate | [X]% |
| Trend Direction | [Upward / Flat / Downward] |
| R-squared (fit quality) | [X] |
| Seasonality Detected | [Yes / No] |
Revenue Drivers
Primary Drivers
| Driver | Current Value | Projected Value | Growth |
|---|---|---|---|
| [Units / Customers / etc.] | [X] | [X] | [X]% |
| [Price / ARPU / etc.] | $[X] | $[X] | [X]% |
| [Conversion / Retention] | [X]% | [X]% | [X]pp |
Driver Assumptions
- [Driver 1]: [Assumption and rationale]
- [Driver 2]: [Assumption and rationale]
- [Driver 3]: [Assumption and rationale]
Scenario Comparison
Summary
| Scenario | Total Revenue | Growth Rate | Op. Income | Gross Margin | Probability |
|---|---|---|---|---|---|
| Bull | $[X]M | [X]% | $[X]M | [X]% | [X]% |
| Base | $[X]M | [X]% | $[X]M | [X]% | [X]% |
| Bear | $[X]M | [X]% | $[X]M | [X]% | [X]% |
Scenario Assumptions
Bull Case:
- [Key assumption 1]
- [Key assumption 2]
- [Trigger: what conditions would cause this scenario]
Base Case:
- [Key assumption 1]
- [Key assumption 2]
Bear Case:
- [Key assumption 1]
- [Key assumption 2]
- [Trigger: what conditions would cause this scenario]
Monthly/Quarterly Forecast Detail (Base Case)
| Period | Revenue | COGS | Gross Profit | OpEx | Op. Income |
|---|---|---|---|---|---|
| [Period 1] | $[X] | $[X] | $[X] | $[X] | $[X] |
| [Period 2] | $[X] | $[X] | $[X] | $[X] | $[X] |
| [Period 3] | $[X] | $[X] | $[X] | $[X] | $[X] |
| [Period 4] | $[X] | $[X] | $[X] | $[X] | $[X] |
| ... | ... | ... | ... | ... | ... |
| Total | $[X] | $[X] | $[X] | $[X] | $[X] |
13-Week Rolling Cash Flow
Summary
| Metric | Value |
|---|---|
| Opening Cash Balance | $[X] |
| Projected Closing Balance | $[X] |
| Net Cash Change | $[X] |
| Minimum Cash Balance | $[X] (Week [N]) |
| Cash Runway | [N] weeks |
Weekly Cash Flow Projection
| Week | Inflows | Outflows | Net Cash Flow | Closing Balance |
|---|---|---|---|---|
| 1 | $[X] | $[X] | $[X] | $[X] |
| 2 | $[X] | $[X] | $[X] | $[X] |
| 3 | $[X] | $[X] | $[X] | $[X] |
| ... | ... | ... | ... | ... |
| 13 | $[X] | $[X] | $[X] | $[X] |
Cash Flow Notes
- Week [N]: [Description of any significant one-time items]
- Week [N]: [Description of any significant one-time items]
Forecast Accuracy Tracking
vs Prior Forecast
| Metric | Prior Forecast | Current Forecast | Change |
|---|---|---|---|
| Revenue | $[X]M | $[X]M | [X]% |
| Growth Rate | [X]% | [X]% | [X]pp |
| Gross Margin | [X]% | [X]% | [X]pp |
Historical Forecast Accuracy (MAPE)
| Period | Forecast | Actual | Error | MAPE |
|---|---|---|---|---|
| [Period-3] | $[X] | $[X] | $[X] | [X]% |
| [Period-2] | $[X] | $[X] | $[X] | [X]% |
| [Period-1] | $[X] | $[X] | $[X] | [X]% |
| Average MAPE | [X]% |
Key Risks and Assumptions
Upside Risks
- [Risk/opportunity with quantified potential impact]
- [Risk/opportunity with quantified potential impact]
Downside Risks
- [Risk with quantified potential impact]
- [Risk with quantified potential impact]
Critical Assumptions
- [Assumption that if wrong would materially change the forecast]
- [Assumption that if wrong would materially change the forecast]
Recommendations
- [Recommendation 1]: [Specific action with expected impact]
- [Recommendation 2]: [Specific action with expected impact]
- [Recommendation 3]: [Specific action with expected impact]
Next Steps
| # | Action | Owner | Due Date |
|---|---|---|---|
| 1 | [Action item] | [Name] | [Date] |
| 2 | [Action item] | [Name] | [Date] |
| 3 | [Action item] | [Name] | [Date] |
Report generated using Financial Analyst Skill - Forecast Builder
{
"_description": "Sample financial data covering all 4 scripts: ratio_calculator, dcf_valuation, budget_variance_analyzer, and forecast_builder",
"ratio_analysis": {
"income_statement": {
"revenue": 50000000,
"cost_of_goods_sold": 30000000,
"operating_income": 8000000,
"ebitda": 10000000,
"net_income": 5500000,
"interest_expense": 1200000
},
"balance_sheet": {
"total_assets": 40000000,
"current_assets": 15000000,
"cash_and_equivalents": 5000000,
"accounts_receivable": 6000000,
"inventory": 3500000,
"total_equity": 22000000,
"total_debt": 12000000,
"current_liabilities": 8000000
},
"cash_flow": {
"operating_cash_flow": 7500000,
"total_debt_service": 3000000
},
"market_data": {
"share_price": 45.00,
"shares_outstanding": 10000000,
"market_cap": 450000000,
"earnings_growth_rate": 0.12
}
},
"dcf_valuation": {
"historical": {
"revenue": [38000000, 42000000, 45000000, 48000000, 50000000],
"net_income": [3800000, 4200000, 4500000, 5000000, 5500000],
"net_debt": 7000000,
"shares_outstanding": 10000000
},
"assumptions": {
"projection_years": 5,
"revenue_growth_rates": [0.10, 0.09, 0.08, 0.07, 0.06],
"fcf_margins": [0.12, 0.13, 0.13, 0.14, 0.14],
"default_revenue_growth": 0.05,
"default_fcf_margin": 0.10,
"terminal_growth_rate": 0.025,
"terminal_ebitda_margin": 0.20,
"exit_ev_ebitda_multiple": 12.0,
"wacc_inputs": {
"risk_free_rate": 0.04,
"equity_risk_premium": 0.06,
"beta": 1.1,
"cost_of_debt": 0.055,
"tax_rate": 0.25,
"debt_weight": 0.30,
"equity_weight": 0.70
}
}
},
"budget_variance": {
"company": "Acme Corp",
"period": "Q4 2025",
"line_items": [
{
"name": "Product Revenue",
"type": "revenue",
"department": "Sales",
"category": "Revenue",
"actual": 12500000,
"budget": 12000000,
"prior_year": 10800000
},
{
"name": "Service Revenue",
"type": "revenue",
"department": "Sales",
"category": "Revenue",
"actual": 3200000,
"budget": 3500000,
"prior_year": 2900000
},
{
"name": "Cost of Goods Sold",
"type": "expense",
"department": "Operations",
"category": "COGS",
"actual": 7800000,
"budget": 7200000,
"prior_year": 6700000
},
{
"name": "Salaries & Wages",
"type": "expense",
"department": "Human Resources",
"category": "Personnel",
"actual": 2100000,
"budget": 2200000,
"prior_year": 1950000
},
{
"name": "Marketing & Advertising",
"type": "expense",
"department": "Marketing",
"category": "Sales & Marketing",
"actual": 850000,
"budget": 750000,
"prior_year": 680000
},
{
"name": "Software & Technology",
"type": "expense",
"department": "Engineering",
"category": "Technology",
"actual": 420000,
"budget": 400000,
"prior_year": 350000
},
{
"name": "Office & Facilities",
"type": "expense",
"department": "Operations",
"category": "G&A",
"actual": 180000,
"budget": 200000,
"prior_year": 175000
},
{
"name": "Travel & Entertainment",
"type": "expense",
"department": "Sales",
"category": "Sales & Marketing",
"actual": 95000,
"budget": 120000,
"prior_year": 88000
},
{
"name": "Professional Services",
"type": "expense",
"department": "Finance",
"category": "G&A",
"actual": 310000,
"budget": 250000,
"prior_year": 220000
},
{
"name": "R&D Expenses",
"type": "expense",
"department": "Engineering",
"category": "R&D",
"actual": 1500000,
"budget": 1400000,
"prior_year": 1200000
}
]
},
"forecast": {
"historical_periods": [
{"period": "Q1 2024", "revenue": 10500000, "gross_profit": 4200000, "operating_income": 1575000},
{"period": "Q2 2024", "revenue": 11200000, "gross_profit": 4480000, "operating_income": 1680000},
{"period": "Q3 2024", "revenue": 11800000, "gross_profit": 4720000, "operating_income": 1770000},
{"period": "Q4 2024", "revenue": 12500000, "gross_profit": 5000000, "operating_income": 1875000},
{"period": "Q1 2025", "revenue": 12800000, "gross_profit": 5120000, "operating_income": 1920000},
{"period": "Q2 2025", "revenue": 13500000, "gross_profit": 5400000, "operating_income": 2025000},
{"period": "Q3 2025", "revenue": 14100000, "gross_profit": 5640000, "operating_income": 2115000},
{"period": "Q4 2025", "revenue": 15700000, "gross_profit": 6280000, "operating_income": 2355000}
],
"drivers": {
"units": {
"base_units": 5000,
"growth_rate": 0.04
},
"pricing": {
"base_price": 2800,
"annual_increase": 0.03
}
},
"assumptions": {
"revenue_growth_rate": 0.08,
"gross_margin": 0.40,
"opex_pct_revenue": 0.25,
"forecast_periods": 12
},
"scenarios": {
"base": {
"growth_adjustment": 0.0,
"margin_adjustment": 0.0
},
"bull": {
"growth_adjustment": 0.04,
"margin_adjustment": 0.03
},
"bear": {
"growth_adjustment": -0.03,
"margin_adjustment": -0.02
}
},
"cash_flow_inputs": {
"opening_cash_balance": 2500000,
"weekly_revenue": 350000,
"collection_rate": 0.85,
"collection_lag_weeks": 2,
"weekly_payroll": 160000,
"weekly_rent": 15000,
"weekly_operating": 45000,
"weekly_other": 20000,
"one_time_items": [
{"week": 3, "amount": -250000, "description": "Annual insurance premium"},
{"week": 6, "amount": 500000, "description": "Customer prepayment"},
{"week": 9, "amount": -180000, "description": "Equipment purchase"},
{"week": 13, "amount": -75000, "description": "Quarterly tax payment"}
]
},
"forecast_periods": 12
}
}
Budget Variance Report
Report Header
| Field | Value |
|---|---|
| Company | [Company Name] |
| Period | [Reporting Period] |
| Prepared By | [Analyst Name] |
| Date | [Report Date] |
| Materiality Threshold | [X]% or $[Y]K |
Executive Summary
[2-3 sentence overview of overall performance vs budget, highlighting whether the company is tracking ahead or behind plan and the primary drivers of variance.]
Key Metrics
| Metric | Actual | Budget | Variance ($) | Variance (%) | Status |
|---|---|---|---|---|---|
| Total Revenue | $[X] | $[X] | $[X] | [X]% | [Fav/Unfav] |
| Total Expenses | $[X] | $[X] | $[X] | [X]% | [Fav/Unfav] |
| Net Income | $[X] | $[X] | $[X] | [X]% | [Fav/Unfav] |
| Operating Margin | [X]% | [X]% | [X]pp | - | [Fav/Unfav] |
Material Variances
[Variance Item 1 - e.g., Product Revenue]
| Actual | Budget | Variance | ||
|---|---|---|---|---|
| Amount | $[X] | $[X] | $[X] | [X]% |
Root Cause: [Detailed explanation of why this variance occurred]
Impact: [Quantified impact on profitability and cash flow]
Corrective Action: [Specific steps being taken to address the variance]
Responsible: [Owner] | Target Date: [Date]
[Variance Item 2]
| Actual | Budget | Variance | ||
|---|---|---|---|---|
| Amount | $[X] | $[X] | $[X] | [X]% |
Root Cause: [Explanation]
Impact: [Impact]
Corrective Action: [Action items]
Responsible: [Owner] | Target Date: [Date]
Department Performance
| Department | Actual | Budget | Variance ($) | Variance (%) | Favorable | Unfavorable |
|---|---|---|---|---|---|---|
| Sales | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
| Operations | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
| Marketing | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
| Engineering | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
| Finance | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
| HR | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
Category Breakdown
| Category | Actual | Budget | Variance ($) | Variance (%) |
|---|---|---|---|---|
| Revenue | $[X] | $[X] | $[X] | [X]% |
| COGS | $[X] | $[X] | $[X] | [X]% |
| Personnel | $[X] | $[X] | $[X] | [X]% |
| Sales & Marketing | $[X] | $[X] | $[X] | [X]% |
| Technology | $[X] | $[X] | $[X] | [X]% |
| G&A | $[X] | $[X] | $[X] | [X]% |
| R&D | $[X] | $[X] | $[X] | [X]% |
Prior Year Comparison
| Metric | Current Actual | Prior Year | YoY Change ($) | YoY Change (%) |
|---|---|---|---|---|
| Revenue | $[X] | $[X] | $[X] | [X]% |
| Gross Profit | $[X] | $[X] | $[X] | [X]% |
| Operating Income | $[X] | $[X] | $[X] | [X]% |
| Net Income | $[X] | $[X] | $[X] | [X]% |
Risks and Opportunities
Risks
- [Risk description with quantified impact]
- [Risk description with quantified impact]
Opportunities
- [Opportunity description with quantified upside]
- [Opportunity description with quantified upside]
Forecast Impact
Based on current variances, the full-year forecast is adjusted as follows:
| Metric | Original FY Forecast | Revised FY Forecast | Change |
|---|---|---|---|
| Revenue | $[X] | $[X] | $[X] |
| EBITDA | $[X] | $[X] | $[X] |
| Net Income | $[X] | $[X] | $[X] |
Action Items
| # | Action | Owner | Due Date | Status |
|---|---|---|---|---|
| 1 | [Action description] | [Name] | [Date] | [Open/In Progress/Complete] |
| 2 | [Action description] | [Name] | [Date] | [Open/In Progress/Complete] |
| 3 | [Action description] | [Name] | [Date] | [Open/In Progress/Complete] |
Report generated using Financial Analyst Skill - Budget Variance Analyzer
Financial Ratios Guide
Comprehensive reference for financial ratio analysis covering formulas, interpretation, and industry benchmarks across five categories.
1. Profitability Ratios
Measure a company's ability to generate earnings relative to revenue, assets, or equity.
Return on Equity (ROE)
Formula: Net Income / Total Shareholders' Equity
Interpretation:
- Measures how effectively management uses equity to generate profits
- Higher ROE indicates more efficient use of equity capital
- Compare against cost of equity - ROE should exceed it
Benchmarks:
| Rating | Range |
|---|---|
| Below Average | < 8% |
| Acceptable | 8% - 15% |
| Good | 15% - 25% |
| Excellent | > 25% |
Caveats: High leverage can inflate ROE. Use DuPont decomposition (ROE = Margin x Turnover x Leverage) for deeper analysis.
Return on Assets (ROA)
Formula: Net Income / Total Assets
Interpretation:
- Measures how efficiently assets generate profit
- Asset-light businesses naturally have higher ROA
- Compare within industry only
Benchmarks:
| Rating | Range |
|---|---|
| Below Average | < 3% |
| Acceptable | 3% - 6% |
| Good | 6% - 12% |
| Excellent | > 12% |
Gross Margin
Formula: (Revenue - COGS) / Revenue
Interpretation:
- Measures production efficiency and pricing power
- Declining gross margin may signal competitive pressure or cost inflation
- Critical for evaluating business model sustainability
Benchmarks by Industry:
| Industry | Typical Range |
|---|---|
| Software/SaaS | 70% - 85% |
| Financial Services | 50% - 70% |
| Retail | 25% - 45% |
| Manufacturing | 20% - 40% |
| Grocery | 25% - 30% |
Operating Margin
Formula: Operating Income / Revenue
Interpretation:
- Measures operational efficiency after all operating expenses
- Excludes interest and taxes for better operational comparison
- Indicates management effectiveness in controlling costs
Benchmarks:
| Rating | Range |
|---|---|
| Below Average | < 5% |
| Acceptable | 5% - 15% |
| Good | 15% - 25% |
| Excellent | > 25% |
Net Margin
Formula: Net Income / Revenue
Interpretation:
- Bottom-line profitability after all expenses
- Affected by tax strategy, capital structure, and one-time items
- Most comprehensive profitability measure
Benchmarks:
| Rating | Range |
|---|---|
| Below Average | < 3% |
| Acceptable | 3% - 10% |
| Good | 10% - 20% |
| Excellent | > 20% |
2. Liquidity Ratios
Measure a company's ability to meet short-term obligations.
Current Ratio
Formula: Current Assets / Current Liabilities
Interpretation:
- Measures short-term solvency
- Too high may indicate inefficient asset use
- Too low signals potential liquidity risk
Benchmarks:
| Rating | Range |
|---|---|
| Concern | < 1.0 |
| Acceptable | 1.0 - 1.5 |
| Healthy | 1.5 - 3.0 |
| Excessive | > 3.0 |
Quick Ratio (Acid Test)
Formula: (Current Assets - Inventory) / Current Liabilities
Interpretation:
- More conservative than current ratio
- Excludes inventory (least liquid current asset)
- Critical for businesses with slow-moving inventory
Benchmarks:
| Rating | Range |
|---|---|
| Concern | < 0.8 |
| Acceptable | 0.8 - 1.0 |
| Healthy | 1.0 - 2.0 |
| Excessive | > 2.0 |
Cash Ratio
Formula: Cash & Equivalents / Current Liabilities
Interpretation:
- Most conservative liquidity measure
- Indicates ability to pay obligations with cash on hand
- Particularly important during credit crunches
Benchmarks:
| Rating | Range |
|---|---|
| Low | < 0.2 |
| Adequate | 0.2 - 0.5 |
| Strong | 0.5 - 1.0 |
| Excessive | > 1.0 |
3. Leverage Ratios
Measure the extent to which a company uses debt financing.
Debt-to-Equity Ratio
Formula: Total Debt / Total Shareholders' Equity
Interpretation:
- Measures financial leverage and risk
- Higher ratio = more reliance on debt financing
- Industry norms vary significantly (utilities vs tech)
Benchmarks:
| Rating | Range |
|---|---|
| Conservative | < 0.3 |
| Moderate | 0.3 - 0.8 |
| Elevated | 0.8 - 2.0 |
| High Risk | > 2.0 |
Interest Coverage Ratio
Formula: Operating Income (EBIT) / Interest Expense
Interpretation:
- Measures ability to service debt from operating earnings
- Below 1.5x is a red flag for lenders
- Critical for credit analysis
Benchmarks:
| Rating | Range |
|---|---|
| Distressed | < 2.0 |
| Adequate | 2.0 - 5.0 |
| Strong | 5.0 - 10.0 |
| Very Strong | > 10.0 |
Debt Service Coverage Ratio (DSCR)
Formula: Operating Cash Flow / Total Debt Service
Interpretation:
- Cash-based measure of debt servicing capacity
- Includes principal repayments (unlike interest coverage)
- Required by many loan covenants
Benchmarks:
| Rating | Range |
|---|---|
| Default Risk | < 1.0 |
| Minimum | 1.0 - 1.5 |
| Comfortable | 1.5 - 2.5 |
| Strong | > 2.5 |
4. Efficiency Ratios
Measure how effectively a company uses its assets and manages operations.
Asset Turnover
Formula: Revenue / Total Assets
Interpretation:
- Measures revenue generated per dollar of assets
- Higher indicates more efficient asset utilization
- Inversely related to profit margins (DuPont)
Benchmarks:
| Industry | Typical Range |
|---|---|
| Retail | 2.0 - 3.0 |
| Manufacturing | 0.8 - 1.5 |
| Utilities | 0.3 - 0.5 |
| Technology | 0.5 - 1.0 |
Inventory Turnover
Formula: COGS / Average Inventory
Interpretation:
- Measures how quickly inventory is sold
- Low turnover suggests overstock or obsolescence risk
- High turnover may indicate strong sales or thin inventory
Benchmarks:
| Rating | Range |
|---|---|
| Slow | < 4x |
| Average | 4x - 8x |
| Efficient | 8x - 12x |
| Very Efficient | > 12x |
Receivables Turnover
Formula: Revenue / Accounts Receivable
Interpretation:
- Measures efficiency of credit and collections
- Higher turnover means faster collections
- Monitor trends for credit policy changes
Benchmarks:
| Rating | Range |
|---|---|
| Slow | < 6x |
| Average | 6x - 10x |
| Efficient | 10x - 15x |
| Very Efficient | > 15x |
Days Sales Outstanding (DSO)
Formula: 365 / Receivables Turnover
Interpretation:
- Average days to collect payment after a sale
- Lower DSO = faster cash conversion
- Compare against payment terms
Benchmarks:
| Rating | Range |
|---|---|
| Excellent | < 30 days |
| Good | 30 - 45 days |
| Acceptable | 45 - 60 days |
| Concern | > 60 days |
5. Valuation Ratios
Measure a company's market value relative to financial metrics.
Price-to-Earnings (P/E) Ratio
Formula: Share Price / Earnings Per Share
Interpretation:
- Most widely used valuation metric
- High P/E suggests growth expectations or overvaluation
- Use trailing (TTM) and forward P/E for comparison
Benchmarks:
| Rating | Range |
|---|---|
| Value | < 10x |
| Fair | 10x - 20x |
| Growth | 20x - 35x |
| Premium | > 35x |
Price-to-Book (P/B) Ratio
Formula: Share Price / Book Value Per Share
Interpretation:
- Compares market value to accounting value
- Below 1.0 may indicate undervaluation or distress
- Most useful for asset-heavy industries
Benchmarks:
| Rating | Range |
|---|---|
| Undervalued | < 1.0 |
| Fair | 1.0 - 2.5 |
| Premium | 2.5 - 5.0 |
| Rich | > 5.0 |
Price-to-Sales (P/S) Ratio
Formula: Market Cap / Revenue
Interpretation:
- Useful for companies without positive earnings
- Compare within industry only
- Lower = potentially better value
Benchmarks:
| Rating | Range |
|---|---|
| Value | < 1.0 |
| Fair | 1.0 - 3.0 |
| Growth | 3.0 - 8.0 |
| Premium | > 8.0 |
EV/EBITDA
Formula: Enterprise Value / EBITDA
Interpretation:
- Capital-structure-neutral valuation metric
- Preferred for M&A analysis and leveraged buyouts
- More comparable across capital structures than P/E
Benchmarks:
| Rating | Range |
|---|---|
| Value | < 6x |
| Fair | 6x - 12x |
| Growth | 12x - 20x |
| Premium | > 20x |
PEG Ratio
Formula: P/E Ratio / Earnings Growth Rate (%)
Interpretation:
- Growth-adjusted P/E ratio
- PEG of 1.0 suggests fair valuation relative to growth
- Below 1.0 may indicate undervaluation
Benchmarks:
| Rating | Range |
|---|---|
| Undervalued | < 0.5 |
| Fair | 0.5 - 1.0 |
| Fully Valued | 1.0 - 2.0 |
| Overvalued | > 2.0 |
Ratio Analysis Best Practices
- Compare within industry - Ratios vary significantly across sectors
- Analyze trends - A single period snapshot is insufficient; look at 3-5 year trends
- Use multiple ratios - No single ratio tells the complete story
- Consider context - Accounting policies, business cycle, and company stage matter
- DuPont decomposition - Break ROE into margin, turnover, and leverage components
- Peer comparison - Compare against direct competitors, not just broad benchmarks
- Watch for manipulation - Revenue recognition changes, off-balance-sheet items, and one-time adjustments can distort ratios
Forecasting Best Practices
Comprehensive reference for financial forecasting including driver-based models, rolling forecasts, accuracy improvement techniques, and scenario planning.
1. Driver-Based Forecasting
Overview
Driver-based forecasting models financial outcomes based on key business drivers rather than extrapolating from historical trends alone. This approach creates more transparent, actionable, and accurate forecasts.
Identifying Key Drivers
Revenue Drivers:
| Business Model | Primary Drivers |
|---|---|
| SaaS/Subscription | Customers x ARPU x Retention Rate |
| E-commerce | Visitors x Conversion Rate x AOV |
| Manufacturing | Units x Price per Unit |
| Professional Services | Headcount x Utilization x Bill Rate |
| Retail | Stores x Revenue per Store (or sqft) |
| Marketplace | GMV x Take Rate |
Cost Drivers:
| Category | Common Drivers |
|---|---|
| COGS | Revenue x (1 - Gross Margin) or Units x Unit Cost |
| Headcount Costs | Employees x Average Compensation x (1 + Benefits Rate) |
| Sales & Marketing | Revenue x S&M % or CAC x New Customers |
| R&D | Engineering Headcount x Avg Salary |
| G&A | Headcount-based + fixed costs |
| CapEx | Revenue x CapEx Intensity or Project-based |
Building a Driver-Based Model
Step 1: Map the value chain
- Revenue = f(volume drivers, pricing drivers, mix drivers)
- Costs = f(variable drivers, fixed components, step functions)
Step 2: Establish driver relationships
- Linear: Revenue = Units x Price
- Non-linear: Revenue = Base x (1 + Growth Rate)^t
- Step function: Facilities costs that jump at capacity thresholds
Step 3: Validate driver assumptions
- Compare driver values to historical actuals
- Benchmark against industry data
- Stress-test extreme values
Step 4: Build sensitivity
- Identify which drivers have the largest impact on output
- Quantify the range of reasonable values for each driver
- Create scenario combinations
Driver Sensitivity Matrix
Rank drivers by impact and uncertainty:
| High Impact | Low Impact | |
|---|---|---|
| High Uncertainty | Model these carefully, run scenarios | Monitor but don't over-model |
| Low Uncertainty | Get these right; high accuracy needed | Use simple assumptions |
2. Rolling Forecasts
What Is a Rolling Forecast?
A rolling forecast continuously extends the forecast horizon as each period closes. Unlike a static annual budget, a rolling forecast always looks forward the same number of periods (typically 12-18 months).
Rolling Forecast vs Annual Budget
| Feature | Annual Budget | Rolling Forecast |
|---|---|---|
| Time Horizon | Fixed (Jan-Dec) | Rolling (12-18 months) |
| Update Frequency | Once per year | Monthly or quarterly |
| Detail Level | Very detailed | Driver-level |
| Preparation Time | 3-6 months | 2-5 days per cycle |
| Relevance | Declines over time | Stays current |
| Flexibility | Rigid | Adaptive |
Implementation Steps
- Select the horizon - 12 months rolling is most common (some use 18 months for CapEx planning)
- Define update cadence - Monthly for volatile businesses; quarterly for stable ones
- Choose the right detail - Driver-level, not line-item detail
- Automate data feeds - Reduce manual effort per cycle
- Separate actuals from forecast - Clear delineation between reported and projected periods
- Track forecast accuracy - Measure MAPE (Mean Absolute Percentage Error) over time
13-Week Cash Flow Forecast
A specialized rolling forecast for liquidity management:
Structure:
- Week-by-week cash inflows and outflows
- Opening and closing cash balances
- Minimum cash threshold alerts
Key Components:
| Inflows | Outflows |
|---|---|
| Customer collections (by aging) | Payroll (fixed cadence) |
| Other receivables | Rent / Lease payments |
| Asset sales | Vendor payments (by terms) |
| Financing proceeds | Debt service |
| Tax refunds | Tax payments |
| Other income | Capital expenditures |
Collection Modeling:
- Apply collection rates by customer segment or aging bucket
- Model DSO trends to project collection timing
- Account for seasonal patterns in payment behavior
3. Accuracy Improvement
Measuring Forecast Accuracy
Mean Absolute Percentage Error (MAPE):
MAPE = (1/n) x Sum of |Actual - Forecast| / |Actual| x 100%Accuracy Benchmarks:
| MAPE | Rating |
|---|---|
| < 5% | Excellent |
| 5% - 10% | Good |
| 10% - 20% | Acceptable |
| > 20% | Needs improvement |
Weighted MAPE (WMAPE): Use when line items vary significantly in magnitude - weights errors by actual values.
Techniques to Improve Accuracy
1. Bias Detection and Correction
- Track directional bias (consistently over or under forecasting)
- Calculate mean signed error to detect systematic bias
- Adjust driver assumptions to correct persistent bias
2. Variance Analysis Loop
- After each period closes, compare actual vs forecast
- Identify root causes of significant variances
- Update driver assumptions based on learnings
- Document what changed and why
3. Ensemble Approach
- Combine multiple forecasting methods
- Blend statistical (trend) with judgmental (management input)
- Weight methods by their historical accuracy
4. Granularity Optimization
- Forecast at the right level of detail - not too aggregated, not too granular
- Product/segment level usually more accurate than single top-line
- Aggregate bottom-up forecasts for total, then adjust
5. Leading Indicators
- Identify metrics that predict financial outcomes 1-3 months ahead
- Pipeline/bookings predict revenue
- Hiring plans predict headcount costs
- Customer churn signals predict retention revenue
Common Accuracy Killers
- Anchoring bias - Over-relying on last year's numbers
- Optimism bias - Systematic overestimation of growth
- Lack of accountability - No one tracks forecast vs actual
- Stale assumptions - Not updating for market changes
- Missing data - Forecasting without key driver inputs
- Over-precision - False precision in uncertain environments
4. Scenario Planning
Three-Scenario Framework
| Scenario | Description | Probability |
|---|---|---|
| Base Case | Most likely outcome based on current trajectory | 50-60% |
| Bull Case | Favorable conditions, upside realization | 15-25% |
| Bear Case | Adverse conditions, downside risks | 15-25% |
Scenario Construction
Base Case:
- Continuation of current trends
- Management's operational plan
- Market consensus assumptions
- Normal competitive dynamics
Bull Case (apply selectively, not uniformly):
- Faster customer acquisition or market adoption
- Successful product launch or expansion
- Favorable macro conditions
- Competitor weakness or exit
- Margin expansion from operating leverage
Bear Case (be realistic, not catastrophic):
- Slower growth or market contraction
- Increased competition or pricing pressure
- Key customer or contract loss
- Supply chain disruption
- Regulatory headwinds
Scenario Variables
Map each scenario to specific driver values:
| Driver | Bear | Base | Bull |
|---|---|---|---|
| Revenue Growth | +2% | +8% | +15% |
| Gross Margin | 35% | 40% | 43% |
| Customer Churn | 8% | 5% | 3% |
| New Customers/Month | 50 | 100 | 180 |
| Price Increase | 0% | 3% | 5% |
Presenting Scenarios
- Show the range - Management needs to see the potential outcomes
- Quantify the gap - Dollar impact of bull vs bear on key metrics
- Identify triggers - What conditions would cause each scenario
- Define actions - What levers to pull in each scenario
- Assign probabilities - Not all scenarios are equally likely
5. Forecast Communication
Stakeholder Needs
| Audience | Needs |
|---|---|
| Board | High-level scenarios, key risks, strategic implications |
| CEO/CFO | Detailed drivers, variance explanations, action items |
| Department Heads | Their specific budget vs forecast, headcount plans |
| Investors | Revenue guidance, margin trajectory, capital allocation |
| Operations | Weekly/monthly targets, resource requirements |
Presentation Framework
- Executive summary - Key metrics, direction of travel, confidence level
- Variance bridge - Walk from budget/prior forecast to current forecast
- Driver analysis - What changed and why
- Scenario comparison - Range of outcomes
- Key risks and opportunities - What could change the forecast
- Action items - Decisions needed based on forecast
Forecast Cadence
| Activity | Frequency | Time Required |
|---|---|---|
| 13-week cash flow update | Weekly | 1-2 hours |
| Rolling forecast update | Monthly | 1-2 days |
| Full reforecast | Quarterly | 3-5 days |
| Annual budget/plan | Annually | 4-8 weeks |
| Board reporting | Quarterly | 2-3 days |
6. Industry-Specific Considerations
SaaS Metrics in Forecasting
- MRR/ARR decomposition: New, expansion, contraction, churn
- Cohort-based forecasting: Forecast by customer cohort for retention accuracy
- Rule of 40: Revenue growth % + Profit margin % should exceed 40%
- Net Revenue Retention: Target > 110% for healthy SaaS
- CAC Payback: Should be < 18 months
Retail Forecasting
- Same-store sales growth as primary organic growth metric
- Seasonal decomposition for accurate monthly/weekly forecasts
- Markdown optimization impact on gross margin
- Inventory turns drive working capital forecasts
Manufacturing Forecasting
- Order backlog as a leading indicator
- Capacity constraints creating step-function cost increases
- Raw material price forecasts for COGS
- Maintenance CapEx vs growth CapEx distinction
- Utilization rates driving unit cost projections
Industry Adaptations
Sector-specific metrics, benchmarks, and considerations for financial analysis.
SaaS / Software
Key Metrics:
- ARR / MRR growth rate
- Net Revenue Retention (NRR) — target >110%
- CAC Payback Period — target <18 months
- Rule of 40 (growth rate + profit margin ≥ 40%)
- LTV:CAC ratio — target >3:1
- Gross margin — target >70%
Valuation Multiples:
- Revenue multiple: 5-15x ARR (growth-adjusted)
- High-growth (>50%): 15-25x ARR
- Moderate growth (20-50%): 8-15x ARR
- Low growth (<20%): 3-8x ARR
Considerations:
- Deferred revenue recognition (ASC 606)
- Stock-based compensation impact on margins
- Cohort analysis critical for retention metrics
Retail / E-Commerce
Key Metrics:
- Same-store sales growth (SSS)
- Gross margin by category
- Inventory turnover — target varies by segment (grocery: 14-20x, fashion: 4-6x)
- Revenue per square foot (physical)
- Customer acquisition cost vs. AOV
- Return rate impact on unit economics
Valuation Multiples:
- EV/EBITDA: 8-15x (premium brands higher)
- P/E: 15-25x
Considerations:
- Seasonal revenue concentration (Q4 holiday)
- Working capital intensity (inventory cycles)
- Omnichannel attribution complexity
Manufacturing
Key Metrics:
- Gross margin by product line
- Capacity utilization rate — target >80%
- Days Inventory Outstanding (DIO)
- Warranty reserve as % of revenue
- Capex as % of revenue (maintenance vs. growth)
- Order backlog / book-to-bill ratio
Valuation Multiples:
- EV/EBITDA: 6-12x
- P/E: 12-20x
Considerations:
- Raw material cost volatility
- Currency exposure in supply chain
- Depreciation schedules (straight-line vs. accelerated)
- Regulatory compliance costs (environmental, safety)
Financial Services
Key Metrics:
- Net Interest Margin (NIM)
- Return on Equity (ROE) — target >12%
- Cost-to-Income Ratio — target <60%
- Non-Performing Loan (NPL) ratio
- Tier 1 Capital Ratio — regulatory minimum varies
- Assets Under Management (AUM) growth
Valuation Multiples:
- Price-to-Book (P/B): 1.0-2.5x
- P/E: 10-18x
Considerations:
- Regulatory capital requirements (Basel III/IV)
- Interest rate sensitivity analysis
- Credit risk provisioning (CECL / IFRS 9)
- Mark-to-market vs. held-to-maturity accounting
Healthcare
Key Metrics:
- Revenue per patient / per bed
- Payor mix (Medicare/Medicaid vs. commercial)
- EBITDAR margin (rent-adjusted for facilities)
- Clinical trial pipeline value (biotech/pharma)
- Patent cliff exposure
- R&D as % of revenue — benchmark 15-25% (pharma)
Valuation Multiples:
- EV/EBITDA: 10-18x (medtech), 12-20x (pharma)
- EV/Revenue: 3-8x (services), 5-15x (devices)
Considerations:
- Reimbursement rate changes (regulatory risk)
- FDA approval timelines and probability-weighted pipeline
- 340B pricing program impact
- Medical device regulation (MDR, QSR compliance)
Valuation Methodology Guide
Comprehensive reference for business valuation approaches including DCF analysis, comparable company analysis, and precedent transactions.
1. Discounted Cash Flow (DCF) Methodology
Overview
DCF is an intrinsic valuation method that estimates the present value of a company's expected future free cash flows, discounted at an appropriate rate reflecting the risk of those cash flows.
Core Principle: The value of a business equals the present value of all future cash flows it will generate.
Formula:
Enterprise Value = Sum of [FCF_t / (1 + WACC)^t] + Terminal Value / (1 + WACC)^nWhere:
- FCF_t = Free Cash Flow in year t
- WACC = Weighted Average Cost of Capital
- n = number of projection years
Step 1: Historical Analysis
Before projecting, analyze 3-5 years of historical financials:
- Revenue growth rates - Identify organic vs acquisition-driven growth
- Margin trends - Gross, operating, and net margin trajectories
- Capital intensity - CapEx as % of revenue
- Working capital - Cash conversion cycle trends
- Free cash flow conversion - FCF / Net Income ratio
Step 2: Revenue Projections
Approaches:
- Top-down: Market size x Market share x Pricing
- Bottom-up: Units x Price, or Customers x ARPU
- Growth rate extrapolation: Historical growth with decay
Revenue Projection Best Practices:
- Use 5-7 year explicit projection period
- Growth should converge toward GDP growth by terminal year
- Support assumptions with market data and management guidance
- Model revenue by segment/product line when possible
Step 3: Free Cash Flow Calculation
Unlevered Free Cash Flow (UFCF):
UFCF = EBIT x (1 - Tax Rate)
+ Depreciation & Amortization
- Capital Expenditures
- Changes in Net Working CapitalKey Drivers:
- Operating margin trajectory
- CapEx as % of revenue (maintenance vs growth)
- Working capital requirements (DSO, DIO, DPO)
- Tax rate (effective vs marginal)
Step 4: WACC Calculation
Weighted Average Cost of Capital:
WACC = (E/V x Re) + (D/V x Rd x (1 - T))Where:
- E/V = Equity weight (market value)
- D/V = Debt weight (market value)
- Re = Cost of equity
- Rd = Cost of debt (pre-tax)
- T = Marginal tax rate
Cost of Equity (CAPM)
Re = Rf + Beta x (Rm - Rf) + Size Premium + Company-Specific Risk| Component | Description | Typical Range |
|---|---|---|
| Risk-Free Rate (Rf) | 10-year Treasury yield | 3.5% - 5.0% |
| Equity Risk Premium (ERP) | Market return above risk-free | 5.0% - 7.0% |
| Beta | Systematic risk relative to market | 0.5 - 2.0 |
| Size Premium | Small-cap additional risk | 0% - 5% |
| Company-Specific Risk | Unique risk factors | 0% - 5% |
Beta Estimation:
- Use 2-5 year weekly returns against broad market index
- Unlevered betas for comparability, then re-lever to target capital structure
- Consider industry median beta for stability
Cost of Debt
Rd = Yield on comparable-maturity corporate bonds
OR
Rd = Risk-Free Rate + Credit SpreadCredit Spread by Rating:
| Rating | Typical Spread |
|---|---|
| AAA | 0.5% - 1.0% |
| AA | 1.0% - 1.5% |
| A | 1.5% - 2.0% |
| BBB | 2.0% - 3.0% |
| BB | 3.0% - 5.0% |
| B | 5.0% - 8.0% |
Step 5: Terminal Value
Terminal value typically represents 60-80% of total enterprise value. Use two methods and cross-check.
Perpetuity Growth Method
TV = FCF_n x (1 + g) / (WACC - g)Where g = terminal growth rate (typically 2.0% - 3.0%, should not exceed long-term GDP growth)
Sensitivity: Terminal value is highly sensitive to g. A 0.5% change in g can move enterprise value by 15-25%.
Exit Multiple Method
TV = Terminal Year EBITDA x Exit EV/EBITDA MultipleExit Multiple Selection:
- Use current trading multiples of comparable companies
- Consider whether current multiples are at historical highs/lows
- Apply a discount for lack of marketability if private
Cross-Check: Both methods should yield similar results. Large discrepancies signal inconsistent assumptions.
Step 6: Enterprise to Equity Bridge
Enterprise Value
- Net Debt (Total Debt - Cash)
- Minority Interest
- Preferred Equity
+ Equity Method Investments
= Equity Value
Equity Value / Diluted Shares Outstanding = Value Per ShareStep 7: Sensitivity Analysis
Always present results as a range, not a single point estimate.
Standard Sensitivity Tables:
- WACC vs Terminal Growth Rate
- WACC vs Exit Multiple
- Revenue Growth vs Operating Margin
Scenario Analysis:
- Base case: Management guidance / consensus estimates
- Bull case: Upside scenario with faster growth or margin expansion
- Bear case: Downside scenario with slower growth or margin compression
2. Comparable Company Analysis
Methodology
- Select peer group - Similar size, industry, growth profile, and margins
- Calculate trading multiples for each peer
- Determine appropriate multiple range
- Apply to target company's metrics
Common Multiples
| Multiple | When to Use |
|---|---|
| EV/Revenue | Pre-profit companies, high-growth tech |
| EV/EBITDA | Most common for mature companies |
| EV/EBIT | When D&A differs significantly across peers |
| P/E | Stable earnings, financial services |
| P/B | Banks, insurance, asset-heavy industries |
| EV/FCF | Capital-light businesses with clean FCF |
Peer Selection Criteria
- Industry: Same or closely adjacent sectors
- Size: Within 0.5x to 2x of target revenue/market cap
- Geography: Same primary markets
- Growth profile: Similar revenue growth rates (within 5-10%)
- Margin profile: Similar operating margin structure
- Business model: Comparable revenue mix and customer base
Premium/Discount Adjustments
| Factor | Adjustment |
|---|---|
| Higher growth | Premium of 1-3x on EV/EBITDA |
| Lower margins | Discount of 1-2x |
| Smaller scale | Discount of 10-20% |
| Private company | Discount of 15-30% (illiquidity) |
| Control premium | Premium of 20-40% (for acquisitions) |
3. Precedent Transaction Analysis
Methodology
- Identify comparable transactions in same industry
- Calculate transaction multiples (EV/Revenue, EV/EBITDA)
- Adjust for market conditions and deal-specific factors
- Apply adjusted multiples to target
Key Considerations
- Transactions include control premiums (typically 20-40%)
- Market conditions at time of deal affect multiples
- Strategic vs financial buyer valuations differ
- Consider synergy expectations embedded in price
- More recent transactions carry greater relevance
4. Valuation Framework Selection
| Situation | Primary Method | Secondary Method |
|---|---|---|
| Profitable, stable | DCF | Comparable companies |
| High growth, pre-profit | Comparable companies (EV/Revenue) | DCF with scenario analysis |
| M&A target | Precedent transactions | DCF |
| Asset-heavy, cyclical | Asset-based valuation | Normalized DCF |
| Financial institution | Dividend discount model | P/B, P/E comps |
| Distressed | Liquidation value | Restructured DCF |
5. Common Pitfalls
- Hockey stick projections - Unrealistic growth acceleration in later years
- Terminal value dominance - If TV > 80% of EV, shorten projection period or question assumptions
- Circular references - WACC depends on equity value which depends on WACC
- Ignoring working capital - Can significantly affect FCF
- Single-point estimates - Always present as a range
- Stale comparables - Market conditions change; update regularly
- Confirmation bias - Don't work backward from a desired conclusion
- Ignoring dilution - Use fully diluted shares (treasury stock method for options)
#!/usr/bin/env python3
"""
Budget Variance Analyzer
Analyzes actual vs budget vs prior year performance with materiality
threshold filtering, favorable/unfavorable classification, and
department/category breakdown.
Usage:
python budget_variance_analyzer.py budget_data.json
python budget_variance_analyzer.py budget_data.json --format json
python budget_variance_analyzer.py budget_data.json --threshold-pct 5 --threshold-amt 25000
"""
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 BudgetVarianceAnalyzer:
"""Analyze budget variances with materiality filtering and classification."""
def __init__(
self,
data: Dict[str, Any],
threshold_pct: float = 10.0,
threshold_amt: float = 50000.0,
) -> None:
"""
Initialize the analyzer.
Args:
data: Budget data with line items
threshold_pct: Materiality threshold as percentage (default 10%)
threshold_amt: Materiality threshold as dollar amount (default $50K)
"""
self.line_items: List[Dict[str, Any]] = data.get("line_items", [])
self.period: str = data.get("period", "Current Period")
self.company: str = data.get("company", "Company")
self.threshold_pct = threshold_pct
self.threshold_amt = threshold_amt
self.variances: List[Dict[str, Any]] = []
self.material_variances: List[Dict[str, Any]] = []
self.summary: Dict[str, Any] = {}
def classify_favorability(
self, line_type: str, variance_amount: float
) -> str:
"""
Classify variance as favorable or unfavorable.
Revenue: over budget = favorable
Expense: under budget = favorable
"""
if line_type.lower() in ("revenue", "income", "sales"):
return "Favorable" if variance_amount > 0 else "Unfavorable"
else:
# For expenses, under budget (negative variance) is favorable
return "Favorable" if variance_amount < 0 else "Unfavorable"
def calculate_variances(self) -> List[Dict[str, Any]]:
"""Calculate variances for all line items."""
self.variances = []
for item in self.line_items:
name = item.get("name", "Unknown")
line_type = item.get("type", "expense")
department = item.get("department", "General")
category = item.get("category", "Other")
actual = item.get("actual", 0)
budget = item.get("budget", 0)
prior_year = item.get("prior_year", None)
# Budget variance
budget_var_amt = actual - budget
budget_var_pct = safe_divide(budget_var_amt, budget) * 100
# Prior year variance (if available)
py_var_amt = (actual - prior_year) if prior_year is not None else None
py_var_pct = (
safe_divide(py_var_amt, prior_year) * 100
if prior_year is not None
else None
)
favorability = self.classify_favorability(line_type, budget_var_amt)
is_material = (
abs(budget_var_pct) >= self.threshold_pct
or abs(budget_var_amt) >= self.threshold_amt
)
variance_record = {
"name": name,
"type": line_type,
"department": department,
"category": category,
"actual": actual,
"budget": budget,
"prior_year": prior_year,
"budget_variance_amount": budget_var_amt,
"budget_variance_pct": round(budget_var_pct, 2),
"prior_year_variance_amount": py_var_amt,
"prior_year_variance_pct": (
round(py_var_pct, 2) if py_var_pct is not None else None
),
"favorability": favorability,
"is_material": is_material,
}
self.variances.append(variance_record)
# Filter material variances
self.material_variances = [v for v in self.variances if v["is_material"]]
return self.variances
def department_summary(self) -> Dict[str, Dict[str, Any]]:
"""Summarize variances by department."""
departments: Dict[str, Dict[str, float]] = {}
for v in self.variances:
dept = v["department"]
if dept not in departments:
departments[dept] = {
"total_actual": 0.0,
"total_budget": 0.0,
"total_variance": 0.0,
"favorable_count": 0,
"unfavorable_count": 0,
"line_count": 0,
}
departments[dept]["total_actual"] += v["actual"]
departments[dept]["total_budget"] += v["budget"]
departments[dept]["total_variance"] += v["budget_variance_amount"]
departments[dept]["line_count"] += 1
if v["favorability"] == "Favorable":
departments[dept]["favorable_count"] += 1
else:
departments[dept]["unfavorable_count"] += 1
# Add variance percentage
for dept_data in departments.values():
dept_data["variance_pct"] = round(
safe_divide(
dept_data["total_variance"], dept_data["total_budget"]
)
* 100,
2,
)
return departments
def category_summary(self) -> Dict[str, Dict[str, Any]]:
"""Summarize variances by category."""
categories: Dict[str, Dict[str, float]] = {}
for v in self.variances:
cat = v["category"]
if cat not in categories:
categories[cat] = {
"total_actual": 0.0,
"total_budget": 0.0,
"total_variance": 0.0,
"line_count": 0,
}
categories[cat]["total_actual"] += v["actual"]
categories[cat]["total_budget"] += v["budget"]
categories[cat]["total_variance"] += v["budget_variance_amount"]
categories[cat]["line_count"] += 1
for cat_data in categories.values():
cat_data["variance_pct"] = round(
safe_divide(
cat_data["total_variance"], cat_data["total_budget"]
)
* 100,
2,
)
return categories
def generate_executive_summary(self) -> Dict[str, Any]:
"""Generate an executive summary of the variance analysis."""
total_actual = sum(
v["actual"] for v in self.variances if v["type"].lower() in ("revenue", "income", "sales")
)
total_budget = sum(
v["budget"] for v in self.variances if v["type"].lower() in ("revenue", "income", "sales")
)
total_expense_actual = sum(
v["actual"] for v in self.variances if v["type"].lower() not in ("revenue", "income", "sales")
)
total_expense_budget = sum(
v["budget"] for v in self.variances if v["type"].lower() not in ("revenue", "income", "sales")
)
revenue_variance = total_actual - total_budget
expense_variance = total_expense_actual - total_expense_budget
favorable_count = sum(
1 for v in self.variances if v["favorability"] == "Favorable"
)
unfavorable_count = sum(
1 for v in self.variances if v["favorability"] == "Unfavorable"
)
self.summary = {
"period": self.period,
"company": self.company,
"total_line_items": len(self.variances),
"material_variances_count": len(self.material_variances),
"favorable_count": favorable_count,
"unfavorable_count": unfavorable_count,
"revenue": {
"actual": total_actual,
"budget": total_budget,
"variance_amount": revenue_variance,
"variance_pct": round(
safe_divide(revenue_variance, total_budget) * 100, 2
),
},
"expenses": {
"actual": total_expense_actual,
"budget": total_expense_budget,
"variance_amount": expense_variance,
"variance_pct": round(
safe_divide(expense_variance, total_expense_budget) * 100, 2
),
},
"net_impact": revenue_variance - expense_variance,
"materiality_thresholds": {
"percentage": self.threshold_pct,
"amount": self.threshold_amt,
},
}
return self.summary
def run_analysis(self) -> Dict[str, Any]:
"""Run the complete variance analysis."""
self.calculate_variances()
dept_summary = self.department_summary()
cat_summary = self.category_summary()
exec_summary = self.generate_executive_summary()
return {
"executive_summary": exec_summary,
"all_variances": self.variances,
"material_variances": self.material_variances,
"department_summary": dept_summary,
"category_summary": cat_summary,
}
def format_text(self, results: Dict[str, Any]) -> str:
"""Format results as human-readable text."""
lines: List[str] = []
lines.append("=" * 70)
lines.append("BUDGET VARIANCE ANALYSIS")
lines.append("=" * 70)
summary = results["executive_summary"]
lines.append(f"\n Company: {summary['company']}")
lines.append(f" Period: {summary['period']}")
def fmt_money(val: float) -> str:
sign = "+" if val > 0 else ""
if abs(val) >= 1e6:
return f"{sign}${val / 1e6:,.2f}M"
if abs(val) >= 1e3:
return f"{sign}${val / 1e3:,.1f}K"
return f"{sign}${val:,.2f}"
lines.append(f"\n--- EXECUTIVE SUMMARY ---")
rev = summary["revenue"]
exp = summary["expenses"]
lines.append(
f" Revenue: Actual {fmt_money(rev['actual'])} vs "
f"Budget {fmt_money(rev['budget'])} "
f"({fmt_money(rev['variance_amount'])}, {rev['variance_pct']:+.1f}%)"
)
lines.append(
f" Expenses: Actual {fmt_money(exp['actual'])} vs "
f"Budget {fmt_money(exp['budget'])} "
f"({fmt_money(exp['variance_amount'])}, {exp['variance_pct']:+.1f}%)"
)
lines.append(f" Net Impact: {fmt_money(summary['net_impact'])}")
lines.append(
f" Total Items: {summary['total_line_items']} | "
f"Material: {summary['material_variances_count']} | "
f"Favorable: {summary['favorable_count']} | "
f"Unfavorable: {summary['unfavorable_count']}"
)
# Material variances
material = results["material_variances"]
if material:
lines.append(f"\n--- MATERIAL VARIANCES ---")
lines.append(
f" (Threshold: {self.threshold_pct}% or "
f"${self.threshold_amt:,.0f})"
)
for v in material:
lines.append(
f"\n {v['name']} ({v['department']})"
)
lines.append(
f" Actual: {fmt_money(v['actual'])} | "
f"Budget: {fmt_money(v['budget'])}"
)
lines.append(
f" Variance: {fmt_money(v['budget_variance_amount'])} "
f"({v['budget_variance_pct']:+.1f}%) - {v['favorability']}"
)
# Department summary
dept = results["department_summary"]
if dept:
lines.append(f"\n--- DEPARTMENT SUMMARY ---")
for dept_name, d in dept.items():
lines.append(
f" {dept_name}: Variance {fmt_money(d['total_variance'])} "
f"({d['variance_pct']:+.1f}%) | "
f"Fav: {d['favorable_count']} / Unfav: {d['unfavorable_count']}"
)
# Category summary
cat = results["category_summary"]
if cat:
lines.append(f"\n--- CATEGORY SUMMARY ---")
for cat_name, c in cat.items():
lines.append(
f" {cat_name}: Variance {fmt_money(c['total_variance'])} "
f"({c['variance_pct']:+.1f}%)"
)
lines.append("\n" + "=" * 70)
return "\n".join(lines)
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Analyze budget variances with materiality filtering"
)
parser.add_argument(
"input_file",
help="Path to JSON file with budget data",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
parser.add_argument(
"--threshold-pct",
type=float,
default=10.0,
help="Materiality threshold percentage (default: 10)",
)
parser.add_argument(
"--threshold-amt",
type=float,
default=50000.0,
help="Materiality threshold dollar amount (default: 50000)",
)
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)
analyzer = BudgetVarianceAnalyzer(
data,
threshold_pct=args.threshold_pct,
threshold_amt=args.threshold_amt,
)
results = analyzer.run_analysis()
if args.format == "json":
print(json.dumps(results, indent=2))
else:
print(analyzer.format_text(results))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
DCF Valuation Model
Discounted Cash Flow enterprise and equity valuation with WACC calculation,
terminal value estimation, and two-way sensitivity analysis.
Uses standard library only (math, statistics) - NO numpy/pandas/scipy.
Usage:
python dcf_valuation.py valuation_data.json
python dcf_valuation.py valuation_data.json --format json
python dcf_valuation.py valuation_data.json --projection-years 7
"""
import argparse
import json
import math
import sys
from statistics import mean
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 DCFModel:
"""Discounted Cash Flow valuation model."""
def __init__(self) -> None:
"""Initialize the DCF model."""
self.historical: Dict[str, Any] = {}
self.assumptions: Dict[str, Any] = {}
self.wacc: float = 0.0
self.projected_revenue: List[float] = []
self.projected_fcf: List[float] = []
self.projection_years: int = 5
self.terminal_value_perpetuity: float = 0.0
self.terminal_value_exit_multiple: float = 0.0
self.enterprise_value_perpetuity: float = 0.0
self.enterprise_value_exit_multiple: float = 0.0
self.equity_value_perpetuity: float = 0.0
self.equity_value_exit_multiple: float = 0.0
self.value_per_share_perpetuity: float = 0.0
self.value_per_share_exit_multiple: float = 0.0
def set_historical_financials(self, historical: Dict[str, Any]) -> None:
"""Set historical financial data."""
self.historical = historical
def set_assumptions(self, assumptions: Dict[str, Any]) -> None:
"""Set projection assumptions."""
self.assumptions = assumptions
self.projection_years = assumptions.get("projection_years", 5)
def calculate_wacc(self) -> float:
"""Calculate Weighted Average Cost of Capital via CAPM."""
wacc_inputs = self.assumptions.get("wacc_inputs", {})
risk_free_rate = wacc_inputs.get("risk_free_rate", 0.04)
equity_risk_premium = wacc_inputs.get("equity_risk_premium", 0.06)
beta = wacc_inputs.get("beta", 1.0)
cost_of_debt = wacc_inputs.get("cost_of_debt", 0.05)
tax_rate = wacc_inputs.get("tax_rate", 0.25)
debt_weight = wacc_inputs.get("debt_weight", 0.30)
equity_weight = wacc_inputs.get("equity_weight", 0.70)
# CAPM: Cost of Equity = Risk-Free Rate + Beta * Equity Risk Premium
cost_of_equity = risk_free_rate + beta * equity_risk_premium
# WACC = (E/V * Re) + (D/V * Rd * (1 - T))
after_tax_cost_of_debt = cost_of_debt * (1 - tax_rate)
self.wacc = (equity_weight * cost_of_equity) + (
debt_weight * after_tax_cost_of_debt
)
return self.wacc
def project_cash_flows(self) -> Tuple[List[float], List[float]]:
"""Project revenue and free cash flow over the projection period."""
base_revenue = self.historical.get("revenue", [])
if not base_revenue:
raise ValueError("Historical revenue data is required")
last_revenue = base_revenue[-1]
revenue_growth_rates = self.assumptions.get("revenue_growth_rates", [])
fcf_margins = self.assumptions.get("fcf_margins", [])
# If growth rates not provided for all years, use average or default
default_growth = self.assumptions.get("default_revenue_growth", 0.05)
default_fcf_margin = self.assumptions.get("default_fcf_margin", 0.10)
self.projected_revenue = []
self.projected_fcf = []
current_revenue = last_revenue
for year in range(self.projection_years):
growth = (
revenue_growth_rates[year]
if year < len(revenue_growth_rates)
else default_growth
)
fcf_margin = (
fcf_margins[year]
if year < len(fcf_margins)
else default_fcf_margin
)
current_revenue = current_revenue * (1 + growth)
fcf = current_revenue * fcf_margin
self.projected_revenue.append(current_revenue)
self.projected_fcf.append(fcf)
return self.projected_revenue, self.projected_fcf
def calculate_terminal_value(self) -> Tuple[float, float]:
"""Calculate terminal value using both perpetuity growth and exit multiple."""
if not self.projected_fcf:
raise ValueError("Must project cash flows before terminal value")
terminal_fcf = self.projected_fcf[-1]
terminal_growth = self.assumptions.get("terminal_growth_rate", 0.025)
exit_multiple = self.assumptions.get("exit_ev_ebitda_multiple", 12.0)
# Perpetuity growth method: TV = FCF * (1+g) / (WACC - g)
if self.wacc > terminal_growth:
self.terminal_value_perpetuity = (
terminal_fcf * (1 + terminal_growth)
) / (self.wacc - terminal_growth)
else:
self.terminal_value_perpetuity = 0.0
# Exit multiple method: TV = Terminal EBITDA * Exit Multiple
terminal_revenue = self.projected_revenue[-1]
ebitda_margin = self.assumptions.get("terminal_ebitda_margin", 0.20)
terminal_ebitda = terminal_revenue * ebitda_margin
self.terminal_value_exit_multiple = terminal_ebitda * exit_multiple
return self.terminal_value_perpetuity, self.terminal_value_exit_multiple
def calculate_enterprise_value(self) -> Tuple[float, float]:
"""Calculate enterprise value by discounting projected FCFs and terminal value."""
if not self.projected_fcf:
raise ValueError("Must project cash flows first")
# Discount projected FCFs
pv_fcf = 0.0
for i, fcf in enumerate(self.projected_fcf):
discount_factor = (1 + self.wacc) ** (i + 1)
pv_fcf += fcf / discount_factor
# Discount terminal values
terminal_discount = (1 + self.wacc) ** self.projection_years
pv_tv_perpetuity = self.terminal_value_perpetuity / terminal_discount
pv_tv_exit = self.terminal_value_exit_multiple / terminal_discount
self.enterprise_value_perpetuity = pv_fcf + pv_tv_perpetuity
self.enterprise_value_exit_multiple = pv_fcf + pv_tv_exit
return self.enterprise_value_perpetuity, self.enterprise_value_exit_multiple
def calculate_equity_value(self) -> Tuple[float, float]:
"""Calculate equity value from enterprise value."""
net_debt = self.historical.get("net_debt", 0)
shares_outstanding = self.historical.get("shares_outstanding", 1)
self.equity_value_perpetuity = (
self.enterprise_value_perpetuity - net_debt
)
self.equity_value_exit_multiple = (
self.enterprise_value_exit_multiple - net_debt
)
self.value_per_share_perpetuity = safe_divide(
self.equity_value_perpetuity, shares_outstanding
)
self.value_per_share_exit_multiple = safe_divide(
self.equity_value_exit_multiple, shares_outstanding
)
return self.equity_value_perpetuity, self.equity_value_exit_multiple
def sensitivity_analysis(
self,
wacc_range: Optional[List[float]] = None,
growth_range: Optional[List[float]] = None,
) -> Dict[str, Any]:
"""
Two-way sensitivity analysis: WACC vs terminal growth rate.
Returns a table of enterprise values using nested lists (no numpy).
"""
if wacc_range is None:
base_wacc = self.wacc
wacc_range = [
round(base_wacc - 0.02, 4),
round(base_wacc - 0.01, 4),
round(base_wacc, 4),
round(base_wacc + 0.01, 4),
round(base_wacc + 0.02, 4),
]
if growth_range is None:
base_growth = self.assumptions.get("terminal_growth_rate", 0.025)
growth_range = [
round(base_growth - 0.01, 4),
round(base_growth - 0.005, 4),
round(base_growth, 4),
round(base_growth + 0.005, 4),
round(base_growth + 0.01, 4),
]
rows = len(wacc_range)
cols = len(growth_range)
# Initialize sensitivity table as nested lists
ev_table = [[0.0] * cols for _ in range(rows)]
share_price_table = [[0.0] * cols for _ in range(rows)]
terminal_fcf = self.projected_fcf[-1] if self.projected_fcf else 0
for i, wacc_val in enumerate(wacc_range):
for j, growth_val in enumerate(growth_range):
if wacc_val <= growth_val:
ev_table[i][j] = float("inf")
share_price_table[i][j] = float("inf")
continue
# Recalculate PV of projected FCFs with this WACC
pv_fcf = 0.0
for k, fcf in enumerate(self.projected_fcf):
pv_fcf += fcf / ((1 + wacc_val) ** (k + 1))
# Terminal value with this growth rate
tv = (terminal_fcf * (1 + growth_val)) / (wacc_val - growth_val)
pv_tv = tv / ((1 + wacc_val) ** self.projection_years)
ev = pv_fcf + pv_tv
ev_table[i][j] = round(ev, 2)
net_debt = self.historical.get("net_debt", 0)
shares = self.historical.get("shares_outstanding", 1)
equity = ev - net_debt
share_price_table[i][j] = round(
safe_divide(equity, shares), 2
)
return {
"wacc_values": wacc_range,
"growth_values": growth_range,
"enterprise_value_table": ev_table,
"share_price_table": share_price_table,
}
def run_full_valuation(self) -> Dict[str, Any]:
"""Run the complete DCF valuation."""
self.calculate_wacc()
self.project_cash_flows()
self.calculate_terminal_value()
self.calculate_enterprise_value()
self.calculate_equity_value()
sensitivity = self.sensitivity_analysis()
return {
"wacc": self.wacc,
"projected_revenue": self.projected_revenue,
"projected_fcf": self.projected_fcf,
"terminal_value": {
"perpetuity_growth": self.terminal_value_perpetuity,
"exit_multiple": self.terminal_value_exit_multiple,
},
"enterprise_value": {
"perpetuity_growth": self.enterprise_value_perpetuity,
"exit_multiple": self.enterprise_value_exit_multiple,
},
"equity_value": {
"perpetuity_growth": self.equity_value_perpetuity,
"exit_multiple": self.equity_value_exit_multiple,
},
"value_per_share": {
"perpetuity_growth": self.value_per_share_perpetuity,
"exit_multiple": self.value_per_share_exit_multiple,
},
"sensitivity_analysis": sensitivity,
}
def format_text(self, results: Dict[str, Any]) -> str:
"""Format valuation results as human-readable text."""
lines: List[str] = []
lines.append("=" * 70)
lines.append("DCF VALUATION ANALYSIS")
lines.append("=" * 70)
def fmt_money(val: float) -> str:
if val == float("inf"):
return "N/A (WACC <= growth)"
if abs(val) >= 1e9:
return f"${val / 1e9:,.2f}B"
if abs(val) >= 1e6:
return f"${val / 1e6:,.2f}M"
if abs(val) >= 1e3:
return f"${val / 1e3:,.1f}K"
return f"${val:,.2f}"
lines.append(f"\n--- WACC ---")
lines.append(f" Weighted Average Cost of Capital: {results['wacc'] * 100:.2f}%")
lines.append(f"\n--- REVENUE PROJECTIONS ---")
for i, rev in enumerate(results["projected_revenue"], 1):
lines.append(f" Year {i}: {fmt_money(rev)}")
lines.append(f"\n--- FREE CASH FLOW PROJECTIONS ---")
for i, fcf in enumerate(results["projected_fcf"], 1):
lines.append(f" Year {i}: {fmt_money(fcf)}")
lines.append(f"\n--- TERMINAL VALUE ---")
lines.append(
f" Perpetuity Growth Method: "
f"{fmt_money(results['terminal_value']['perpetuity_growth'])}"
)
lines.append(
f" Exit Multiple Method: "
f"{fmt_money(results['terminal_value']['exit_multiple'])}"
)
lines.append(f"\n--- ENTERPRISE VALUE ---")
lines.append(
f" Perpetuity Growth Method: "
f"{fmt_money(results['enterprise_value']['perpetuity_growth'])}"
)
lines.append(
f" Exit Multiple Method: "
f"{fmt_money(results['enterprise_value']['exit_multiple'])}"
)
lines.append(f"\n--- EQUITY VALUE ---")
lines.append(
f" Perpetuity Growth Method: "
f"{fmt_money(results['equity_value']['perpetuity_growth'])}"
)
lines.append(
f" Exit Multiple Method: "
f"{fmt_money(results['equity_value']['exit_multiple'])}"
)
lines.append(f"\n--- VALUE PER SHARE ---")
vps = results["value_per_share"]
lines.append(f" Perpetuity Growth Method: ${vps['perpetuity_growth']:,.2f}")
lines.append(f" Exit Multiple Method: ${vps['exit_multiple']:,.2f}")
# Sensitivity table
sens = results["sensitivity_analysis"]
lines.append(f"\n--- SENSITIVITY ANALYSIS (Enterprise Value) ---")
lines.append(f" WACC vs Terminal Growth Rate")
lines.append("")
header = " {:>10s}".format("WACC \\ g")
for g in sens["growth_values"]:
header += f" {g * 100:>8.1f}%"
lines.append(header)
lines.append(" " + "-" * (10 + 10 * len(sens["growth_values"])))
for i, w in enumerate(sens["wacc_values"]):
row = f" {w * 100:>9.1f}%"
for j in range(len(sens["growth_values"])):
val = sens["enterprise_value_table"][i][j]
if val == float("inf"):
row += f" {'N/A':>8s}"
else:
row += f" {fmt_money(val):>8s}"
lines.append(row)
lines.append("\n" + "=" * 70)
return "\n".join(lines)
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="DCF Valuation Model - Enterprise and equity valuation"
)
parser.add_argument(
"input_file",
help="Path to JSON file with valuation data",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
parser.add_argument(
"--projection-years",
type=int,
default=None,
help="Number of projection years (overrides input file)",
)
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)
model = DCFModel()
model.set_historical_financials(data.get("historical", {}))
assumptions = data.get("assumptions", {})
if args.projection_years is not None:
assumptions["projection_years"] = args.projection_years
model.set_assumptions(assumptions)
try:
results = model.run_full_valuation()
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if args.format == "json":
# Handle inf values for JSON serialization
def sanitize(obj: Any) -> Any:
if isinstance(obj, float) and math.isinf(obj):
return None
if isinstance(obj, dict):
return {k: sanitize(v) for k, v in obj.items()}
if isinstance(obj, list):
return [sanitize(v) for v in obj]
return obj
print(json.dumps(sanitize(results), indent=2))
else:
print(model.format_text(results))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Forecast Builder
Driver-based revenue forecasting with 13-week rolling cash flow projection,
scenario modeling (base/bull/bear), and trend analysis using simple linear
regression (standard library only).
Usage:
python forecast_builder.py forecast_data.json
python forecast_builder.py forecast_data.json --format json
python forecast_builder.py forecast_data.json --scenarios base,bull,bear
"""
import argparse
import json
import math
import sys
from statistics import mean
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
def simple_linear_regression(
x_values: List[float], y_values: List[float]
) -> Tuple[float, float, float]:
"""
Simple linear regression using standard library.
Returns (slope, intercept, r_squared).
"""
n = len(x_values)
if n < 2 or n != len(y_values):
return (0.0, 0.0, 0.0)
x_mean = mean(x_values)
y_mean = mean(y_values)
ss_xy = sum((x - x_mean) * (y - y_mean) for x, y in zip(x_values, y_values))
ss_xx = sum((x - x_mean) ** 2 for x in x_values)
ss_yy = sum((y - y_mean) ** 2 for y in y_values)
slope = safe_divide(ss_xy, ss_xx)
intercept = y_mean - slope * x_mean
# R-squared
r_squared = safe_divide(ss_xy ** 2, ss_xx * ss_yy) if ss_yy > 0 else 0.0
return (slope, intercept, r_squared)
class ForecastBuilder:
"""Driver-based revenue forecasting with scenario modeling."""
def __init__(self, data: Dict[str, Any]) -> None:
"""Initialize the forecast builder."""
self.historical: List[Dict[str, Any]] = data.get("historical_periods", [])
self.drivers: Dict[str, Any] = data.get("drivers", {})
self.assumptions: Dict[str, Any] = data.get("assumptions", {})
self.cash_flow_inputs: Dict[str, Any] = data.get("cash_flow_inputs", {})
self.scenarios_config: Dict[str, Any] = data.get("scenarios", {})
self.forecast_periods: int = data.get("forecast_periods", 12)
def analyze_trends(self) -> Dict[str, Any]:
"""Analyze historical trends using linear regression."""
if not self.historical:
return {"error": "No historical data available"}
# Extract revenue series
revenues = [p.get("revenue", 0) for p in self.historical]
periods = list(range(1, len(revenues) + 1))
slope, intercept, r_squared = simple_linear_regression(
[float(x) for x in periods],
[float(y) for y in revenues],
)
# Calculate growth rates
growth_rates = []
for i in range(1, len(revenues)):
if revenues[i - 1] > 0:
growth = (revenues[i] - revenues[i - 1]) / revenues[i - 1]
growth_rates.append(growth)
avg_growth = mean(growth_rates) if growth_rates else 0.0
# Seasonality detection (if enough data)
seasonality_index: List[float] = []
if len(revenues) >= 4:
overall_avg = mean(revenues)
if overall_avg > 0:
seasonality_index = [r / overall_avg for r in revenues[-4:]]
return {
"trend": {
"slope": round(slope, 2),
"intercept": round(intercept, 2),
"r_squared": round(r_squared, 4),
"direction": "upward" if slope > 0 else "downward" if slope < 0 else "flat",
},
"growth_rates": [round(g, 4) for g in growth_rates],
"average_growth_rate": round(avg_growth, 4),
"seasonality_index": [round(s, 4) for s in seasonality_index],
"historical_revenues": revenues,
}
def build_driver_based_forecast(
self, scenario: str = "base"
) -> Dict[str, Any]:
"""
Build a driver-based revenue forecast.
Drivers may include: units, price, customers, ARPU, conversion rate, etc.
"""
scenario_adjustments = self.scenarios_config.get(scenario, {})
growth_adjustment = scenario_adjustments.get("growth_adjustment", 0.0)
margin_adjustment = scenario_adjustments.get("margin_adjustment", 0.0)
base_revenue = 0.0
if self.historical:
base_revenue = self.historical[-1].get("revenue", 0)
# Driver-based calculation
unit_drivers = self.drivers.get("units", {})
price_drivers = self.drivers.get("pricing", {})
customer_drivers = self.drivers.get("customers", {})
base_growth = self.assumptions.get("revenue_growth_rate", 0.05)
adjusted_growth = base_growth + growth_adjustment
base_margin = self.assumptions.get("gross_margin", 0.40)
adjusted_margin = base_margin + margin_adjustment
cogs_pct = 1.0 - adjusted_margin
opex_pct = self.assumptions.get("opex_pct_revenue", 0.25)
forecast_periods: List[Dict[str, Any]] = []
current_revenue = base_revenue
# If we have unit and price drivers, use them
has_unit_drivers = bool(unit_drivers) and bool(price_drivers)
if has_unit_drivers:
base_units = unit_drivers.get("base_units", 1000)
unit_growth = unit_drivers.get("growth_rate", 0.03) + growth_adjustment
base_price = price_drivers.get("base_price", 100)
price_growth = price_drivers.get("annual_increase", 0.02)
current_units = base_units
current_price = base_price
for period in range(1, self.forecast_periods + 1):
current_units = current_units * (1 + unit_growth / 12)
if period % 12 == 0:
current_price = current_price * (1 + price_growth)
period_revenue = current_units * current_price
cogs = period_revenue * cogs_pct
gross_profit = period_revenue - cogs
opex = period_revenue * opex_pct
operating_income = gross_profit - opex
forecast_periods.append({
"period": period,
"revenue": round(period_revenue, 2),
"units": round(current_units, 0),
"price": round(current_price, 2),
"cogs": round(cogs, 2),
"gross_profit": round(gross_profit, 2),
"gross_margin": round(adjusted_margin, 4),
"opex": round(opex, 2),
"operating_income": round(operating_income, 2),
})
else:
# Simple growth-based forecast
monthly_growth = (1 + adjusted_growth) ** (1 / 12) - 1
for period in range(1, self.forecast_periods + 1):
current_revenue = current_revenue * (1 + monthly_growth)
cogs = current_revenue * cogs_pct
gross_profit = current_revenue - cogs
opex = current_revenue * opex_pct
operating_income = gross_profit - opex
forecast_periods.append({
"period": period,
"revenue": round(current_revenue, 2),
"cogs": round(cogs, 2),
"gross_profit": round(gross_profit, 2),
"gross_margin": round(adjusted_margin, 4),
"opex": round(opex, 2),
"operating_income": round(operating_income, 2),
})
total_revenue = sum(p["revenue"] for p in forecast_periods)
total_operating_income = sum(p["operating_income"] for p in forecast_periods)
return {
"scenario": scenario,
"growth_rate": round(adjusted_growth, 4),
"gross_margin": round(adjusted_margin, 4),
"forecast_periods": forecast_periods,
"total_revenue": round(total_revenue, 2),
"total_operating_income": round(total_operating_income, 2),
"average_monthly_revenue": round(
safe_divide(total_revenue, len(forecast_periods)), 2
),
}
def build_rolling_cash_flow(self, weeks: int = 13) -> Dict[str, Any]:
"""Build a 13-week rolling cash flow projection."""
cfi = self.cash_flow_inputs
opening_balance = cfi.get("opening_cash_balance", 0)
weekly_revenue = cfi.get("weekly_revenue", 0)
collection_rate = cfi.get("collection_rate", 0.85)
collection_lag_weeks = cfi.get("collection_lag_weeks", 2)
# Weekly expenses
weekly_payroll = cfi.get("weekly_payroll", 0)
weekly_rent = cfi.get("weekly_rent", 0)
weekly_operating = cfi.get("weekly_operating", 0)
weekly_other = cfi.get("weekly_other", 0)
total_weekly_expenses = weekly_payroll + weekly_rent + weekly_operating + weekly_other
# One-time items
one_time_items: List[Dict[str, Any]] = cfi.get("one_time_items", [])
weekly_projections: List[Dict[str, Any]] = []
running_balance = opening_balance
# Revenue pipeline for lagged collections
revenue_pipeline: List[float] = [0.0] * collection_lag_weeks
for week in range(1, weeks + 1):
# Revenue collections (lagged)
revenue_pipeline.append(weekly_revenue)
collections = revenue_pipeline.pop(0) * collection_rate
# One-time items for this week
one_time_inflows = 0.0
one_time_outflows = 0.0
one_time_labels: List[str] = []
for item in one_time_items:
if item.get("week") == week:
amount = item.get("amount", 0)
if amount > 0:
one_time_inflows += amount
else:
one_time_outflows += abs(amount)
one_time_labels.append(item.get("description", ""))
total_inflows = collections + one_time_inflows
total_outflows = total_weekly_expenses + one_time_outflows
net_cash_flow = total_inflows - total_outflows
running_balance += net_cash_flow
weekly_projections.append({
"week": week,
"collections": round(collections, 2),
"one_time_inflows": round(one_time_inflows, 2),
"total_inflows": round(total_inflows, 2),
"payroll": round(weekly_payroll, 2),
"rent": round(weekly_rent, 2),
"operating": round(weekly_operating, 2),
"other_expenses": round(weekly_other, 2),
"one_time_outflows": round(one_time_outflows, 2),
"total_outflows": round(total_outflows, 2),
"net_cash_flow": round(net_cash_flow, 2),
"closing_balance": round(running_balance, 2),
"notes": ", ".join(one_time_labels) if one_time_labels else "",
})
# Summary
total_inflows = sum(w["total_inflows"] for w in weekly_projections)
total_outflows = sum(w["total_outflows"] for w in weekly_projections)
min_balance = min(w["closing_balance"] for w in weekly_projections)
min_balance_week = next(
w["week"]
for w in weekly_projections
if w["closing_balance"] == min_balance
)
return {
"weeks": weeks,
"opening_balance": opening_balance,
"closing_balance": round(running_balance, 2),
"total_inflows": round(total_inflows, 2),
"total_outflows": round(total_outflows, 2),
"net_change": round(total_inflows - total_outflows, 2),
"minimum_balance": round(min_balance, 2),
"minimum_balance_week": min_balance_week,
"cash_runway_weeks": (
round(safe_divide(running_balance, total_weekly_expenses))
if total_weekly_expenses > 0
else None
),
"weekly_projections": weekly_projections,
}
def build_scenario_comparison(
self, scenarios: Optional[List[str]] = None
) -> Dict[str, Any]:
"""Build and compare multiple scenarios."""
if scenarios is None:
scenarios = ["base", "bull", "bear"]
scenario_results: Dict[str, Any] = {}
for scenario in scenarios:
scenario_results[scenario] = self.build_driver_based_forecast(scenario)
# Comparison summary
comparison: List[Dict[str, Any]] = []
for scenario in scenarios:
result = scenario_results[scenario]
comparison.append({
"scenario": scenario,
"total_revenue": result["total_revenue"],
"total_operating_income": result["total_operating_income"],
"growth_rate": result["growth_rate"],
"gross_margin": result["gross_margin"],
"avg_monthly_revenue": result["average_monthly_revenue"],
})
return {
"scenarios": scenario_results,
"comparison": comparison,
}
def run_full_forecast(
self, scenarios: Optional[List[str]] = None
) -> Dict[str, Any]:
"""Run the complete forecast analysis."""
trends = self.analyze_trends()
scenario_comparison = self.build_scenario_comparison(scenarios)
cash_flow = self.build_rolling_cash_flow()
return {
"trend_analysis": trends,
"scenario_comparison": scenario_comparison,
"rolling_cash_flow": cash_flow,
}
def format_text(self, results: Dict[str, Any]) -> str:
"""Format forecast results as human-readable text."""
lines: List[str] = []
lines.append("=" * 70)
lines.append("FINANCIAL FORECAST REPORT")
lines.append("=" * 70)
def fmt_money(val: float) -> str:
if abs(val) >= 1e9:
return f"${val / 1e9:,.2f}B"
if abs(val) >= 1e6:
return f"${val / 1e6:,.2f}M"
if abs(val) >= 1e3:
return f"${val / 1e3:,.1f}K"
return f"${val:,.2f}"
# Trend Analysis
trend = results["trend_analysis"]
if "error" not in trend:
lines.append(f"\n--- TREND ANALYSIS ---")
t = trend["trend"]
lines.append(f" Direction: {t['direction']}")
lines.append(f" R-squared: {t['r_squared']:.4f}")
lines.append(
f" Average Historical Growth: "
f"{trend['average_growth_rate'] * 100:.1f}%"
)
if trend["seasonality_index"]:
lines.append(
f" Seasonality Index (last 4): "
f"{', '.join(f'{s:.2f}' for s in trend['seasonality_index'])}"
)
# Scenario Comparison
comp = results["scenario_comparison"]["comparison"]
lines.append(f"\n--- SCENARIO COMPARISON ---")
lines.append(
f" {'Scenario':<10s} {'Revenue':>14s} {'Op. Income':>14s} "
f"{'Growth':>8s} {'Margin':>8s}"
)
lines.append(" " + "-" * 62)
for c in comp:
lines.append(
f" {c['scenario']:<10s} {fmt_money(c['total_revenue']):>14s} "
f"{fmt_money(c['total_operating_income']):>14s} "
f"{c['growth_rate'] * 100:>7.1f}% "
f"{c['gross_margin'] * 100:>7.1f}%"
)
# Base scenario detail
base = results["scenario_comparison"]["scenarios"].get("base", {})
if base and base.get("forecast_periods"):
lines.append(f"\n--- BASE CASE MONTHLY FORECAST ---")
lines.append(
f" {'Period':>6s} {'Revenue':>12s} {'Gross Profit':>12s} "
f"{'Op. Income':>12s}"
)
lines.append(" " + "-" * 48)
for p in base["forecast_periods"]:
lines.append(
f" {p['period']:>6d} {fmt_money(p['revenue']):>12s} "
f"{fmt_money(p['gross_profit']):>12s} "
f"{fmt_money(p['operating_income']):>12s}"
)
# Cash Flow
cf = results["rolling_cash_flow"]
lines.append(f"\n--- 13-WEEK ROLLING CASH FLOW ---")
lines.append(f" Opening Balance: {fmt_money(cf['opening_balance'])}")
lines.append(f" Closing Balance: {fmt_money(cf['closing_balance'])}")
lines.append(f" Net Change: {fmt_money(cf['net_change'])}")
lines.append(
f" Minimum Balance: {fmt_money(cf['minimum_balance'])} "
f"(Week {cf['minimum_balance_week']})"
)
if cf.get("cash_runway_weeks"):
lines.append(f" Cash Runway: {cf['cash_runway_weeks']:.0f} weeks")
lines.append(f"\n Weekly Detail:")
lines.append(
f" {'Wk':>3s} {'Inflows':>10s} {'Outflows':>10s} "
f"{'Net':>10s} {'Balance':>12s}"
)
lines.append(" " + "-" * 50)
for w in cf["weekly_projections"]:
notes = f" {w['notes']}" if w["notes"] else ""
lines.append(
f" {w['week']:>3d} {fmt_money(w['total_inflows']):>10s} "
f"{fmt_money(w['total_outflows']):>10s} "
f"{fmt_money(w['net_cash_flow']):>10s} "
f"{fmt_money(w['closing_balance']):>12s}{notes}"
)
lines.append("\n" + "=" * 70)
return "\n".join(lines)
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Driver-based revenue forecasting with scenario modeling"
)
parser.add_argument(
"input_file",
help="Path to JSON file with forecast data",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
parser.add_argument(
"--scenarios",
type=str,
default="base,bull,bear",
help="Comma-separated list of scenarios (default: base,bull,bear)",
)
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)
builder = ForecastBuilder(data)
scenarios = [s.strip() for s in args.scenarios.split(",")]
results = builder.run_full_forecast(scenarios)
if args.format == "json":
print(json.dumps(results, indent=2))
else:
print(builder.format_text(results))
if __name__ == "__main__":
main()
#!/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()
Install this Skill
Skills give your AI agent a consistent, structured approach to this task — better output than a one-off prompt.
npx skills add alirezarezvani/claude-skills --skill finance/financial-analyst Community skill by @alirezarezvani. Need a walkthrough? See the install guide →
Works with
Prefer no terminal? Download the ZIP and place it manually.
Details
- Category
- Finance
- License
- MIT
- Author
- @alirezarezvani
- Source
- GitHub →
- Source file
-
show path
finance/financial-analyst/SKILL.md
People who install this also use
CFO Advisor
Financial leadership guidance — budgeting, forecasting, SaaS metrics, investor relations, and financial strategy for growing companies.
@alirezarezvani
CEO Advisor
Executive leadership coaching — strategic decision-making, organizational development, board governance, and navigating high-stakes business challenges.
@alirezarezvani