Add real-time data dashboard with account, positions, keys, and closes.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
# 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
# 详见 LICENSE.zh-CN.txt 与 docs/软件购买与使用协议.md
|
||||
|
||||
"""数据看板:账户、关键位、平仓记录聚合。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
_TZ = ZoneInfo("Asia/Shanghai")
|
||||
|
||||
|
||||
def _direction_label(direction: str) -> str:
|
||||
return "做多" if (direction or "").strip().lower() == "long" else "做空"
|
||||
|
||||
|
||||
def build_dashboard_payload(
|
||||
*,
|
||||
get_db: Callable,
|
||||
get_setting: Callable[[str, str], str],
|
||||
fetch_price: Callable[[str, str, str], Optional[float]],
|
||||
closes_limit: int = 40,
|
||||
sync_ctp_trades: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
from trading_context import get_account_capital, get_trading_mode, trading_mode_label
|
||||
from vnpy_bridge import ctp_account_margin_used, ctp_status, get_bridge
|
||||
|
||||
mode = get_trading_mode(get_setting)
|
||||
ctp_st = dict(ctp_status(mode) or {})
|
||||
conn = get_db()
|
||||
try:
|
||||
capital = float(get_account_capital(conn, get_setting) or 0)
|
||||
equity = capital
|
||||
available: Optional[float] = None
|
||||
margin_used: Optional[float] = None
|
||||
|
||||
if ctp_st.get("connected"):
|
||||
if sync_ctp_trades:
|
||||
try:
|
||||
from ctp_trade_sync import sync_trade_logs_from_ctp
|
||||
|
||||
sync_trade_logs_from_ctp(
|
||||
conn, mode, capital=capital, trading_mode=mode,
|
||||
)
|
||||
conn.commit()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
b = get_bridge()
|
||||
if b.connected_mode == mode and b.ping():
|
||||
acc = b.get_account() or {}
|
||||
else:
|
||||
acc = {}
|
||||
balance = float(acc.get("balance") or 0)
|
||||
if balance > 0:
|
||||
equity = balance
|
||||
avail = acc.get("available")
|
||||
if avail is not None:
|
||||
available = round(float(avail), 2)
|
||||
mu = ctp_account_margin_used(mode)
|
||||
if mu is not None and mu > 0:
|
||||
margin_used = round(float(mu), 2)
|
||||
elif available is not None and equity > 0:
|
||||
margin_used = round(max(0.0, equity - available), 2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
key_rows = conn.execute(
|
||||
"""
|
||||
SELECT id, symbol, symbol_name, market_code, sina_code,
|
||||
monitor_type, direction, upper, lower, trade_mode,
|
||||
bar_period, trailing_be
|
||||
FROM key_monitors
|
||||
WHERE status='active' OR status IS NULL
|
||||
ORDER BY id DESC
|
||||
"""
|
||||
).fetchall()
|
||||
keys: list[dict[str, Any]] = []
|
||||
for r in key_rows:
|
||||
sym = r["symbol"]
|
||||
market = r["market_code"] or ""
|
||||
sina = r["sina_code"] or ""
|
||||
upper = float(r["upper"] or 0)
|
||||
lower = float(r["lower"] or 0)
|
||||
price = fetch_price(sym, market, sina)
|
||||
dist_upper = dist_lower = None
|
||||
if price is not None:
|
||||
dist_upper = round(upper - float(price), 2)
|
||||
dist_lower = round(float(price) - lower, 2)
|
||||
mtype = r["monitor_type"] or ""
|
||||
keys.append({
|
||||
"id": r["id"],
|
||||
"symbol": sym,
|
||||
"symbol_name": r["symbol_name"] or sym,
|
||||
"monitor_type": mtype,
|
||||
"direction": r["direction"] or "",
|
||||
"direction_label": _direction_label(r["direction"] or "long")
|
||||
if r["direction"] else "",
|
||||
"upper": upper,
|
||||
"lower": lower,
|
||||
"trade_mode": r["trade_mode"] or "",
|
||||
"bar_period": r["bar_period"] or "5m",
|
||||
"trailing_be": bool(r["trailing_be"]),
|
||||
"price": price,
|
||||
"dist_upper": dist_upper,
|
||||
"dist_lower": dist_lower,
|
||||
})
|
||||
|
||||
close_rows = conn.execute(
|
||||
"""
|
||||
SELECT id, symbol, symbol_name, direction, lots,
|
||||
entry_price, close_price, pnl, pnl_net, fee,
|
||||
close_time, result, source
|
||||
FROM trade_logs
|
||||
ORDER BY id DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
(max(1, min(200, closes_limit)),),
|
||||
).fetchall()
|
||||
closes: list[dict[str, Any]] = []
|
||||
for r in close_rows:
|
||||
closes.append({
|
||||
"id": r["id"],
|
||||
"symbol": r["symbol_name"] or r["symbol"],
|
||||
"symbol_code": r["symbol"],
|
||||
"direction": r["direction"] or "long",
|
||||
"direction_label": _direction_label(r["direction"] or "long"),
|
||||
"lots": float(r["lots"] or 0),
|
||||
"entry_price": float(r["entry_price"] or 0),
|
||||
"close_price": float(r["close_price"] or 0),
|
||||
"pnl": float(r["pnl"] or 0) if r["pnl"] is not None else None,
|
||||
"pnl_net": float(r["pnl_net"] or 0) if r["pnl_net"] is not None else None,
|
||||
"fee": float(r["fee"] or 0) if r["fee"] is not None else None,
|
||||
"close_time": (r["close_time"] or "")[:16].replace("T", " "),
|
||||
"result": r["result"] or "",
|
||||
"source": r["source"] or "",
|
||||
})
|
||||
|
||||
now_iso = datetime.now(_TZ).strftime("%Y-%m-%d %H:%M:%S")
|
||||
return {
|
||||
"ok": True,
|
||||
"updated_at": now_iso,
|
||||
"trading_mode_label": trading_mode_label(get_setting),
|
||||
"ctp_status": ctp_st,
|
||||
"account": {
|
||||
"equity": round(equity, 2),
|
||||
"margin_used": margin_used,
|
||||
"available": available,
|
||||
"capital_fallback": round(capital, 2),
|
||||
},
|
||||
"keys": keys,
|
||||
"closes": closes,
|
||||
}
|
||||
finally:
|
||||
conn.close()
|
||||
Reference in New Issue
Block a user