← Files Institutional Equity AnalystARCHIVED FILE

scripts/finance_core.py

20.8 KB · Oct 3, 2026 · 06:37 UTC

↓ Download file

#!/usr/bin/env python3
"""Standard-library deterministic calculations for public-equity research.

Functions never fetch data. Callers must provide source-traceable, unit-consistent
inputs and preserve the returned reconciliation diagnostics.
"""
from __future__ import annotations

import math
from statistics import mean, pstdev


def _require_rate(value: float, name: str, allow_negative: bool = False) -> None:
    lower = -1.0 if allow_negative else 0.0
    if not lower <= value <= 1.0:
        raise ValueError(f"{name} must be between {lower} and 1")


def _safe_div(numerator: float, denominator: float, name: str) -> float:
    if denominator == 0:
        raise ValueError(f"{name} denominator cannot be zero")
    return numerator / denominator


# Accounting -----------------------------------------------------------------

def three_statement_reconcile(assets: float, liabilities: float, equity: float,
                              beginning_cash: float, ending_cash: float,
                              cfo: float, cfi: float, cff: float,
                              fx_other: float = 0.0, tolerance: float = 1e-6) -> dict:
    balance_residual = assets - liabilities - equity
    cash_residual = ending_cash - beginning_cash - cfo - cfi - cff - fx_other
    return {
        "balance_sheet_residual": balance_residual,
        "cash_flow_residual": cash_residual,
        "balance_sheet_pass": abs(balance_residual) <= tolerance,
        "cash_flow_pass": abs(cash_residual) <= tolerance,
        "pass": abs(balance_residual) <= tolerance and abs(cash_residual) <= tolerance,
    }


def cash_flow_reconcile(net_income: float, noncash_items: float,
                        change_working_capital: float, other_operating: float,
                        reported_cfo: float, tolerance: float = 1e-6) -> dict:
    calculated = net_income + noncash_items - change_working_capital + other_operating
    residual = reported_cfo - calculated
    return {"calculated_cfo": calculated, "reported_cfo": reported_cfo,
            "residual": residual, "pass": abs(residual) <= tolerance}


def share_count_bridge(beginning_shares: float, issuance: float = 0.0,
                       sbc_issuance: float = 0.0, option_dilution: float = 0.0,
                       convertible_dilution: float = 0.0, repurchases: float = 0.0,
                       ending_shares: float | None = None, tolerance: float = 1e-6) -> dict:
    calculated = beginning_shares + issuance + sbc_issuance + option_dilution + convertible_dilution - repurchases
    result = {"calculated_ending_shares": calculated,
              "gross_dilution": issuance + sbc_issuance + option_dilution + convertible_dilution,
              "repurchases": repurchases}
    if ending_shares is not None:
        result.update({"reported_ending_shares": ending_shares,
                       "residual": ending_shares - calculated,
                       "pass": abs(ending_shares - calculated) <= tolerance})
    return result


def working_capital_metrics(accounts_receivable: float, inventory: float,
                            accounts_payable: float, revenue: float, cogs: float,
                            days: float = 365.0) -> dict:
    dso = _safe_div(accounts_receivable * days, revenue, "DSO")
    dio = _safe_div(inventory * days, cogs, "DIO")
    dpo = _safe_div(accounts_payable * days, cogs, "DPO")
    return {"dso": dso, "dio": dio, "dpo": dpo, "cash_conversion_cycle": dso + dio - dpo}


def lease_normalization(rent_expense: float, imputed_interest: float,
                        lease_liability: float, reported_ebitda: float,
                        reported_debt: float) -> dict:
    if rent_expense < 0 or imputed_interest < 0 or lease_liability < 0:
        raise ValueError("lease inputs cannot be negative")
    depreciation_component = max(rent_expense - imputed_interest, 0.0)
    return {"lease_adjusted_ebitda": reported_ebitda + rent_expense,
            "lease_adjusted_debt": reported_debt + lease_liability,
            "imputed_interest": imputed_interest,
            "imputed_depreciation": depreciation_component}


