Files
crypto_monitor/manual_trading_hub/hub_ai/context.py
T
dekun 2786acf884 fix: feed today-only data to AI daily summary to reduce hallucination
Shrink summary context and prompts to today's trades and positions only, and tighten anti-fabrication rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 09:39:40 +08:00

714 lines
27 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""中控 AI:四户数据聚合为结构化上下文。"""
from __future__ import annotations
import hashlib
import json
import os
from datetime import datetime, timedelta
from typing import Any, Optional
import httpx
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
def _hub_token() -> str:
return (os.getenv("HUB_BRIDGE_TOKEN") or os.getenv("CONTROL_TOKEN") or "").strip()
def _hub_headers() -> dict[str, str]:
tok = _hub_token()
return {"X-Hub-Token": tok} if tok else {}
def _agent_headers() -> dict[str, str]:
tok = (os.getenv("CONTROL_TOKEN") or os.getenv("HUB_BRIDGE_TOKEN") or "").strip()
return {"X-Control-Token": tok} if tok else {}
def _safe_float(v: Any) -> Optional[float]:
try:
if v is None or v == "":
return None
return float(v)
except (TypeError, ValueError):
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))
if v is not None:
return v
return 0.0
def _collect_open_issues(
*,
monitored: bool,
agent_ok: bool,
flask_ok: bool,
positions: list,
hub_mon: Optional[dict],
day_pnl: float,
) -> list[str]:
issues: list[str] = []
if not monitored:
return issues
if not agent_ok:
issues.append("Agent 连接异常")
if not flask_ok:
issues.append("Flask 监控连接异常")
if day_pnl < -0.01:
issues.append(f"当日平仓亏损 {day_pnl:.2f}U")
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 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 ""
enabled = bool(ex.get("enabled"))
env_disabled = bool(ex.get("env_disabled"))
monitored = enabled and not env_disabled
base: dict[str, Any] = {
"id": ex.get("id"),
"key": key,
"name": name,
"enabled": enabled,
"env_disabled": env_disabled,
"status": "未监控" if not monitored else "已监控",
"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,
"hub_monitor": None,
"active_orders": 0,
"active_trends": 0,
}
if not monitored:
base["issues"] = []
return base
agent_url = (ex.get("agent_url") or "").rstrip("/")
flask_url = (ex.get("flask_url") or "").rstrip("/")
agent_body = None
if agent_url:
try:
r = client.get(
f"{agent_url}/status",
headers=_agent_headers(),
timeout=hub_agent_timeout(),
)
if r.status_code == 200:
agent_body = r.json()
base["agent_ok"] = True
except Exception as exc:
base["issues"].append(f"Agent: {exc}")
if isinstance(agent_body, dict):
base["balance_usdt"] = _safe_float(agent_body.get("balance_usdt"))
positions = agent_body.get("positions") or []
if isinstance(positions, list):
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",
headers=_hub_headers(),
params={"trading_day": trading_day},
timeout=hub_flask_timeout(),
)
if r.status_code == 200:
trades_body = r.json()
if isinstance(trades_body, dict) and trades_body.get("ok"):
base["trades"] = trades_body.get("trades") or []
base["trade_stats"] = trades_body.get("stats") or summarize_trades(base["trades"])
base["flask_ok"] = True
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",
headers=_hub_headers(),
timeout=hub_flask_timeout(),
)
if r.status_code == 200:
hub_mon = r.json()
if isinstance(hub_mon, dict) and hub_mon.get("ok") is not False:
base["hub_monitor"] = hub_mon
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}")
if monitored and not base["agent_ok"] and not base["flask_ok"]:
base["status"] = "连接异常"
elif base["issues"]:
base["status"] = "已监控·需关注"
day_pnl = float((base.get("trade_stats") or {}).get("total_pnl_u") or 0)
base["issues"].extend(
_collect_open_issues(
monitored=monitored,
agent_ok=base["agent_ok"],
flask_ok=base["flask_ok"],
positions=base["positions"],
hub_mon=hub_mon if isinstance(hub_mon, dict) else None,
day_pnl=day_pnl,
)
)
base["issues"] = list(dict.fromkeys(base["issues"]))
return base
def build_daily_context(
exchanges: list[dict],
*,
trading_day: Optional[str] = None,
) -> dict[str, Any]:
day = (trading_day or "").strip()[:10] or current_trading_day(
reset_hour=trading_day_reset_hour()
)
accounts: list[dict] = []
with httpx.Client() as client:
for ex in exchanges or []:
accounts.append(_fetch_account_bundle(client, ex, day))
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
st = ac.get("trade_stats") or {}
total_closed_pnl += float(st.get("total_pnl_u") or 0)
total_closed += int(st.get("closed_count") or 0)
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,
}
text = format_context_text(payload)
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()[:16]
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"【合计·今日 {day}】平仓盈亏 {totals.get('total_pnl_u')}U | "
f"笔数 {totals.get('closed_count')}(胜{totals.get('win_count')}/负{totals.get('loss_count')}| "
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"资金账户 {_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"昨日({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("持仓明细(交易所实盘):")
for p in positions[:8]:
if not isinstance(p, dict):
continue
sym = p.get("symbol") or "?"
side = p.get("side") or "?"
contracts = p.get("contracts") or p.get("size") or "?"
upnl = _position_float_pnl(p)
lines.append(f" - {sym} {side} 张数{contracts} 浮盈亏{upnl:.4f}U")
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))
lines.append("")
return "\n".join(lines).strip()
def format_summary_context_text(payload: dict) -> str:
"""今日总结专用:仅当日平仓/持仓/监控,不含昨日明细与资金走势。"""
lines = []
totals = payload.get("totals") or {}
day = totals.get("trading_day")
lines.append(
f"【合计·今日 {day}】平仓盈亏 {totals.get('total_pnl_u')}U | "
f"笔数 {totals.get('closed_count')}(胜{totals.get('win_count')}/负{totals.get('loss_count')}| "
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"【说明】交易日={day}"
"「持仓」= 交易所 Agent 实盘;「趋势/关键位/监控单/加仓」= 本地计划,不等于已开仓。"
)
lines.append("")
for ac in payload.get("accounts") or []:
st = ac.get("trade_stats") 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"资金账户 {_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')}"
)
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("持仓明细(交易所实盘):")
for p in positions[:8]:
if not isinstance(p, dict):
continue
sym = p.get("symbol") or "?"
side = p.get("side") or "?"
contracts = p.get("contracts") or p.get("size") or "?"
upnl = _position_float_pnl(p)
lines.append(f" - {sym} {side} 张数{contracts} 浮盈亏{upnl:.4f}U")
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("今日平仓明细:")
for t in trades_today[:15]:
lines.append(f" - {_format_trade_line(t)}")
else:
lines.append("今日平仓明细:无")
issues = ac.get("issues") or []
if issues:
lines.append("关注点:" + "".join(issues))
lines.append("")
return "\n".join(lines).strip()
def summary_context_hash(payload: dict) -> str:
text = format_summary_context_text(payload)
return hashlib.sha256(text.encode("utf-8")).hexdigest()[:16]
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 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 ""
return "".join(parts)
def collect_closed_trades_snapshot(
accounts: list[dict],
*,
today: str,
yesterday: str | None = None,
) -> list[dict]:
rows: list[dict] = []
for ac in accounts or []:
name = ac.get("name") or ac.get("key")
if yesterday:
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
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_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
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)