Deduct open position margin from recommend max lots.

Recalculate tradable symbol budgets from remaining margin after CTP usage and refresh the table on position updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-29 09:39:42 +08:00
parent fd2dba22fd
commit 71c480a587
5 changed files with 96 additions and 8 deletions
+61 -3
View File
@@ -117,17 +117,61 @@ def _ctp_connected_for_mode(trading_mode: str) -> bool:
return False
def recommend_margin_used(trading_mode: str) -> float:
"""当前持仓已占用保证金(CTP 柜台优先)。"""
if not _ctp_connected_for_mode(trading_mode):
return 0.0
try:
from vnpy_bridge import ctp_account_margin_used, ctp_list_positions
used = ctp_account_margin_used(trading_mode)
if used is not None and used > 0:
return float(used)
total = 0.0
for p in ctp_list_positions(
trading_mode, refresh_if_empty=False, refresh_margin=True,
):
m = float(p.get("margin") or 0)
if m > 0:
total += m
return round(total, 2) if total > 0 else 0.0
except Exception as exc:
logger.debug("recommend_margin_used: %s", exc)
return 0.0
def margin_budget_info(
capital: float,
max_margin_pct: float,
margin_used: float = 0.0,
) -> dict[str, float]:
"""保证金上限总额、已占用、剩余可开额度。"""
cap = float(capital or 0)
pct = max(1.0, min(100.0, float(max_margin_pct or 30.0)))
total = cap * pct / 100.0 if cap > 0 else 0.0
used = max(0.0, float(margin_used or 0))
remaining = max(0.0, total - used)
return {
"margin_budget_total": round(total, 2),
"margin_used": round(used, 2),
"margin_budget_remaining": round(remaining, 2),
"max_margin_pct": pct,
}
def enrich_recommend_rows(
rows: list[dict],
capital: float,
*,
max_margin_pct: float = 30.0,
trading_mode: str = "simulation",
margin_used: float = 0.0,
) -> list[dict]:
"""用当前权益与保证金比例补算最大可开手数(兼容旧缓存)。"""
cap = float(capital or 0)
pct = max(1.0, min(100.0, float(max_margin_pct or 30.0)))
budget = cap * pct / 100.0 if cap > 0 else 0.0
budget_info = margin_budget_info(cap, max_margin_pct, margin_used)
pct = budget_info["max_margin_pct"]
budget = budget_info["margin_budget_remaining"]
ctp_connected = _ctp_connected_for_mode(trading_mode)
enriched: list[dict] = []
for raw in rows:
@@ -172,6 +216,8 @@ def enrich_recommend_rows(
row["max_lots"] = lots
row.pop("recommended_lots", None)
row["margin_budget"] = round(budget, 2)
row["margin_budget_total"] = budget_info["margin_budget_total"]
row["margin_used"] = budget_info["margin_used"]
row["max_margin_pct"] = pct
status = row.get("status") or ""
if lots >= 1 and status in ("ok", "margin_ok"):
@@ -181,6 +227,8 @@ def enrich_recommend_rows(
)
if row.get("margin_source") == "ctp":
row["status_label"] += f"{src}保证金)"
if budget_info["margin_used"] > 0:
row["status_label"] += "·扣持仓"
elif lots < 1 and status in ("ok", "margin_ok"):
row["status"] = "blocked"
row["status_label"] = "资金不足"
@@ -214,17 +262,24 @@ def refresh_recommend_cache(
*,
trading_mode: str = "simulation",
max_margin_pct: float = 30.0,
margin_used: float | None = None,
) -> list[dict]:
"""后台拉行情、筛选并写入数据库。"""
ensure_recommend_tables(conn)
ensure_fee_rates_schema(conn)
ctp_connected = _ctp_connected_for_mode(trading_mode)
used = (
float(margin_used)
if margin_used is not None
else recommend_margin_used(trading_mode)
)
all_rows = list_product_recommendations(
capital,
quote_fn,
max_margin_pct=max_margin_pct,
trading_mode=trading_mode,
ctp_connected=ctp_connected,
margin_used=used,
)
rows = filter_affordable_recommendations(all_rows)
if not rows and float(capital or 0) > 0:
@@ -293,11 +348,14 @@ def recommend_payload(
payload = load_recommend_cache(conn)
cap = float(live_capital or 0)
pct = max(1.0, min(100.0, float(max_margin_pct or 30.0)))
used = recommend_margin_used(trading_mode)
budget_info = margin_budget_info(cap, pct, used)
payload["capital"] = cap
payload["max_margin_pct"] = pct
payload.update(budget_info)
rows = payload.get("rows") or []
rows = enrich_recommend_rows(
rows, cap, max_margin_pct=pct, trading_mode=trading_mode,
rows, cap, max_margin_pct=pct, trading_mode=trading_mode, margin_used=used,
)
rows = filter_rows_for_account_scope(
rows, cap, ctp_connected=_ctp_connected_for_mode(trading_mode),