Use CTP contract margin rates for position margin and ratio display.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+72
-7
@@ -976,7 +976,29 @@ class CtpBridge:
|
||||
def _lookup_position_margin(self, sym: str, direction: str) -> float:
|
||||
return float(self._position_margins.get(self._position_margin_key(sym, direction), 0) or 0)
|
||||
|
||||
def estimate_margin_one_lot(self, ths_code: str, price: float) -> Optional[float]:
|
||||
@staticmethod
|
||||
def _vnpy_sym_to_ths(sym: str, ex_name: str) -> str:
|
||||
import re
|
||||
|
||||
s = (sym or "").strip()
|
||||
if not s:
|
||||
return ""
|
||||
ex = (ex_name or "").upper()
|
||||
m = re.match(r"^([A-Za-z]+)(\d+)$", s)
|
||||
if not m:
|
||||
return s
|
||||
letters, digits = m.group(1), m.group(2)
|
||||
if ex == "CZCE":
|
||||
return letters.upper() + (digits[-3:] if len(digits) >= 4 else digits)
|
||||
return letters.lower() + digits
|
||||
|
||||
def estimate_margin_one_lot(
|
||||
self,
|
||||
ths_code: str,
|
||||
price: float,
|
||||
*,
|
||||
direction: str = "long",
|
||||
) -> Optional[float]:
|
||||
"""用 CTP 合约信息估算 1 手保证金(需已连接并完成合约查询)。"""
|
||||
if not self._engine or not price or price <= 0:
|
||||
return None
|
||||
@@ -990,7 +1012,13 @@ class CtpBridge:
|
||||
mult = float(getattr(contract, "size", 0) or 0)
|
||||
long_r = float(getattr(contract, "long_margin_ratio", 0) or 0)
|
||||
short_r = float(getattr(contract, "short_margin_ratio", 0) or 0)
|
||||
ratio = max(long_r, short_r)
|
||||
d = (direction or "long").strip().lower()
|
||||
if d == "short" and short_r > 0:
|
||||
ratio = short_r
|
||||
elif d != "short" and long_r > 0:
|
||||
ratio = long_r
|
||||
else:
|
||||
ratio = max(long_r, short_r)
|
||||
if mult <= 0 or ratio <= 0:
|
||||
return None
|
||||
return round(float(price) * mult * ratio, 2)
|
||||
@@ -998,6 +1026,34 @@ class CtpBridge:
|
||||
logger.debug("estimate_margin_one_lot %s: %s", ths_code, exc)
|
||||
return None
|
||||
|
||||
def estimate_position_margin(
|
||||
self,
|
||||
sym: str,
|
||||
ex_name: str,
|
||||
direction: str,
|
||||
lots: int,
|
||||
price: float,
|
||||
*,
|
||||
pos: Any = None,
|
||||
) -> Optional[float]:
|
||||
"""持仓占用保证金:优先 vnpy 字段,其次 CTP 合约保证金率估算。"""
|
||||
if lots <= 0 or price <= 0:
|
||||
return None
|
||||
if pos is not None:
|
||||
raw = float(getattr(pos, "margin", 0) or getattr(pos, "use_margin", 0) or 0)
|
||||
if raw > 0:
|
||||
return round(raw, 2)
|
||||
cached = self._lookup_position_margin(sym, direction)
|
||||
if cached > 0:
|
||||
return round(cached, 2)
|
||||
ths = self._vnpy_sym_to_ths(sym, ex_name)
|
||||
if not ths:
|
||||
return None
|
||||
per_lot = self.estimate_margin_one_lot(ths, price, direction=direction)
|
||||
if per_lot and per_lot > 0:
|
||||
return round(per_lot * lots, 2)
|
||||
return None
|
||||
|
||||
def lookup_contract_spec(self, ths_code: str) -> Optional[dict]:
|
||||
"""从 CTP 合约信息读取乘数与最小变动价位。"""
|
||||
if not self._engine:
|
||||
@@ -1037,17 +1093,20 @@ class CtpBridge:
|
||||
sym = getattr(pos, "symbol", "") or ""
|
||||
exchange = getattr(pos, "exchange", None)
|
||||
ex_name = str(exchange.value if hasattr(exchange, "value") else exchange or "")
|
||||
margin = self._lookup_position_margin(sym, d)
|
||||
price = float(getattr(pos, "price", 0) or 0)
|
||||
margin = self.estimate_position_margin(
|
||||
sym, ex_name, d, vol, price, pos=pos,
|
||||
)
|
||||
open_time = self._lookup_position_open_time(sym, d) or None
|
||||
out.append({
|
||||
"symbol": sym,
|
||||
"exchange": ex_name,
|
||||
"direction": d,
|
||||
"lots": vol,
|
||||
"avg_price": float(getattr(pos, "price", 0) or 0),
|
||||
"avg_price": price,
|
||||
"pnl": float(getattr(pos, "pnl", 0) or 0),
|
||||
"frozen": int(getattr(pos, "frozen", 0) or 0),
|
||||
"margin": round(margin, 2) if margin > 0 else None,
|
||||
"margin": margin,
|
||||
"open_time": open_time,
|
||||
})
|
||||
return out
|
||||
@@ -1471,12 +1530,18 @@ def ctp_get_tick_detail(mode: str, ths_code: str) -> dict[str, Any]:
|
||||
return {}
|
||||
|
||||
|
||||
def ctp_estimate_margin_one_lot(mode: str, ths_code: str, price: float) -> Optional[float]:
|
||||
def ctp_estimate_margin_one_lot(
|
||||
mode: str,
|
||||
ths_code: str,
|
||||
price: float,
|
||||
*,
|
||||
direction: str = "long",
|
||||
) -> Optional[float]:
|
||||
b = get_bridge()
|
||||
if b.connected_mode != mode or not b.ping():
|
||||
return None
|
||||
try:
|
||||
return b.estimate_margin_one_lot(ths_code, price)
|
||||
return b.estimate_margin_one_lot(ths_code, price, direction=direction)
|
||||
except Exception as exc:
|
||||
logger.debug("ctp_estimate_margin_one_lot: %s", exc)
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user