"""按券商与固定财年配对预测修正,覆盖变动不冒充上调。""" import json from decimal import Decimal as D year = 2027 old_meta = {"as_of": "2026-10-01", "source": "synthetic-snapshot-old", "year": year, "currency": "CNY", "basis": "diluted"} new_meta = {**old_meta, "as_of": "2026-10-26", "source": "synthetic-snapshot-new"} assert old_meta["as_of"] < new_meta["as_of"] old = [("A", 2027, "1.00"), ("B", 2027, "2.00"), ("C", 2027, "-0.50"), ("D", 2027, "0.01"), ("G", 2027, "1.00"), ("E", 2026, "3.00")] new = [("A", 2027, "1.20"), ("B", 2027, "1.80"), ("C", 2027, "-0.20"), ("D", 2027, "0.02"), ("E", 2027, "2.00"), ("F", 2027, "1.50")] def snapshot(rows): selected = {} for broker, fiscal_year, value in rows: if fiscal_year != year: continue if broker in selected: raise ValueError("duplicate_broker_year") parsed = D(value) if not parsed.is_finite(): raise ValueError("nonfinite_forecast_eps") selected[broker] = parsed return selected before, after = snapshot(old), snapshot(new) paired = [] for broker in sorted(before.keys() & after.keys()): prior, current = before[broker], after[broker] change = current-prior relative = change/prior if prior > D("0.05") else None paired.append({"broker": broker, "year": year, "old": str(prior), "new": str(current), "change": str(change), "relative": str(relative) if relative is not None else None, "relative_status": "valid" if relative is not None else "negative_or_near_zero_base"}) entered, exited = sorted(after.keys()-before.keys()), sorted(before.keys()-after.keys()) assert entered == ["E", "F"] and exited == ["G"] assert [r["relative"] for r in paired] == ["0.2", "-0.1", None, None] assert [r["change"] for r in paired] == ["0.20", "-0.20", "0.30", "0.01"] mean_change = sum(D(r["change"]) for r in paired)/len(paired) mean_magnitude = sum(abs(D(r["change"])) for r in paired)/len(paired) up_pairs = sum(D(r["change"]) > 0 for r in paired) down_pairs = sum(D(r["change"]) < 0 for r in paired) assert mean_change == D("0.0775") and len(paired) == 4 assert mean_magnitude == D("0.1775") and (up_pairs, down_pairs) == (3, 1) assert snapshot(list(reversed(old))) == before for token in ["NaN", "Infinity", "-Infinity"]: try: snapshot([("A", 2027, token)]) except ValueError as exc: assert str(exc) == "nonfinite_forecast_eps" else: raise AssertionError("非有限EPS不能进入预测快照") print(json.dumps({"paired": paired, "entered_coverage": entered, "exited_coverage": exited, "snapshots": [old_meta, new_meta], "mean_signed_eps_change": str(mean_change), "mean_absolute_change_magnitude": str(mean_magnitude), "up_pairs": up_pairs, "down_pairs": down_pairs, "eligible_percentage_pairs": sum(r["relative"] is not None for r in paired)}, sort_keys=True))