"""Read-only baseline comparison over explicit local Alpha and PnL snapshots.""" import math from fastapi import HTTPException from ..models import Alpha, Pnl from .serialization import encode_snapshot as jsonable_encoder async def compare(db, alpha_ids): if len(set(alpha_ids)) != len(alpha_ids): raise HTTPException(422, "比较项不能重复") rows, by_id = [], {} for alpha_id in alpha_ids: alpha = await db.get(Alpha, alpha_id) if not alpha: raise HTTPException(404, f"Alpha {alpha_id} 尚未同步") pnl = await db.get(Pnl, alpha_id) by_id[alpha_id] = ( { p["date"][:10]: p["value"] for p in pnl.points if type(p.get("value")) in (int, float) and math.isfinite(p["value"]) } if pnl else {} ) rows.append( { "alpha_id": alpha.id, "expression": alpha.expression, "settings": alpha.settings, "metrics": alpha.is_metrics, "observed_at": alpha.synced_at, "pnl_fetched_at": pnl.fetched_at if pnl else None, } ) common = sorted(set.intersection(*(set(points) for points in by_id.values()))) for row in rows: points = by_id[row["alpha_id"]] row["pnl"] = [{"date": date, "value": points[date] - points[common[0]]} for date in common] return jsonable_encoder( { "baseline_alpha_id": alpha_ids[0], "items": rows, "common_dates": common, "window": {"from": common[0], "to": common[-1]} if common else None, "different_settings": any(row["settings"] != rows[0]["settings"] for row in rows[1:]), "note": "PnL 按共同日期展示并从窗口起点归零;缓存缺失时请在 Alpha 详情获取 PnL", } )