def sbc_dilution(beginning_shares: float, ending_shares: float, repurchased_shares: float,
                 sbc_expense: float, revenue: float, free_cash_flow: float) -> dict:
    gross_issuance = ending_shares - beginning_shares + repurchased_shares
    return {"net_share_change": ending_shares - beginning_shares,
            "gross_issuance_estimate": gross_issuance,
            "sbc_percent_revenue": _safe_div(sbc_expense, revenue, "SBC/revenue"),
            "fcf_after_sbc_repurchase": free_cash_flow - sbc_expense}


def tax_normalization(pre_tax_income: float, reported_tax: float,
                      discrete_items: float = 0.0, statutory_rate: float | None = None) -> dict:
    normalized_tax = reported_tax - discrete_items
    result = {"reported_effective_rate": _safe_div(reported_tax, pre_tax_income, "reported tax rate"),
              "normalized_tax": normalized_tax,
              "normalized_effective_rate": _safe_div(normalized_tax, pre_tax_income, "normalized tax rate")}
    if statutory_rate is not None:
        _require_rate(statutory_rate, "statutory_rate")
        result["normalized_vs_statutory"] = result["normalized_effective_rate"] - statutory_rate
    return result


# Valuation ------------------------------------------------------------------

def terminal_value_gordon(final_cash_flow: float, discount_rate: float, perpetual_growth: float) -> float:
    _require_rate(discount_rate, "discount_rate")
    _require_rate(perpetual_growth, "perpetual_growth", allow_negative=True)
    if discount_rate <= perpetual_growth:
        raise ValueError("discount_rate must exceed perpetual_growth")
    return final_cash_flow * (1 + perpetual_growth) / (discount_rate - perpetual_growth)


def dcf(cash_flows: list[float], discount_rate: float, perpetual_growth: float,
        net_debt: float = 0.0, non_operating_assets: float = 0.0,
        minority_interest: float = 0.0, preferred_equity: float = 0.0,
        diluted_shares: float | None = None) -> dict:
    if not cash_flows:
        raise ValueError("cash_flows cannot be empty")
    _require_rate(discount_rate, "discount_rate")
    pv_explicit = sum(cf / (1 + discount_rate) ** year for year, cf in enumerate(cash_flows, 1))
    terminal = terminal_value_gordon(cash_flows[-1], discount_rate, perpetual_growth)
    pv_terminal = terminal / (1 + discount_rate) ** len(cash_flows)
    enterprise_value = pv_explicit + pv_terminal
    equity_value = enterprise_value - net_debt + non_operating_assets - minority_interest - preferred_equity
    result = {"pv_explicit": pv_explicit, "terminal_value": terminal,
              "pv_terminal": pv_terminal, "terminal_value_weight": pv_terminal / enterprise_value,
              "enterprise_value": enterprise_value, "equity_value": equity_value}
    if diluted_shares is not None:
        if diluted_shares <= 0:
            raise ValueError("diluted_shares must be positive")
        result["value_per_share"] = equity_value / diluted_shares
    return result


def reverse_dcf_implied_growth(enterprise_value: float, starting_cash_flow: float,
                               years: int, discount_rate: float, perpetual_growth: float,
                               lower: float = -0.50, upper: float = 1.00,
                               iterations: int = 200) -> dict:
    if enterprise_value <= 0 or years <= 0:
        raise ValueError("enterprise_value and years must be positive")
    def value(growth: float) -> float:
        flows = [starting_cash_flow * (1 + growth) ** year for year in range(1, years + 1)]
        return dcf(flows, discount_rate, perpetual_growth)["enterprise_value"]
    lo, hi = lower, upper
    if not (value(lo) <= enterprise_value <= value(hi)):
        raise ValueError("implied growth not bracketed by lower and upper")
    for _ in range(iterations):
        mid = (lo + hi) / 2
        if value(mid) < enterprise_value:
            lo = mid
        else:
            hi = mid
    growth = (lo + hi) / 2
    return {"implied_growth": growth, "reconstructed_enterprise_value": value(growth),
            "target_enterprise_value": enterprise_value}


