中控汇总:Bridge 增加窗口化期权统计接口。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-03 02:59:19 +08:00
parent 6d5b71eb3d
commit a71042c3e1
2 changed files with 128 additions and 0 deletions
+53
View File
@@ -204,6 +204,59 @@ def register_hub_bridge_routes(app: Flask, app_module: Any) -> None:
return jsonify({"ok": False, "msg": str(e), "version": 1, "enabled": True}), 500 return jsonify({"ok": False, "msg": str(e), "version": 1, "enabled": True}), 500
return jsonify(_enrich_hub_protocol(snap, app_module=app_module)) return jsonify(_enrich_hub_protocol(snap, app_module=app_module))
@app.route("/api/hub/options/stats")
def api_hub_options_stats():
"""中控汇总用:窗口化已实现盈亏统计(不含浮盈进净盈亏主指标以外的展示字段仍附带)."""
if not hub_bridge_token_ok(request.headers):
return jsonify({"ok": False, "msg": "未授权", "version": 1}), 401
hub_ctx = app.config.get("HUB_CTX") or {}
cfg = hub_ctx.get("options_cfg")
if not isinstance(cfg, dict):
return jsonify({"ok": False, "msg": "期权未就绪", "version": 1}), 503
from lib.options.options_history_lib import load_options_history
from lib.options.options_margin_mode_lib import is_coin_margin_mode
from lib.options.options_positions_lib import sum_options_net_pnl_usdc
from lib.options.options_stats_lib import (
compute_options_stats_for_window,
history_pnl_to_usdt,
normalize_stats_window,
)
window = normalize_stats_window(request.args.get("window") or "7d")
ex = cfg.get("exchange_options")
if ex is None:
return jsonify({"ok": False, "msg": "交易所未就绪", "version": 1}), 503
try:
raw_live = cfg["fetch_option_positions"](ex)
if raw_live is None:
return jsonify({"ok": False, "msg": "获取期权持仓失败", "version": 1})
history = load_options_history(ex, cfg)
stats = compute_options_stats_for_window(history_pnl_to_usdt(history, ex), window)
open_float = sum_options_net_pnl_usdc(cfg, ex, raw_live)
net_realized = stats.get("net_realized_pnl")
try:
net_f = float(net_realized) if net_realized is not None else 0.0
except (TypeError, ValueError):
net_f = 0.0
total_pnl = None
if open_float is not None:
total_pnl = round(net_f + float(open_float), 4)
elif stats.get("total_closed"):
total_pnl = round(net_f, 4)
pnl_unit = "U" if is_coin_margin_mode() else "USDC"
return jsonify(
{
"ok": True,
"version": 1,
"pnl_unit": pnl_unit,
"open_float_pnl": open_float,
"total_pnl": total_pnl,
**stats,
}
)
except Exception as e:
return jsonify({"ok": False, "msg": str(e) or "统计失败", "version": 1}), 500
@app.route("/api/hub/options/close", methods=["POST"]) @app.route("/api/hub/options/close", methods=["POST"])
def api_hub_options_close(): def api_hub_options_close():
"""中控下发全平指令:客户端本地买一限价执行;无效买一拒绝.""" """中控下发全平指令:客户端本地买一限价执行;无效买一拒绝."""
+75
View File
@@ -185,6 +185,81 @@ def compute_options_stats_from_history(history: list[dict[str, Any]]) -> dict[st
} }
_STATS_WINDOWS = ("today", "7d", "30d", "all")
def normalize_stats_window(raw: Any) -> str:
w = str(raw or "7d").strip().lower()
if w in ("1d", "day", "今日"):
return "today"
if w in ("7", "week"):
return "7d"
if w in ("30", "month"):
return "30d"
if w in ("0", "全部", "total"):
return "all"
return w if w in _STATS_WINDOWS else "7d"
def stats_window_bounds(window: str, *, now: datetime | None = None) -> tuple[datetime | None, datetime | None]:
"""返回闭区间起止(本地/业务时区近似用本地 now).all → (None, None)."""
w = normalize_stats_window(window)
end = now or datetime.now()
if w == "all":
return None, None
if w == "today":
start = end.replace(hour=0, minute=0, second=0, microsecond=0)
return start, end
days = 7 if w == "7d" else 30
from datetime import timedelta
start = end - timedelta(days=days)
return start, end
def filter_history_for_stats_window(
history: list[dict[str, Any]],
window: str,
*,
now: datetime | None = None,
) -> list[dict[str, Any]]:
"""窗口内已平仓 + 当前持仓(open)."""
start, end = stats_window_bounds(window, now=now)
out: list[dict[str, Any]] = []
for row in history or []:
if row.get("status") == "open":
out.append(row)
continue
if start is None:
out.append(row)
continue
ts = _parse_ts(row.get("closed_at")) or _parse_ts(row.get("created_at"))
if ts is None:
continue
if ts < start:
continue
if end is not None and ts > end:
continue
out.append(row)
return out
def compute_options_stats_for_window(
history: list[dict[str, Any]],
window: str,
*,
now: datetime | None = None,
) -> dict[str, Any]:
w = normalize_stats_window(window)
filtered = filter_history_for_stats_window(history, w, now=now)
stats = compute_options_stats_from_history(filtered)
start, end = stats_window_bounds(w, now=now)
stats["window"] = w
stats["window_start"] = start.strftime("%Y-%m-%d %H:%M:%S") if start else None
stats["window_end"] = end.strftime("%Y-%m-%d %H:%M:%S") if end else None
return stats
def compute_options_stats(get_db) -> dict[str, Any]: def compute_options_stats(get_db) -> dict[str, Any]:
conn = get_db() conn = get_db()
try: try: