"""合成反向DCF:固定模型、有限搜索域及代回检查;金额单位百万CNY。""" import json from decimal import Decimal as D, InvalidOperation def finite(value): try: number = D(str(value)) except (InvalidOperation, ValueError, TypeError) as exc: raise ValueError("invalid_numeric_input") from exc if not number.is_finite(): raise ValueError("nonfinite_input") return number MODEL = {"fcf0": "100", "wacc": "0.10", "terminal_g": "0.02", "net_debt": "120", "shares": "100"} def value(growth, model=MODEL): m = {key: finite(val) for key, val in model.items()} h = finite(growth) if not (m["fcf0"] > 0 and m["shares"] > 0 and m["wacc"] > m["terminal_g"] >= 0 and h > -1): raise ValueError("unsupported_model_domain") flows = [m["fcf0"] * (1+h)**t for t in range(1, 4)] explicit = sum(flow/(1+m["wacc"])**t for t, flow in enumerate(flows, 1)) terminal = flows[-1]*(1+m["terminal_g"])/(m["wacc"]-m["terminal_g"])/(1+m["wacc"])**3 return (explicit+terminal-m["net_debt"])/m["shares"], terminal/(explicit+terminal) def solve(price, low="-0.05", high="0.25", model=MODEL): target, lo, hi = map(finite, [price, low, high]) if target <= 0 or not (-1 < lo < hi): raise ValueError("invalid_search_contract") # 正起始FCF、固定正折现分母和终值系数保证此特定模型严格递增。 floor, ceiling = value(lo, model)[0], value(hi, model)[0] if not floor < ceiling: raise ValueError("not_increasing") if not floor <= target <= ceiling: return {"status": "no_solution_in_domain", "endpoint_prices": list(map(str, [floor, ceiling]))} log = [] for iteration in range(1, 101): mid = (lo+hi)/2 computed, terminal_share = value(mid, model) residual = computed-target log.append([iteration, str(mid), str(residual)]) if abs(residual) <= D("0.00000001"): return {"status": "solved", "growth": str(mid), "price": str(computed), "residual": str(residual), "terminal_share": str(terminal_share), "log": log} if residual < 0: lo = mid else: hi = mid return {"status": "not_converged", "log": log} def reject_nonmonotonic_probe(function, low, high): xs = [low+(high-low)*D(i)/20 for i in range(21)] ys = [finite(function(x)) for x in xs] if not all(a < b for a, b in zip(ys, ys[1:])): return "nonmonotonic_probe_rejected" return "probe_passed_not_a_general_proof" result = solve("14.55") assert value("0.10")[0] == D("14.55") assert result["status"] == "solved" and abs(D(result["growth"])-D("0.10")) < D("0.00000001") assert abs(D(result["residual"])) <= D("0.00000001") no_solution = solve("99") assert no_solution["status"] == "no_solution_in_domain" counter = reject_nonmonotonic_probe(lambda h: (h-D("0.05"))**2, D("-0.05"), D("0.25")) assert counter == "nonmonotonic_probe_rejected" for token in ["NaN", "Infinity", "-Infinity"]: for invalid in [lambda: solve(token), lambda: solve("14.55", low=token), lambda: value("0.10", {**MODEL, "wacc": token})]: try: invalid() except ValueError: pass else: raise AssertionError("非有限输入必须阻断") print(json.dumps({"solution": result, "no_solution": no_solution, "counterexample": counter}, sort_keys=True))