feat(live): B4前端三层展示+预算表单+账户实况端点(spec§B4)——①新建实盘表单:起始资金升级『实例预算』,默认值=剩余可分配(budget-info,用户改过不跟随),占用率进度条(已分配+本次 vs 现金),超限红条+禁提交,快照不可用警告条(与后端fail-closed双保险)②监控页三层重构:本策略买卖(live_trades归因,今日/全部)/本策略账本+持仓(实例视图,现金/市值/净值语义重命名)/账户实况(新卡:现金/市值/总资产+Σ实例市值+未归因市值=账户市值−Σ实例+实例分解表+QMT全账户持仓表)③列表页:每QMT账号一条占用率横幅,存量超限亮黄条⚠④后端补GET /live/account-snapshot(注册在{aid}前):全局快照+同账号实例最新账本分解+unattributed_mv,快照缺失fresh=False数值None;⑤live.ts补BudgetInfo/AccountSnapshotView类型+updateLive带initial_capital;+3端点测试(分解算术/无快照降级/跨账号过滤);api 164绿;npm run build绿 [vps]
This commit is contained in:
@@ -258,6 +258,57 @@ def get_budget_info(account: str = ""):
|
||||
return _budget_state(db, account)
|
||||
|
||||
|
||||
@router.get("/live/account-snapshot", dependencies=[Depends(verify_token)])
|
||||
def get_account_snapshot_route(account: str = ""):
|
||||
"""账户实况(B4 三层展示第三层):全局快照 + Σ实例市值分解。
|
||||
|
||||
instances = 同 QMT 账号各实盘实例的最新账本(live_balance 实例视图);
|
||||
unattributed = 全账户市值 − Σ实例市值(重建后应≈0,大数=遗留仓/手动仓)。
|
||||
快照缺失时 fresh=False、数值字段 None(前端显示『快照不可用』)。
|
||||
"""
|
||||
from sanguo_live.persistence import (
|
||||
get_account_snapshot, list_accounts, get_last_balance,
|
||||
)
|
||||
|
||||
db = _db_path["path"]
|
||||
if not db:
|
||||
raise HTTPException(400, "db 未配置")
|
||||
acc = (account or "").strip()
|
||||
snap = get_account_snapshot(db, acc) if acc else None
|
||||
from sanguo_live.persistence import get_fresh_account_snapshot
|
||||
fresh = get_fresh_account_snapshot(db, acc) is not None if acc else False
|
||||
|
||||
instances = []
|
||||
for row in list_accounts(db):
|
||||
if (row.get("account") or "").strip() != acc:
|
||||
continue
|
||||
last = get_last_balance(db, row["id"])
|
||||
instances.append({
|
||||
"id": row["id"], "name": row.get("name") or "",
|
||||
"status": row.get("status") or "",
|
||||
"initial_capital": float(row.get("initial_capital") or 0),
|
||||
"cash": (last or {}).get("cash"),
|
||||
"market_value": (last or {}).get("market_value"),
|
||||
"equity": (last or {}).get("total"),
|
||||
})
|
||||
instance_mv = sum(
|
||||
float(i["market_value"] or 0) for i in instances)
|
||||
snap_mv = float(snap.get("market_value")) if snap else None
|
||||
return {
|
||||
"account": acc,
|
||||
"fresh": fresh,
|
||||
"cash": (snap or {}).get("cash"),
|
||||
"market_value": snap_mv,
|
||||
"total": (snap or {}).get("total"),
|
||||
"positions": (snap or {}).get("positions") or [],
|
||||
"updated_at": (snap or {}).get("updated_at"),
|
||||
"instances": instances,
|
||||
"instance_mv_total": instance_mv,
|
||||
"unattributed_mv": (snap_mv - instance_mv
|
||||
if snap_mv is not None else None),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/live/{aid}", dependencies=[Depends(verify_token)])
|
||||
def get_live(aid: int):
|
||||
from sanguo_live.persistence import get_account, get_first_balance, get_last_balance
|
||||
|
||||
Reference in New Issue
Block a user