Fix CTP position average price using OpenCost instead of PositionCost.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-02 20:53:15 +08:00
parent b0afff53af
commit 870dfb3bc0
2 changed files with 138 additions and 5 deletions
+86 -2
View File
@@ -342,6 +342,8 @@ class CtpBridge:
self._last_position_query_ts: float = 0.0
self._position_margins: dict[str, float] = {}
self._position_open_times: dict[str, str] = {}
self._position_open_avg: dict[str, float] = {}
self._position_open_cost_acc: dict[str, dict[str, float]] = {}
self._margin_hooked = False
self._trade_hooked = False
self._trade_query_results: list[dict[str, Any]] = []
@@ -524,7 +526,7 @@ class CtpBridge:
sym = getattr(pos, "symbol", "") or ""
exchange = getattr(pos, "exchange", None)
ex_name = str(exchange.value if hasattr(exchange, "value") else exchange or "")
price = float(getattr(pos, "price", 0) or 0)
price = self._position_avg_from_vnpy(pos, sym=sym, ex_name=ex_name, direction=d)
yd = int(getattr(pos, "yd_volume", 0) or 0)
td = max(0, vol - yd)
margin = self.estimate_position_margin(sym, ex_name, d, vol, price, pos=pos)
@@ -1214,6 +1216,13 @@ class CtpBridge:
def on_rsp_position(
data: dict, error: dict, reqid: int, last: bool,
) -> None:
try:
if data:
bridge._ingest_position_open_cost(data)
if last:
bridge._finalize_position_open_cost_acc()
except Exception as exc:
logger.debug("position open avg cache: %s", exc)
ret = orig_pos(data, error, reqid, last)
if last:
now = time.monotonic()
@@ -1620,6 +1629,79 @@ class CtpBridge:
def _position_margin_key(self, sym: str, direction: str) -> str:
return f"{(sym or '').lower()}:{(direction or 'long').strip().lower()}"
@staticmethod
def _direction_from_ctp_posi(posi: Any) -> str:
s = str(posi or "").strip().upper()
if s in ("2", "SHORT", "NET_SHORT"):
return "short"
return "long"
def _contract_mult(self, sym: str, ex_name: str = "") -> float:
ths = self._vnpy_sym_to_ths(sym, ex_name) or sym
try:
return float(get_contract_spec(ths).get("mult") or 0)
except Exception:
return 0.0
def _ingest_position_open_cost(self, data: dict) -> None:
"""累积 CTP 持仓回报 OpenCost,用于计算开仓均价(柜台 开仓均价)。"""
if not data or not data.get("InstrumentID"):
return
vol = int(data.get("Position") or 0)
if vol <= 0:
return
open_cost = float(data.get("OpenCost") or 0)
if open_cost <= 0:
return
sym = str(data["InstrumentID"])
direction = self._direction_from_ctp_posi(data.get("PosiDirection"))
mult = self._contract_mult(sym)
if mult <= 0:
return
key = self._position_margin_key(sym, direction)
acc = self._position_open_cost_acc.setdefault(
key, {"open_cost": 0.0, "vol": 0.0, "mult": mult},
)
acc["open_cost"] += open_cost
acc["vol"] += float(vol)
acc["mult"] = mult
def _finalize_position_open_cost_acc(self) -> None:
for key, acc in list(self._position_open_cost_acc.items()):
vol = float(acc.get("vol") or 0)
mult = float(acc.get("mult") or 0)
open_cost = float(acc.get("open_cost") or 0)
if vol > 0 and mult > 0 and open_cost > 0:
self._position_open_avg[key] = open_cost / (vol * mult)
self._position_open_cost_acc.clear()
def _lookup_position_open_avg(self, sym: str, direction: str) -> float:
return float(
self._position_open_avg.get(self._position_margin_key(sym, direction), 0) or 0
)
def _position_avg_from_vnpy(
self,
pos: Any,
*,
sym: str,
ex_name: str,
direction: str,
) -> float:
cached = self._lookup_position_open_avg(sym, direction)
if cached > 0:
return cached
try:
from modules.ctp.ctp_entry_price import compute_open_avg_from_trades
trades = self.list_trades()
trade_avg = compute_open_avg_from_trades(sym, direction, trades)
if trade_avg > 0:
return trade_avg
except Exception as exc:
logger.debug("position avg from trades: %s", exc)
return float(getattr(pos, "price", 0) or 0)
def _lookup_position_open_time(self, sym: str, direction: str) -> str:
return (self._position_open_times.get(self._position_margin_key(sym, direction)) or "").strip()
@@ -1825,7 +1907,9 @@ class CtpBridge:
sym = getattr(pos, "symbol", "") or ""
exchange = getattr(pos, "exchange", None)
ex_name = str(exchange.value if hasattr(exchange, "value") else exchange or "")
price = float(getattr(pos, "price", 0) or 0)
price = self._position_avg_from_vnpy(
pos, sym=sym, ex_name=ex_name, direction=d,
)
margin = self.estimate_position_margin(
sym, ex_name, d, vol, price, pos=pos,
)