def multiples_valuation(metric: float, multiple: float, net_debt: float = 0.0,
                        diluted_shares: float | None = None, is_enterprise_multiple: bool = True) -> dict:
    if multiple < 0:
        raise ValueError("multiple cannot be negative")
    headline = metric * multiple
    enterprise_value = headline if is_enterprise_multiple else None
    equity_value = headline - net_debt if is_enterprise_multiple else headline
    result = {"headline_value": headline, "enterprise_value": enterprise_value, "equity_value": equity_value}
    if diluted_shares is not None:
        if diluted_shares <= 0:
            raise ValueError("diluted_shares must be positive")
        result["value_per_share"] = equity_value / diluted_shares
    return result


def sum_of_parts(parts: list[dict], corporate_adjustments: float = 0.0,
                 net_debt: float = 0.0, diluted_shares: float | None = None) -> dict:
    if not parts:
        raise ValueError("parts cannot be empty")
    detail = []
    for part in parts:
        name, metric, multiple = part["name"], float(part["metric"]), float(part["multiple"])
        detail.append({"name": name, "value": metric * multiple})
    enterprise_value = sum(p["value"] for p in detail) + corporate_adjustments
    equity_value = enterprise_value - net_debt
    result = {"parts": detail, "enterprise_value": enterprise_value, "equity_value": equity_value}
    if diluted_shares is not None:
        if diluted_shares <= 0:
            raise ValueError("diluted_shares must be positive")
        result["value_per_share"] = equity_value / diluted_shares
    return result


def residual_income(book_value: float, forecast_roe: list[float], cost_of_equity: float,
                    payout_ratio: float = 0.0, terminal_roe: float | None = None) -> dict:
    _require_rate(cost_of_equity, "cost_of_equity")
    _require_rate(payout_ratio, "payout_ratio")
    if book_value <= 0 or not forecast_roe:
        raise ValueError("positive book_value and forecast_roe are required")
    pv_residual = 0.0
    current_book = book_value
    schedule = []
    for year, roe in enumerate(forecast_roe, 1):
        earnings = current_book * roe
        residual = earnings - current_book * cost_of_equity
        pv = residual / (1 + cost_of_equity) ** year
        schedule.append({"year": year, "beginning_book": current_book, "earnings": earnings, "residual_income": residual, "pv": pv})
        pv_residual += pv
        current_book += earnings * (1 - payout_ratio)
    terminal_value = 0.0
    if terminal_roe is not None:
        terminal_residual = current_book * (terminal_roe - cost_of_equity)
        terminal_value = terminal_residual / cost_of_equity / (1 + cost_of_equity) ** len(forecast_roe)
    return {"book_value": book_value, "pv_residual_income": pv_residual,
            "pv_terminal_residual_income": terminal_value,
            "equity_value": book_value + pv_residual + terminal_value, "schedule": schedule}


def scenario_valuation(values: list[float], probabilities: list[float], tolerance: float = 1e-9) -> dict:
    if not values or len(values) != len(probabilities):
        raise ValueError("values and probabilities must have equal nonzero length")
    if any(p < 0 for p in probabilities):
        raise ValueError("probabilities cannot be negative")
    if abs(sum(probabilities) - 1.0) > tolerance:
        raise ValueError("probabilities must sum to 1")
    expected = sum(v * p for v, p in zip(values, probabilities))
    variance = sum(p * (v - expected) ** 2 for v, p in zip(values, probabilities))
    return {"expected_value": expected, "standard_deviation": math.sqrt(variance),
            "minimum": min(values), "maximum": max(values)}


# Forensics ------------------------------------------------------------------

def accruals(net_income: float, cfo: float, average_assets: float) -> dict:
    accrual_amount = net_income - cfo
    return {"accrual_amount": accrual_amount,
            "accrual_ratio": _safe_div(accrual_amount, average_assets, "accrual ratio")}


def beneish_m_score(dsri: float, gmi: float, aqi: float, sgi: float,
                    depi: float, sgai: float, lvgi: float, tata: float) -> dict:
    score = -4.84 + 0.920 * dsri + 0.528 * gmi + 0.404 * aqi + 0.892 * sgi + 0.115 * depi - 0.172 * sgai + 4.679 * tata - 0.327 * lvgi
    return {"m_score": score, "screen_flag": score > -1.78,
            "interpretation": "diagnostic screen only; not evidence of fraud"}


