"""R633:带时点TWR与日期MWR;限定二次多解反例的解析诊断。""" from datetime import date from math import isclose, prod, sqrt def twr(segments): if any(start <= 0 or end < 0 for start, end in segments): raise ValueError("nonpositive base or negative ending value") return prod(end / start for start, end in segments) - 1 def xnpv(rate, flows): if rate <= -1: raise ValueError("rate must exceed -1") origin = flows[0][0] return sum(amount / (1 + rate) ** ((day-origin).days / 365) for day, amount in flows) def conventional_mwr(flows): if not flows: return "invalid_empty", None if not any(amount for _, amount in flows): return "undefined_zero_flows", None # 日期版求解器先净额归并同一天;没有有效投资期间不报年化率。 by_day = {} for day, amount in flows: by_day[day] = by_day.get(day, 0) + amount if len(by_day) < 2: return "undefined_zero_period", None flows = sorted((day, amount) for day, amount in by_day.items() if amount) if not flows: return "undefined_zero_flows", None if len(flows) < 2: return "undefined_zero_period", None # 只承诺负投入在先、非负回收在后的常规模式。 amounts = [amount for _, amount in flows if amount] signs = [amount > 0 for amount in amounts] if not any(signs) or all(signs): return "no_root_same_sign", None changes = sum(a != b for a, b in zip(signs, signs[1:])) if changes != 1 or signs[0]: return "nonconventional_needs_root_analysis", None lo, hi = -0.9999, 10.0 if xnpv(lo, flows) * xnpv(hi, flows) > 0: return "not_bracketed", None for _ in range(180): mid = (lo + hi) / 2 if xnpv(mid, flows) > 0: lo = mid else: hi = mid rate = (lo + hi) / 2 assert abs(xnpv(rate, flows)) < 1e-6 return "unique_conventional", rate def run(): start, middle, end = date(2023, 1, 1), date(2024, 1, 1), date(2024, 12, 31) assert (middle-start).days == (end-middle).days == 365 # t=1入金前110,入金90后200,t=2期末180;各相隔365天。 opening, before, deposit, terminal = 100, 110, 90, 180 after = before + deposit result = twr([(opening, before), (after, terminal)]) status, annual = conventional_mwr([(start, -opening), (middle, -deposit), (end, terminal)]) net_contributions = opening + deposit gain = terminal - net_contributions assert opening + deposit + gain == terminal assert isclose(result, -0.01, abs_tol=1e-12) assert status == "unique_conventional" assert isclose(annual, (-90 + sqrt(80100)) / 200 - 1, abs_tol=1e-12) # 非常规流:-100,+230,-132,令x=1+r,得到100x²-230x+132=0。 unusual = [(start, -100), (middle, 230), (end, -132)] roots = sorted([(230-sqrt(100))/200-1, (230+sqrt(100))/200-1]) assert all(abs(xnpv(rate, unusual)) < 1e-8 for rate in roots) assert conventional_mwr(unusual)[0] == "nonconventional_needs_root_analysis" no_root = conventional_mwr([(start, -100), (end, -10)])[0] assert no_root == "no_root_same_sign" assert conventional_mwr([])[0] == "invalid_empty" assert conventional_mwr([(start, 0)])[0] == "undefined_zero_flows" assert conventional_mwr([(start, -100), (start, 100)])[0] == "undefined_zero_period" assert conventional_mwr([(start, -100), (start, 100), (end, -10), (end, 10)])[0] == "undefined_zero_flows" print(f"subperiods=10.0000%,-10.0000% TWR={result:.4%}") print(f"gain={gain} ending={terminal} net_contributions={net_contributions} MWR_annual={annual:.6%}") print(f"MWR_two_year_equivalent={(1+annual)**2-1:.6%}") print("nonconventional_roots=10.000000%,20.000000%") print("same_sign_status=" + no_root) print("input_statuses=invalid_empty,undefined_zero_flows,undefined_zero_period") try: twr([(0, 10)]) except ValueError: print("zero_base_TWR=rejected") else: raise AssertionError("zero base accepted") if __name__ == "__main__": run()