feat(hub): enrich AI coach with fund history, closed trades, and chat uploads
- Add 15-day fund snapshot store and /api/hub/account on all instances - Summary includes yesterday/today trades, fund columns, and section 5 操作建议 - Chat context distinguishes empty positions from local monitors - Support image/document attachments in AI chat Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,11 +4,13 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from typing import Any, Callable, Optional
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from hub_ai.config import hub_agent_timeout, hub_flask_timeout, trading_day_reset_hour
|
||||
from hub_ai.config import FUND_HISTORY_DAYS, hub_agent_timeout, hub_flask_timeout, trading_day_reset_hour
|
||||
from hub_ai.fund_history import format_fund_history_text, get_fund_history, record_fund_snapshot
|
||||
from hub_trades_lib import current_trading_day, summarize_trades
|
||||
|
||||
|
||||
@@ -35,6 +37,42 @@ def _safe_float(v: Any) -> Optional[float]:
|
||||
return None
|
||||
|
||||
|
||||
def _position_contracts(p: dict) -> float:
|
||||
for key in ("contracts", "contracts_signed", "size"):
|
||||
v = p.get(key)
|
||||
try:
|
||||
if v is not None and v != "":
|
||||
return float(v)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
return 0.0
|
||||
|
||||
|
||||
def _filter_open_positions(positions: list) -> list[dict]:
|
||||
out: list[dict] = []
|
||||
for p in positions or []:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
if abs(_position_contracts(p)) < 1e-12:
|
||||
continue
|
||||
out.append(p)
|
||||
return out
|
||||
|
||||
|
||||
def _account_open_position_count(ac: dict) -> int:
|
||||
return len(_filter_open_positions(ac.get("positions") or []))
|
||||
|
||||
|
||||
def _monitor_counts(ac: dict) -> dict[str, int]:
|
||||
mon = ac.get("monitor_lines") or {}
|
||||
return {
|
||||
"trends": len(mon.get("trends") or []),
|
||||
"rolls": len(mon.get("rolls") or []),
|
||||
"keys": len(mon.get("keys") or []),
|
||||
"orders": len(mon.get("orders") or []),
|
||||
}
|
||||
|
||||
|
||||
def _position_float_pnl(pos: dict) -> float:
|
||||
for key in ("unrealized_pnl", "unrealizedPnl", "upnl"):
|
||||
v = _safe_float(pos.get(key))
|
||||
@@ -61,17 +99,89 @@ def _collect_open_issues(
|
||||
issues.append("Flask 监控连接异常")
|
||||
if day_pnl < -0.01:
|
||||
issues.append(f"当日平仓亏损 {day_pnl:.2f}U")
|
||||
float_pnl = sum(_position_float_pnl(p) for p in positions if isinstance(p, dict))
|
||||
open_positions = _filter_open_positions(positions)
|
||||
float_pnl = sum(_position_float_pnl(p) for p in open_positions)
|
||||
if float_pnl < -0.5:
|
||||
issues.append(f"当前浮亏 {float_pnl:.2f}U")
|
||||
if isinstance(hub_mon, dict) and hub_mon.get("ok") is not False:
|
||||
orders = hub_mon.get("orders") or []
|
||||
trends = hub_mon.get("trends") or []
|
||||
if positions and not orders and not trends:
|
||||
if open_positions and not orders and not trends:
|
||||
issues.append("交易所有持仓但无本地 active 监控/趋势计划")
|
||||
return issues
|
||||
|
||||
|
||||
def previous_trading_day(trading_day: str) -> str:
|
||||
day = (trading_day or "").strip()[:10]
|
||||
if not day:
|
||||
return day
|
||||
dt = datetime.strptime(day, "%Y-%m-%d")
|
||||
return (dt - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
def _fmt_fund(v: Any) -> str:
|
||||
n = _safe_float(v)
|
||||
if n is None:
|
||||
return "未知"
|
||||
return f"{n:.2f}U"
|
||||
|
||||
|
||||
def _format_trade_line(t: dict, *, day_label: str = "") -> str:
|
||||
prefix = f"[{day_label}] " if day_label else ""
|
||||
return (
|
||||
f"{prefix}{t.get('symbol')} {t.get('direction')} {t.get('result')} "
|
||||
f"{t.get('pnl_amount')}U @ {t.get('closed_at') or '?'}"
|
||||
)
|
||||
|
||||
|
||||
def _monitor_label(item: dict, default: str = "") -> str:
|
||||
for key in ("monitor_type_label", "monitor_type", "entry_reason", "source_label"):
|
||||
val = item.get(key)
|
||||
if val:
|
||||
return str(val)
|
||||
return default
|
||||
|
||||
|
||||
def _format_monitor_sections(hub_mon: Optional[dict]) -> dict[str, list[str]]:
|
||||
out = {"trends": [], "orders": [], "keys": [], "rolls": []}
|
||||
if not isinstance(hub_mon, dict) or hub_mon.get("ok") is False:
|
||||
return out
|
||||
for t in hub_mon.get("trends") or []:
|
||||
if not isinstance(t, dict):
|
||||
continue
|
||||
out["trends"].append(
|
||||
f"{t.get('symbol')} {t.get('direction')} "
|
||||
f"SL={t.get('stop_loss')} TP={t.get('take_profit')} "
|
||||
f"补仓区[{t.get('add_lower')}~{t.get('add_upper')}] "
|
||||
f"状态={t.get('status')}"
|
||||
)
|
||||
for o in hub_mon.get("orders") or []:
|
||||
if not isinstance(o, dict):
|
||||
continue
|
||||
label = _monitor_label(o, "下单监控")
|
||||
out["orders"].append(
|
||||
f"{label}: {o.get('symbol')} {o.get('direction')} "
|
||||
f"触发={o.get('trigger_price')} SL={o.get('stop_loss')} TP={o.get('take_profit')} "
|
||||
f"状态={o.get('status')}"
|
||||
)
|
||||
for k in hub_mon.get("keys") or []:
|
||||
if not isinstance(k, dict):
|
||||
continue
|
||||
out["keys"].append(
|
||||
f"关键位: {k.get('symbol')} {k.get('direction')} "
|
||||
f"上={k.get('upper')} 下={k.get('lower')} 类型={k.get('monitor_type')}"
|
||||
)
|
||||
for r in hub_mon.get("rolls") or []:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
out["rolls"].append(
|
||||
f"顺势加仓: {r.get('symbol')} {r.get('direction')} "
|
||||
f"腿数={r.get('leg_count')} SL={r.get('current_stop_loss') or r.get('initial_stop_loss')} "
|
||||
f"状态={r.get('status')}"
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def _fetch_account_bundle(client: httpx.Client, ex: dict, trading_day: str) -> dict[str, Any]:
|
||||
name = ex.get("name") or ex.get("key") or ex.get("id")
|
||||
key = ex.get("key") or ""
|
||||
@@ -89,8 +199,15 @@ def _fetch_account_bundle(client: httpx.Client, ex: dict, trading_day: str) -> d
|
||||
"trades": [],
|
||||
"trade_stats": summarize_trades([]),
|
||||
"positions": [],
|
||||
"open_position_count": 0,
|
||||
"float_pnl_u": 0.0,
|
||||
"balance_usdt": None,
|
||||
"funding_usdt": None,
|
||||
"trading_usdt": None,
|
||||
"available_trading_usdt": None,
|
||||
"trades_yesterday": [],
|
||||
"trade_stats_yesterday": summarize_trades([]),
|
||||
"monitor_lines": {"trends": [], "orders": [], "keys": [], "rolls": []},
|
||||
"issues": [],
|
||||
"agent_ok": False,
|
||||
"flask_ok": False,
|
||||
@@ -122,13 +239,30 @@ def _fetch_account_bundle(client: httpx.Client, ex: dict, trading_day: str) -> d
|
||||
base["balance_usdt"] = _safe_float(agent_body.get("balance_usdt"))
|
||||
positions = agent_body.get("positions") or []
|
||||
if isinstance(positions, list):
|
||||
base["positions"] = positions
|
||||
base["float_pnl_u"] = round(
|
||||
sum(_position_float_pnl(p) for p in positions if isinstance(p, dict)), 4
|
||||
)
|
||||
open_positions = _filter_open_positions(positions)
|
||||
base["positions"] = open_positions
|
||||
base["open_position_count"] = len(open_positions)
|
||||
base["float_pnl_u"] = round(sum(_position_float_pnl(p) for p in open_positions), 4)
|
||||
|
||||
hub_mon = None
|
||||
prev_day = previous_trading_day(trading_day)
|
||||
if flask_url:
|
||||
try:
|
||||
r = client.get(
|
||||
f"{flask_url}/api/hub/account",
|
||||
headers=_hub_headers(),
|
||||
timeout=hub_flask_timeout(),
|
||||
)
|
||||
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["available_trading_usdt"] = _safe_float(acct_body.get("available_trading_usdt"))
|
||||
base["flask_ok"] = True
|
||||
except Exception as exc:
|
||||
base["issues"].append(f"资金接口: {exc}")
|
||||
|
||||
try:
|
||||
r = client.get(
|
||||
f"{flask_url}/api/hub/trades/today",
|
||||
@@ -145,6 +279,25 @@ def _fetch_account_bundle(client: httpx.Client, ex: dict, trading_day: str) -> d
|
||||
except Exception as exc:
|
||||
base["issues"].append(f"成交接口: {exc}")
|
||||
|
||||
if prev_day:
|
||||
try:
|
||||
r = client.get(
|
||||
f"{flask_url}/api/hub/trades/today",
|
||||
headers=_hub_headers(),
|
||||
params={"trading_day": prev_day},
|
||||
timeout=hub_flask_timeout(),
|
||||
)
|
||||
if r.status_code == 200:
|
||||
y_body = r.json()
|
||||
if isinstance(y_body, dict) and y_body.get("ok"):
|
||||
base["trades_yesterday"] = y_body.get("trades") or []
|
||||
base["trade_stats_yesterday"] = y_body.get("stats") or summarize_trades(
|
||||
base["trades_yesterday"]
|
||||
)
|
||||
base["flask_ok"] = True
|
||||
except Exception as exc:
|
||||
base["issues"].append(f"昨日成交: {exc}")
|
||||
|
||||
try:
|
||||
r = client.get(
|
||||
f"{flask_url}/api/hub/monitor",
|
||||
@@ -158,6 +311,7 @@ def _fetch_account_bundle(client: httpx.Client, ex: dict, trading_day: str) -> d
|
||||
base["flask_ok"] = True
|
||||
base["active_orders"] = len(hub_mon.get("orders") or [])
|
||||
base["active_trends"] = len(hub_mon.get("trends") or [])
|
||||
base["monitor_lines"] = _format_monitor_sections(hub_mon)
|
||||
except Exception as exc:
|
||||
if "成交接口" not in str(base["issues"]):
|
||||
base["issues"].append(f"监控接口: {exc}")
|
||||
@@ -198,6 +352,10 @@ def build_daily_context(
|
||||
total_closed_pnl = 0.0
|
||||
total_closed = total_win = total_loss = 0
|
||||
total_float = 0.0
|
||||
total_funding = 0.0
|
||||
total_trading = 0.0
|
||||
total_open_positions = 0
|
||||
funding_known = trading_known = 0
|
||||
for ac in accounts:
|
||||
if ac.get("status") == "未监控":
|
||||
continue
|
||||
@@ -207,47 +365,128 @@ def build_daily_context(
|
||||
total_win += int(st.get("win_count") or 0)
|
||||
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))
|
||||
fu = _safe_float(ac.get("funding_usdt"))
|
||||
tu = _safe_float(ac.get("trading_usdt"))
|
||||
if fu is not None:
|
||||
total_funding += fu
|
||||
funding_known += 1
|
||||
if tu is not None:
|
||||
total_trading += tu
|
||||
trading_known += 1
|
||||
if not funding_known:
|
||||
total_funding = None
|
||||
if not trading_known:
|
||||
total_trading = None
|
||||
|
||||
totals = {
|
||||
"trading_day": day,
|
||||
"prev_trading_day": previous_trading_day(day),
|
||||
"total_pnl_u": round(total_closed_pnl, 4),
|
||||
"closed_count": total_closed,
|
||||
"win_count": total_win,
|
||||
"loss_count": total_loss,
|
||||
"float_pnl_u": round(total_float, 4),
|
||||
"open_position_count": total_open_positions,
|
||||
"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,
|
||||
}
|
||||
record_fund_snapshot(day, accounts, keep_days=FUND_HISTORY_DAYS)
|
||||
fund_history = get_fund_history(anchor_day=day, keep_days=FUND_HISTORY_DAYS)
|
||||
account_names = {str(ac.get("key") or ac.get("id")): ac.get("name") for ac in accounts}
|
||||
fund_history_text = format_fund_history_text(fund_history, account_names=account_names)
|
||||
payload = {
|
||||
"trading_day": day,
|
||||
"prev_trading_day": previous_trading_day(day),
|
||||
"totals": totals,
|
||||
"accounts": accounts,
|
||||
"fund_history": fund_history,
|
||||
"fund_history_text": fund_history_text,
|
||||
}
|
||||
payload = {"trading_day": day, "totals": totals, "accounts": accounts}
|
||||
text = format_context_text(payload)
|
||||
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()[:16]
|
||||
return {"trading_day": day, "totals": totals, "accounts": accounts, "text": text, "context_hash": digest}
|
||||
return {
|
||||
"trading_day": day,
|
||||
"prev_trading_day": previous_trading_day(day),
|
||||
"totals": totals,
|
||||
"accounts": accounts,
|
||||
"fund_history": fund_history,
|
||||
"fund_history_text": fund_history_text,
|
||||
"text": text,
|
||||
"context_hash": digest,
|
||||
}
|
||||
|
||||
|
||||
def format_context_text(payload: dict) -> str:
|
||||
lines = []
|
||||
totals = payload.get("totals") or {}
|
||||
day = totals.get("trading_day")
|
||||
prev_day = totals.get("prev_trading_day") or previous_trading_day(str(day or ""))
|
||||
lines.append(
|
||||
f"【合计】交易日 {totals.get('trading_day')} | "
|
||||
f"平仓盈亏 {totals.get('total_pnl_u')}U | "
|
||||
f"【合计·今日 {day}】平仓盈亏 {totals.get('total_pnl_u')}U | "
|
||||
f"笔数 {totals.get('closed_count')}(胜{totals.get('win_count')}/负{totals.get('loss_count')})| "
|
||||
f"监控户浮盈亏合计 {totals.get('float_pnl_u')}U"
|
||||
f"实盘持仓 {totals.get('open_position_count', 0)} 仓 | "
|
||||
f"浮盈亏 {totals.get('float_pnl_u')}U | "
|
||||
f"资金账户合计 {_fmt_fund(totals.get('total_funding_usdt'))} | "
|
||||
f"交易账户合计 {_fmt_fund(totals.get('total_trading_usdt'))}"
|
||||
)
|
||||
lines.append(
|
||||
f"【对比交易日】昨日={prev_day},今日={day}。"
|
||||
"「持仓」= 交易所 Agent 实盘;「趋势/关键位/监控单/加仓」= 本地计划,不等于已开仓。"
|
||||
)
|
||||
fund_txt = str(payload.get("fund_history_text") or "").strip()
|
||||
if fund_txt:
|
||||
lines.append("")
|
||||
lines.append(fund_txt)
|
||||
lines.append("")
|
||||
for ac in payload.get("accounts") or []:
|
||||
st = ac.get("trade_stats") or {}
|
||||
sty = ac.get("trade_stats_yesterday") or {}
|
||||
lines.append(f"--- 账户:{ac.get('name')} ({ac.get('key')}) ---")
|
||||
lines.append(f"状态:{ac.get('status')}")
|
||||
if ac.get("status") == "未监控":
|
||||
lines.append("")
|
||||
continue
|
||||
lines.append(
|
||||
f"当日平仓:{st.get('closed_count')} 笔,盈亏 {st.get('total_pnl_u')}U "
|
||||
f"资金账户 {_fmt_fund(ac.get('funding_usdt'))} | "
|
||||
f"交易账户 {_fmt_fund(ac.get('trading_usdt'))} | "
|
||||
f"可用 {_fmt_fund(ac.get('available_trading_usdt'))}"
|
||||
)
|
||||
lines.append(
|
||||
f"今日({day})平仓:{st.get('closed_count')} 笔,盈亏 {st.get('total_pnl_u')}U "
|
||||
f"(胜{st.get('win_count')}/负{st.get('loss_count')})"
|
||||
)
|
||||
lines.append(f"合约可用余额:{ac.get('balance_usdt') if ac.get('balance_usdt') is not None else '未知'} USDT")
|
||||
lines.append(f"当前持仓浮盈亏:{ac.get('float_pnl_u')}U | 下单监控 {ac.get('active_orders')} | 趋势计划 {ac.get('active_trends')}")
|
||||
lines.append(
|
||||
f"昨日({prev_day})平仓:{sty.get('closed_count')} 笔,盈亏 {sty.get('total_pnl_u')}U "
|
||||
f"(胜{sty.get('win_count')}/负{sty.get('loss_count')})"
|
||||
)
|
||||
open_n = int(ac.get("open_position_count") or _account_open_position_count(ac))
|
||||
if open_n <= 0:
|
||||
lines.append("当前交易所持仓:无(空仓)")
|
||||
else:
|
||||
lines.append(
|
||||
f"当前交易所持仓:{open_n} 仓 | 浮盈亏合计 {ac.get('float_pnl_u')}U"
|
||||
)
|
||||
mon = ac.get("monitor_lines") or {}
|
||||
if mon.get("trends"):
|
||||
lines.append("趋势回调计划(本地,非持仓):")
|
||||
for row in mon["trends"][:8]:
|
||||
lines.append(f" - {row}")
|
||||
if mon.get("rolls"):
|
||||
lines.append("顺势加仓(本地,非持仓):")
|
||||
for row in mon["rolls"][:8]:
|
||||
lines.append(f" - {row}")
|
||||
if mon.get("keys"):
|
||||
lines.append("关键位监控(本地,非持仓):")
|
||||
for row in mon["keys"][:8]:
|
||||
lines.append(f" - {row}")
|
||||
if mon.get("orders"):
|
||||
lines.append("进行中的下单监控(本地,非持仓):")
|
||||
for row in mon["orders"][:8]:
|
||||
lines.append(f" - {row}")
|
||||
positions = ac.get("positions") or []
|
||||
if positions:
|
||||
lines.append("持仓:")
|
||||
lines.append("持仓明细(交易所实盘):")
|
||||
for p in positions[:8]:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
@@ -256,14 +495,21 @@ def format_context_text(payload: dict) -> str:
|
||||
contracts = p.get("contracts") or p.get("size") or "?"
|
||||
upnl = _position_float_pnl(p)
|
||||
lines.append(f" - {sym} {side} 张数{contracts} 浮盈亏{upnl:.4f}U")
|
||||
trades = ac.get("trades") or []
|
||||
if trades:
|
||||
lines.append("当日平仓明细:")
|
||||
for t in trades[:15]:
|
||||
lines.append(
|
||||
f" - {t.get('symbol')} {t.get('direction')} {t.get('result')} "
|
||||
f"{t.get('pnl_amount')}U @ {t.get('closed_at') or '?'}"
|
||||
)
|
||||
lines.append(
|
||||
f"Agent合约余额:{ac.get('balance_usdt') if ac.get('balance_usdt') is not None else '未知'} USDT"
|
||||
)
|
||||
trades_today = ac.get("trades") or []
|
||||
if trades_today:
|
||||
lines.append(f"今日平仓明细:")
|
||||
for t in trades_today[:15]:
|
||||
lines.append(f" - {_format_trade_line(t)}")
|
||||
trades_y = ac.get("trades_yesterday") or []
|
||||
if trades_y:
|
||||
lines.append(f"昨日平仓明细:")
|
||||
for t in trades_y[:15]:
|
||||
lines.append(f" - {_format_trade_line(t)}")
|
||||
if not trades_today and not trades_y:
|
||||
lines.append("平仓明细:无")
|
||||
issues = ac.get("issues") or []
|
||||
if issues:
|
||||
lines.append("关注点:" + ";".join(issues))
|
||||
@@ -272,29 +518,99 @@ def format_context_text(payload: dict) -> str:
|
||||
|
||||
|
||||
def format_account_remark(ac: dict) -> str:
|
||||
"""分户表格备注:持仓摘要或关注点。"""
|
||||
"""分户表格备注:监控摘要 + 持仓。"""
|
||||
parts: list[str] = []
|
||||
mon = ac.get("monitor_lines") or {}
|
||||
if mon.get("trends"):
|
||||
parts.append(f"趋势{len(mon['trends'])}")
|
||||
if mon.get("rolls"):
|
||||
parts.append(f"加仓{len(mon['rolls'])}")
|
||||
if mon.get("keys"):
|
||||
parts.append(f"关键位{len(mon['keys'])}")
|
||||
if mon.get("orders"):
|
||||
parts.append(f"监控单{len(mon['orders'])}")
|
||||
positions = ac.get("positions") or []
|
||||
if not positions:
|
||||
if positions:
|
||||
for p in positions[:2]:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
sym = p.get("symbol") or "?"
|
||||
side = p.get("side") or "?"
|
||||
upnl = _position_float_pnl(p)
|
||||
parts.append(f"{sym} {side} 浮{upnl:.2f}U")
|
||||
if len(positions) > 2:
|
||||
parts.append(f"+{len(positions) - 2}仓")
|
||||
if not parts:
|
||||
issues = ac.get("issues") or []
|
||||
if issues:
|
||||
return ";".join(str(x) for x in issues[:2])
|
||||
return "无"
|
||||
parts: list[str] = []
|
||||
for p in positions[:3]:
|
||||
if not isinstance(p, dict):
|
||||
return ";".join(parts)
|
||||
|
||||
|
||||
def collect_closed_trades_snapshot(accounts: list[dict], *, today: str, yesterday: str) -> list[dict]:
|
||||
rows: list[dict] = []
|
||||
for ac in accounts or []:
|
||||
name = ac.get("name") or ac.get("key")
|
||||
for t in ac.get("trades_yesterday") or []:
|
||||
if not isinstance(t, dict):
|
||||
continue
|
||||
rows.append({**t, "account_name": name, "trading_day": yesterday})
|
||||
for t in ac.get("trades") or []:
|
||||
if not isinstance(t, dict):
|
||||
continue
|
||||
rows.append({**t, "account_name": name, "trading_day": today})
|
||||
rows.sort(key=lambda x: str(x.get("closed_at") or x.get("opened_at") or ""), reverse=True)
|
||||
return rows[:80]
|
||||
|
||||
|
||||
def format_chat_position_overview(payload: dict) -> str:
|
||||
totals = payload.get("totals") or {}
|
||||
total_open = int(totals.get("open_position_count") or 0)
|
||||
if total_open <= 0:
|
||||
head = f"【实盘持仓总览】当前空仓(监控户合计 0 仓)。浮盈亏 0U 表示无持仓,不是「有仓但不动」。"
|
||||
else:
|
||||
head = (
|
||||
f"【实盘持仓总览】监控户合计 {total_open} 仓,"
|
||||
f"浮盈亏合计 {totals.get('float_pnl_u')}U。"
|
||||
)
|
||||
lines = [
|
||||
head,
|
||||
"【区分】只有带「持仓明细/交易所实盘」字样的才是已开仓;趋势回调、关键位、下单监控、顺势加仓是本地计划/监控,不算持仓。",
|
||||
]
|
||||
for ac in payload.get("accounts") or []:
|
||||
if ac.get("status") == "未监控":
|
||||
continue
|
||||
sym = p.get("symbol") or "?"
|
||||
side = p.get("side") or "?"
|
||||
contracts = p.get("contracts") if p.get("contracts") is not None else p.get("size")
|
||||
upnl = _position_float_pnl(p)
|
||||
parts.append(f"持仓: {sym} {side} 张数{contracts} 浮盈亏{upnl:.4f}U")
|
||||
if len(positions) > 3:
|
||||
parts.append(f"另有{len(positions) - 3}仓")
|
||||
return ";".join(parts) if parts else "无"
|
||||
n = int(ac.get("open_position_count") or _account_open_position_count(ac))
|
||||
mc = _monitor_counts(ac)
|
||||
mon_parts = []
|
||||
if mc["trends"]:
|
||||
mon_parts.append(f"趋势{mc['trends']}")
|
||||
if mc["rolls"]:
|
||||
mon_parts.append(f"加仓{mc['rolls']}")
|
||||
if mc["keys"]:
|
||||
mon_parts.append(f"关键位{mc['keys']}")
|
||||
if mc["orders"]:
|
||||
mon_parts.append(f"监控单{mc['orders']}")
|
||||
mon_txt = f";本地监控 {' '.join(mon_parts)}" if mon_parts else ""
|
||||
if n <= 0:
|
||||
lines.append(f"- {ac.get('name')}:空仓{mon_txt}")
|
||||
else:
|
||||
lines.append(
|
||||
f"- {ac.get('name')}:{n}仓 浮盈亏{ac.get('float_pnl_u')}U{mon_txt}"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def format_chat_context_brief(payload: dict, max_chars: int = 2500) -> str:
|
||||
text = format_context_text(payload)
|
||||
def format_chat_context_for_chat(payload: dict, max_chars: int = 5200) -> str:
|
||||
overview = format_chat_position_overview(payload)
|
||||
body = format_context_text(payload)
|
||||
text = overview + "\n\n" + body
|
||||
if len(text) <= max_chars:
|
||||
return text
|
||||
return text[: max_chars - 3].rstrip() + "..."
|
||||
budget = max(800, max_chars - len(overview) - 4)
|
||||
return overview + "\n\n" + body[:budget].rstrip() + "..."
|
||||
|
||||
|
||||
def format_chat_context_brief(payload: dict, max_chars: int = 4500) -> str:
|
||||
return format_chat_context_for_chat(payload, max_chars=max_chars)
|
||||
|
||||
Reference in New Issue
Block a user