def cash_conversion(net_income: float, cfo: float, free_cash_flow: float | None = None) -> dict:
    result = {"cfo_to_net_income": _safe_div(cfo, net_income, "CFO/net income")}
    if free_cash_flow is not None:
        result["fcf_to_net_income"] = _safe_div(free_cash_flow, net_income, "FCF/net income")
    return result


def working_capital_anomalies(revenue_growth: float, receivables_growth: float,
                              inventory_growth: float, payables_growth: float,
                              threshold: float = 0.10) -> dict:
    spreads = {"receivables_vs_revenue": receivables_growth - revenue_growth,
               "inventory_vs_revenue": inventory_growth - revenue_growth,
               "payables_vs_revenue": payables_growth - revenue_growth}
    return {"spreads": spreads, "flags": [name for name, value in spreads.items() if abs(value) >= threshold]}


def disclosure_drift(old_text: str, new_text: str) -> dict:
    old_tokens = set(old_text.lower().split())
    new_tokens = set(new_text.lower().split())
    union = old_tokens | new_tokens
    similarity = 1.0 if not union else len(old_tokens & new_tokens) / len(union)
    return {"jaccard_similarity": similarity,
            "added_terms": sorted(new_tokens - old_tokens),
            "removed_terms": sorted(old_tokens - new_tokens),
            "material_review_required": similarity < 0.80}


# Risk -----------------------------------------------------------------------

def liquidity_runway(cash: float, committed_undrawn: float, restricted_cash: float,
                      mandatory_uses: float, periodic_cash_burn: float | None = None) -> dict:
    usable = cash + committed_undrawn - restricted_cash - mandatory_uses
    result = {"usable_liquidity": usable}
    if periodic_cash_burn is not None:
        if periodic_cash_burn <= 0:
            raise ValueError("periodic_cash_burn must be positive")
        result["runway_periods"] = usable / periodic_cash_burn
    return result


def debt_maturity_schedule(maturities: dict[str, float], annual_free_cash_flow: float,
                           starting_cash: float = 0.0) -> dict:
    cumulative = 0.0
    detail = []
    available = starting_cash
    for period in sorted(maturities):
        amount = float(maturities[period])
        cumulative += amount
        available += annual_free_cash_flow
        gap = available - amount
        detail.append({"period": period, "maturity": amount, "available_before_refinancing": available, "surplus_or_gap": gap})
        available = max(gap, 0.0)
    return {"total_maturities": cumulative, "schedule": detail,
            "minimum_surplus_or_gap": min((x["surplus_or_gap"] for x in detail), default=0.0)}


def covenant_headroom(metric: float, covenant_limit: float, direction: str = "maximum") -> dict:
    if direction not in {"maximum", "minimum"}:
        raise ValueError("direction must be maximum or minimum")
    headroom = covenant_limit - metric if direction == "maximum" else metric - covenant_limit
    denominator = abs(covenant_limit) if covenant_limit else 1.0
    return {"headroom": headroom, "headroom_percent_of_limit": headroom / denominator, "pass": headroom >= 0}


def refinancing_stress(debt_to_refinance: float, current_rate: float, stressed_rate: float,
                       stressed_ebitda: float, existing_interest: float = 0.0) -> dict:
    incremental_interest = debt_to_refinance * (stressed_rate - current_rate)
    total_interest = existing_interest + debt_to_refinance * stressed_rate
    return {"incremental_interest": incremental_interest, "stressed_interest": total_interest,
            "stressed_interest_coverage": _safe_div(stressed_ebitda, total_interest, "interest coverage")}


def dilution_stress(cash_shortfall: float, issue_price: float, existing_shares: float,
                    discount: float = 0.0) -> dict:
    _require_rate(discount, "discount")
    effective_price = issue_price * (1 - discount)
    if effective_price <= 0 or existing_shares <= 0:
        raise ValueError("effective issue price and existing shares must be positive")
    new_shares = cash_shortfall / effective_price
    total = existing_shares + new_shares
    return {"effective_issue_price": effective_price, "new_shares": new_shares,
            "post_money_shares": total, "new_holder_ownership": new_shares / total,
            "existing_holder_dilution": new_shares / total}


# Forecasts, management, confidence, and research budget ---------------------

