Feed options positions and playbook brief into trading coach.

Coach context previously omitted options_snapshot details; also inject a short 执行手册 summary each turn.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-22 23:28:16 +08:00
parent eb0eddbc9d
commit 58e9c8f85e
6 changed files with 258 additions and 11 deletions
+115 -9
View File
@@ -86,7 +86,81 @@ def _filter_open_positions(positions: list) -> list[dict]:
def _account_open_position_count(ac: dict) -> int:
return len(_filter_open_positions(ac.get("positions") or []))
perp = len(_filter_open_positions(ac.get("positions") or []))
opt = int(ac.get("options_open_position_count") or 0)
if opt <= 0:
opt = len(_iter_options_position_dicts(ac))
return perp + opt
def _iter_options_position_dicts(ac: dict) -> list[dict]:
snap = ac.get("options_snapshot")
if not isinstance(snap, dict):
return []
if snap.get("ok") is False or snap.get("enabled") is False:
return []
out: list[dict] = []
for p in snap.get("positions") or []:
if not isinstance(p, dict):
continue
inst = str(p.get("inst_id") or p.get("instId") or "").strip()
if not inst:
continue
out.append(p)
return out
def _format_options_position_detail_line(p: dict) -> str:
inst = p.get("inst_id") or p.get("instId") or "?"
opt_type = (p.get("opt_type") or p.get("optType") or "").upper()
label = "Call" if opt_type == "C" else "Put" if opt_type == "P" else (opt_type or "OPT")
src = _options_source_label(p)
sheets = p.get("pos")
if sheets is None:
sheets = p.get("sheets")
if sheets is None:
sheets = p.get("contracts")
if sheets is None:
sheets = "?"
parts = [f"期权 {inst} {label}", f"来源{src}", f"张数{sheets}"]
paid = _safe_float(p.get("premium_paid"))
if paid is not None:
parts.append(f"权利金{paid:g}U")
net: Optional[float] = None
try:
from lib.options.options_positions_lib import net_pnl_from_display_row
net = net_pnl_from_display_row(p)
except Exception:
net = None
if net is None:
net = _safe_float(p.get("net_pnl"))
if net is None:
net = _safe_float(p.get("upl"))
if net is not None:
parts.append(f"净盈亏{net:.4f}U")
tgt = _options_target_monitor_text(p)
if tgt and tgt not in ("", "-", ""):
parts.append(f"目标{tgt}")
return " - " + " ".join(parts)
def _append_options_position_lines(lines: list[str], ac: dict, *, limit: int = 6, indent: str = " - ") -> None:
rows = _iter_options_position_dicts(ac)
if not rows:
return
if indent.startswith(" "):
# chat slim: already under account bullet
for p in rows[:limit]:
lines.append(f" · {_format_options_position_detail_line(p).lstrip(' - ')}")
if len(rows) > limit:
lines.append(f" · …共{len(rows)}笔期权持仓")
return
lines.append("期权持仓明细(交易所实盘,含目标位若已挂):")
for p in rows[:limit]:
lines.append(_format_options_position_detail_line(p))
if len(rows) > limit:
lines.append(f" - …共{len(rows)}笔期权持仓")
def _monitor_counts(ac: dict) -> dict[str, int]:
@@ -788,7 +862,9 @@ def format_context_text(payload: dict) -> str:
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('open_position_count', 0)}"
f"(永续{totals.get('perpetual_open_position_count', totals.get('open_position_count', 0))}/"
f"期权{totals.get('options_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'))}"
@@ -855,6 +931,7 @@ def format_context_text(payload: dict) -> str:
if not isinstance(p, dict):
continue
lines.append(_format_position_detail_line(p, hub_mon))
_append_options_position_lines(lines, ac, limit=8)
lines.append(
f"Agent合约余额:{ac.get('balance_usdt') if ac.get('balance_usdt') is not None else '未知'} USDT"
)
@@ -885,7 +962,9 @@ def format_summary_context_text(payload: dict) -> str:
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('open_position_count', 0)}"
f"(永续{totals.get('perpetual_open_position_count', totals.get('open_position_count', 0))}/"
f"期权{totals.get('options_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'))}"
@@ -943,6 +1022,7 @@ def format_summary_context_text(payload: dict) -> str:
if not isinstance(p, dict):
continue
lines.append(_format_position_detail_line(p, hub_mon))
_append_options_position_lines(lines, ac, limit=8)
lines.append(
f"Agent合约余额:{ac.get('balance_usdt') if ac.get('balance_usdt') is not None else '未知'} USDT"
)
@@ -1289,21 +1369,30 @@ def collect_closed_trades_snapshot(
def format_chat_position_overview(payload: dict) -> str:
totals = payload.get("totals") or {}
total_open = int(totals.get("open_position_count") or 0)
opt_total = int(totals.get("options_open_position_count") or 0)
perp_total = int(
totals.get("perpetual_open_position_count")
if totals.get("perpetual_open_position_count") is not None
else max(0, total_open - opt_total)
)
if total_open <= 0:
head = f"【实盘持仓总览】当前空仓(监控户合计 0 仓).浮盈亏 0U 表示无持仓,不是「有仓但不动」."
else:
head = (
f"【实盘持仓总览】监控户合计 {total_open},"
f"【实盘持仓总览】监控户合计 {total_open}"
f"(永续{perp_total}/期权{opt_total}),"
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))
opt_n = int(ac.get("options_open_position_count") or len(_iter_options_position_dicts(ac)))
perp_n = len(_filter_open_positions(ac.get("positions") or []))
mc = _monitor_counts(ac)
mon_parts = []
if mc["trends"]:
@@ -1319,8 +1408,11 @@ def format_chat_position_overview(payload: dict) -> str:
lines.append(f"- {ac.get('name')}:空仓{mon_txt}")
else:
lines.append(
f"- {ac.get('name')}:{n} 浮盈亏{ac.get('float_pnl_u')}U{mon_txt}"
f"- {ac.get('name')}:{n}(永续{perp_n}/期权{opt_n}) "
f"浮盈亏{ac.get('float_pnl_u')}U{mon_txt}"
)
for p in _iter_options_position_dicts(ac)[:4]:
lines.append(f" · {_format_options_position_detail_line(p).lstrip(' - ')}")
return "\n".join(lines)
@@ -1328,11 +1420,19 @@ def format_chat_context_slim(payload: dict) -> str:
"""聊天专用:不含 180 日资金曲线与昨日平仓明细,避免挤占对话上下文."""
totals = payload.get("totals") or {}
day = totals.get("trading_day")
opt_total = int(totals.get("options_open_position_count") or 0)
perp_total = int(
totals.get("perpetual_open_position_count")
if totals.get("perpetual_open_position_count") is not None
else max(0, int(totals.get("open_position_count") or 0) - opt_total)
)
lines = [
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)} | 浮盈亏 {totals.get('float_pnl_u')}U",
"【说明】持仓=交易所实盘;趋势/关键位/监控单=本地计划,不等于已开仓.持仓行内「止损/止盈」= 交易所条件单或监控计划价(与监控页一致).",
f"实盘持仓 {totals.get('open_position_count', 0)}"
f"(永续{perp_total}/期权{opt_total}) | 浮盈亏 {totals.get('float_pnl_u')}U",
"【说明】持仓=交易所实盘(含期权);趋势/关键位/监控单=本地计划,不等于已开仓."
"永续行「止损/止盈」=条件单或监控计划价;期权行含合约/来源/权利金/净盈亏/目标位.",
]
for ac in payload.get("accounts") or []:
if ac.get("status") == "未监控":
@@ -1340,7 +1440,12 @@ def format_chat_context_slim(payload: dict) -> str:
continue
st = ac.get("trade_stats") or {}
open_n = int(ac.get("open_position_count") or _account_open_position_count(ac))
pos_txt = "空仓" if open_n <= 0 else f"{open_n}仓 浮盈亏{ac.get('float_pnl_u')}U"
opt_n = int(ac.get("options_open_position_count") or len(_iter_options_position_dicts(ac)))
perp_n = len(_filter_open_positions(ac.get("positions") or []))
if open_n <= 0:
pos_txt = "空仓"
else:
pos_txt = f"{open_n}仓(永续{perp_n}/期权{opt_n}) 浮盈亏{ac.get('float_pnl_u')}U"
mc = _monitor_counts(ac)
mon = []
if mc["trends"]:
@@ -1369,6 +1474,7 @@ def format_chat_context_slim(payload: dict) -> str:
if not isinstance(p, dict):
continue
lines.append(f" · {_format_position_detail_line(p, hub_mon).lstrip(' - ')}")
_append_options_position_lines(lines, ac, limit=6, indent=" · ")
return "\n".join(lines)