Fix CTP position average price using OpenCost instead of PositionCost.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
# 详见 LICENSE.zh-CN.txt
|
||||
|
||||
"""CTP 持仓均价:仅使用柜台持仓回报(vnpy pos.price = PositionCost 加权)。"""
|
||||
"""CTP 持仓均价:优先 CTP OpenCost(柜台开仓均价),其次成交加权。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
@@ -45,6 +45,53 @@ def round_to_tick(price: float, sym: str) -> float:
|
||||
return round(round(price / tick) * tick, 4)
|
||||
|
||||
|
||||
def compute_open_avg_from_trades(
|
||||
sym: str,
|
||||
direction: str,
|
||||
trades: Optional[list[dict[str, Any]]],
|
||||
) -> float:
|
||||
"""按开仓成交 FIFO 还原剩余持仓的开仓均价。"""
|
||||
if not trades:
|
||||
return 0.0
|
||||
want = (direction or "long").strip().lower()
|
||||
open_vol = 0.0
|
||||
open_cost = 0.0
|
||||
for t in sorted(trades, key=lambda x: x.get("datetime") or ""):
|
||||
if (t.get("offset") or "").strip().lower() != "open":
|
||||
continue
|
||||
pos_dir = (t.get("position_direction") or t.get("direction") or "long").strip().lower()
|
||||
if pos_dir != want:
|
||||
continue
|
||||
if not symbols_match(t.get("symbol") or "", sym):
|
||||
continue
|
||||
lots = float(int(t.get("lots") or 0))
|
||||
px = float(t.get("price") or 0)
|
||||
if lots <= 0 or px <= 0:
|
||||
continue
|
||||
open_vol += lots
|
||||
open_cost += px * lots
|
||||
if open_vol <= 0:
|
||||
return 0.0
|
||||
for t in sorted(trades, key=lambda x: x.get("datetime") or ""):
|
||||
if (t.get("offset") or "").strip().lower() != "close":
|
||||
continue
|
||||
pos_dir = (t.get("position_direction") or t.get("direction") or "long").strip().lower()
|
||||
if pos_dir != want:
|
||||
continue
|
||||
if not symbols_match(t.get("symbol") or "", sym):
|
||||
continue
|
||||
lots = float(int(t.get("lots") or 0))
|
||||
if lots <= 0 or open_vol <= 0:
|
||||
continue
|
||||
avg = open_cost / open_vol
|
||||
dec = min(lots, open_vol)
|
||||
open_cost -= avg * dec
|
||||
open_vol -= dec
|
||||
if open_vol <= 0:
|
||||
return 0.0
|
||||
return round(open_cost / open_vol, 4)
|
||||
|
||||
|
||||
def resolve_ctp_entry(
|
||||
sym: str,
|
||||
direction: str,
|
||||
@@ -53,11 +100,13 @@ def resolve_ctp_entry(
|
||||
*,
|
||||
tick: Optional[float] = None,
|
||||
) -> tuple[float, str]:
|
||||
"""均价:仅柜台持仓价(trades/tick 参数保留兼容,不参与计算)。"""
|
||||
del direction, trades, tick
|
||||
"""均价:优先 avg_price(OpenCost),否则成交加权。"""
|
||||
if not ctp:
|
||||
return 0.0, "none"
|
||||
pos_avg = float(ctp.get("avg_price") or 0)
|
||||
if pos_avg > 0:
|
||||
return round_to_tick(pos_avg, sym), "ctp"
|
||||
trade_avg = compute_open_avg_from_trades(sym, direction or "long", trades)
|
||||
if trade_avg > 0:
|
||||
return round_to_tick(trade_avg, sym), "trades"
|
||||
return 0.0, "none"
|
||||
|
||||
Reference in New Issue
Block a user