53 lines
1.9 KiB
Python
53 lines
1.9 KiB
Python
|
|
"""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",
|
||
|
|
}
|
||
|
|
)
|