中控汇总:Bridge 增加窗口化期权统计接口。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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():
|
||||||
"""中控下发全平指令:客户端本地买一限价执行;无效买一拒绝."""
|
"""中控下发全平指令:客户端本地买一限价执行;无效买一拒绝."""
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user