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:
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user