Hub funds and dashboard: include OKX options balances in USDT totals.
Merge options USDC/USDT at 1:1 into fund overview snapshots, dashboard aggregation, and monitor board totals while keeping a separate options breakdown in the UI. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,6 +21,11 @@ from hub_ai.config import (
|
||||
trading_day_reset_hour,
|
||||
)
|
||||
from hub_ai.fund_history import format_fund_history_text, get_fund_history, record_fund_snapshot
|
||||
from lib.hub.hub_options_funds_lib import (
|
||||
merge_perp_options_balances,
|
||||
options_float_pnl_usdt,
|
||||
options_open_position_count,
|
||||
)
|
||||
from lib.hub.hub_trades_lib import current_trading_day, summarize_trades
|
||||
|
||||
_CHAT_CONTEXT_CACHE: dict[str, dict[str, Any]] = {}
|
||||
@@ -413,6 +418,13 @@ def _fetch_account_bundle(
|
||||
"funding_usdt": None,
|
||||
"trading_usdt": None,
|
||||
"available_trading_usdt": None,
|
||||
"perpetual_funding_usdt": None,
|
||||
"perpetual_trading_usdt": None,
|
||||
"options_funding_usdt": None,
|
||||
"options_trading_usdt": None,
|
||||
"options_float_pnl_u": None,
|
||||
"options_open_position_count": 0,
|
||||
"options_snapshot": None,
|
||||
"trades_yesterday": [],
|
||||
"trade_stats_yesterday": summarize_trades([]),
|
||||
"monitor_lines": {"trends": [], "orders": [], "keys": [], "rolls": []},
|
||||
@@ -465,8 +477,10 @@ def _fetch_account_bundle(
|
||||
if r.status_code == 200:
|
||||
acct_body = r.json()
|
||||
if isinstance(acct_body, dict) and acct_body.get("ok"):
|
||||
base["funding_usdt"] = _safe_float(acct_body.get("funding_usdt"))
|
||||
base["trading_usdt"] = _safe_float(acct_body.get("trading_usdt"))
|
||||
base["perpetual_funding_usdt"] = _safe_float(acct_body.get("funding_usdt"))
|
||||
base["perpetual_trading_usdt"] = _safe_float(acct_body.get("trading_usdt"))
|
||||
base["funding_usdt"] = base["perpetual_funding_usdt"]
|
||||
base["trading_usdt"] = base["perpetual_trading_usdt"]
|
||||
base["available_trading_usdt"] = _safe_float(acct_body.get("available_trading_usdt"))
|
||||
base["flask_ok"] = True
|
||||
except Exception as exc:
|
||||
@@ -542,6 +556,41 @@ def _fetch_account_bundle(
|
||||
if base["positions"]:
|
||||
_enrich_positions_exchange_tpsl(base["positions"], price_snap, hub_mon)
|
||||
|
||||
caps = ex.get("capabilities") or []
|
||||
if "options" in caps:
|
||||
try:
|
||||
r = client.get(
|
||||
f"{flask_url}/api/hub/options/snapshot",
|
||||
headers=_hub_headers(),
|
||||
timeout=hub_flask_timeout(),
|
||||
)
|
||||
if r.status_code == 200:
|
||||
opt_body = r.json()
|
||||
if isinstance(opt_body, dict):
|
||||
base["options_snapshot"] = opt_body
|
||||
if opt_body.get("ok") is not False and opt_body.get("enabled") is not False:
|
||||
base["flask_ok"] = True
|
||||
merged = merge_perp_options_balances(
|
||||
base.get("perpetual_funding_usdt"),
|
||||
base.get("perpetual_trading_usdt"),
|
||||
opt_body,
|
||||
)
|
||||
base["options_funding_usdt"] = merged.get("options_funding_usdt")
|
||||
base["options_trading_usdt"] = merged.get("options_trading_usdt")
|
||||
if merged.get("funding_usdt") is not None:
|
||||
base["funding_usdt"] = merged.get("funding_usdt")
|
||||
if merged.get("trading_usdt") is not None:
|
||||
base["trading_usdt"] = merged.get("trading_usdt")
|
||||
opt_count = options_open_position_count(opt_body)
|
||||
base["options_open_position_count"] = opt_count
|
||||
base["open_position_count"] += opt_count
|
||||
opt_upl = options_float_pnl_usdt(opt_body)
|
||||
if opt_upl is not None:
|
||||
base["options_float_pnl_u"] = opt_upl
|
||||
base["float_pnl_u"] = round(float(base["float_pnl_u"]) + opt_upl, 4)
|
||||
except Exception as exc:
|
||||
base["issues"].append(f"期权接口: {exc}")
|
||||
|
||||
if monitored and not base["agent_ok"] and not base["flask_ok"]:
|
||||
base["status"] = "连接异常"
|
||||
elif base["issues"]:
|
||||
@@ -598,6 +647,9 @@ def build_daily_context(
|
||||
total_funding = 0.0
|
||||
total_trading = 0.0
|
||||
total_open_positions = 0
|
||||
total_options_open_positions = 0
|
||||
total_options_float = 0.0
|
||||
options_float_known = 0
|
||||
funding_known = trading_known = 0
|
||||
for ac in accounts:
|
||||
if ac.get("status") == "未监控":
|
||||
@@ -609,6 +661,11 @@ def build_daily_context(
|
||||
total_loss += int(st.get("loss_count") or 0)
|
||||
total_float += float(ac.get("float_pnl_u") or 0)
|
||||
total_open_positions += int(ac.get("open_position_count") or _account_open_position_count(ac))
|
||||
total_options_open_positions += int(ac.get("options_open_position_count") or 0)
|
||||
opt_float = _safe_float(ac.get("options_float_pnl_u"))
|
||||
if opt_float is not None:
|
||||
total_options_float += opt_float
|
||||
options_float_known += 1
|
||||
fu = _safe_float(ac.get("funding_usdt"))
|
||||
tu = _safe_float(ac.get("trading_usdt"))
|
||||
if fu is not None:
|
||||
@@ -631,6 +688,8 @@ def build_daily_context(
|
||||
"loss_count": total_loss,
|
||||
"float_pnl_u": round(total_float, 4),
|
||||
"open_position_count": total_open_positions,
|
||||
"options_open_position_count": total_options_open_positions,
|
||||
"options_float_pnl_u": round(total_options_float, 4) if options_float_known else None,
|
||||
"total_funding_usdt": round(total_funding, 4) if total_funding is not None else None,
|
||||
"total_trading_usdt": round(total_trading, 4) if total_trading is not None else None,
|
||||
}
|
||||
@@ -929,6 +988,25 @@ def format_dashboard_account_detail(ac: dict) -> dict[str, Any]:
|
||||
"pnl": round(upnl, 4),
|
||||
}
|
||||
)
|
||||
opt_snap = ac.get("options_snapshot") if isinstance(ac.get("options_snapshot"), dict) else {}
|
||||
if opt_snap.get("ok") is not False and opt_snap.get("enabled") is not False:
|
||||
for p in opt_snap.get("positions") or []:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
inst = p.get("inst_id") or "?"
|
||||
opt_type = (p.get("opt_type") or "").upper()
|
||||
label = "Call" if opt_type == "C" else "Put" if opt_type == "P" else opt_type or "OPT"
|
||||
upl = p.get("upl")
|
||||
line: dict[str, Any] = {
|
||||
"kind": "options",
|
||||
"text": f"期权 {inst} {label}",
|
||||
}
|
||||
if upl is not None:
|
||||
try:
|
||||
line["pnl"] = round(float(upl), 4)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
position_lines.append(line)
|
||||
issues = [str(x) for x in (ac.get("issues") or [])[:3]]
|
||||
return {
|
||||
"monitor_counts": {
|
||||
|
||||
Reference in New Issue
Block a user