def forecast_error(forecast: float, actual: float, attribution: dict[str, float], tolerance: float = 1e-6) -> dict:
    error = actual - forecast
    attributed = sum(attribution.values())
    residual = error - attributed
    return {"forecast": forecast, "actual": actual, "error": error,
            "attribution": attribution, "attributed_total": attributed,
            "residual": residual, "pass": abs(residual) <= tolerance}


def forecast_bias(errors: list[float]) -> dict:
    if not errors:
        raise ValueError("errors cannot be empty")
    return {"mean_error": mean(errors), "mean_absolute_error": mean(abs(x) for x in errors),
            "root_mean_square_error": math.sqrt(mean(x * x for x in errors)),
            "error_standard_deviation": pstdev(errors)}


def management_credibility(outcomes: list[dict]) -> dict:
    weights = {"met": 1.0, "partially_met": 0.5, "missed": 0.0, "withdrawn": 0.0}
    scored = [o for o in outcomes if o.get("outcome") in weights]
    if not scored:
        return {"score": None, "scored_predictions": 0, "not_scored": len(outcomes)}
    total_weight = sum(float(o.get("materiality_weight", 1.0)) for o in scored)
    score = sum(weights[o["outcome"]] * float(o.get("materiality_weight", 1.0)) for o in scored) / total_weight
    return {"score": score * 100, "scored_predictions": len(scored), "not_scored": len(outcomes) - len(scored)}


CONFIDENCE_WEIGHTS = {"evidence_coverage": 0.25, "source_quality": 0.20,
                      "historical_consistency": 0.15, "model_reconciliation": 0.15,
                      "contradiction_resolution": 0.10, "forecast_uncertainty": 0.10,
                      "evidence_freshness": 0.05}


def research_confidence(components: dict[str, float], mandatory_gate_open: bool = False,
                        critical_input_unprovenanced: bool = False,
                        point_in_time_breach: bool = False) -> dict:
    missing = set(CONFIDENCE_WEIGHTS) - set(components)
    if missing:
        raise ValueError(f"missing confidence components: {sorted(missing)}")
    for name, score in components.items():
        if not 0 <= score <= 100:
            raise ValueError(f"{name} must be 0-100")
    raw = sum(components[name] * weight for name, weight in CONFIDENCE_WEIGHTS.items())
    cap = mandatory_gate_open or critical_input_unprovenanced or point_in_time_breach
    final = min(raw, 59.0) if cap else raw
    return {"raw_score": raw, "weighted_score": final, "gate_cap_applied": cap,
            "status": "RESEARCH_INCOMPLETE" if cap else "SCORED"}


def evidence_freshness(source_age_days: float, shelf_life_days: float) -> dict:
    if source_age_days < 0 or shelf_life_days <= 0:
        raise ValueError("source age must be nonnegative and shelf life positive")
    ratio = source_age_days / shelf_life_days
    score = 100.0 if ratio <= 0.25 else max(0.0, 100.0 * (1.0 - (ratio - 0.25) / 1.25))
    return {"age_to_shelf_life": ratio, "freshness_score": score, "stale": source_age_days > shelf_life_days}


def evoi_priority(decision_impact: float, change_probability: float,
                  uncertainty_reduction: float, dependency_multiplier: float,
                  cost_hours: float) -> dict:
    for name, value in {"decision_impact": decision_impact, "change_probability": change_probability,
                        "uncertainty_reduction": uncertainty_reduction}.items():
        if not 0 <= value <= 1:
            raise ValueError(f"{name} must be 0-1")
    if not 1 <= dependency_multiplier <= 2 or cost_hours <= 0:
        raise ValueError("dependency_multiplier must be 1-2 and cost_hours positive")
    score = decision_impact * change_probability * uncertainty_reduction * dependency_multiplier / max(cost_hours, 0.25)
    return {"priority_score": score,
            "inputs": {"decision_impact": decision_impact, "change_probability": change_probability,
                       "uncertainty_reduction": uncertainty_reduction, "dependency_multiplier": dependency_multiplier,
                       "cost_hours": cost_hours}}

SHA-256: 023802a3afe2669e37d8ab512ef32b64492641d9596164012f18ad85ed0acb48