Persist CTP margin, ratio, and fees to DB; use exchange commission in trade logs.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+33
-5
@@ -49,6 +49,12 @@ def _to_ths_code(symbol: str) -> str:
|
|||||||
return sym.lower()
|
return sym.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def _allocate_commission(total_comm: float, matched: int, total_lots: int) -> float:
|
||||||
|
if total_comm <= 0 or matched <= 0 or total_lots <= 0:
|
||||||
|
return 0.0
|
||||||
|
return round(total_comm * matched / total_lots, 2)
|
||||||
|
|
||||||
|
|
||||||
def build_round_trips(trades: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
def build_round_trips(trades: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
"""按 FIFO 将开/平仓成交配对为完整回合。"""
|
"""按 FIFO 将开/平仓成交配对为完整回合。"""
|
||||||
stacks: dict[tuple[str, str], list[dict[str, Any]]] = defaultdict(list)
|
stacks: dict[tuple[str, str], list[dict[str, Any]]] = defaultdict(list)
|
||||||
@@ -70,25 +76,41 @@ def build_round_trips(trades: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|||||||
stacks[key].append({
|
stacks[key].append({
|
||||||
**t,
|
**t,
|
||||||
"remaining": lots,
|
"remaining": lots,
|
||||||
|
"commission_remaining": float(t.get("commission") or 0),
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
close_lots_total = lots
|
||||||
close_lots_left = lots
|
close_lots_left = lots
|
||||||
close_price = float(t.get("price") or 0)
|
close_price = float(t.get("price") or 0)
|
||||||
close_time = t.get("datetime") or ""
|
close_time = t.get("datetime") or ""
|
||||||
close_trade_id = str(t.get("trade_id") or "")
|
close_trade_id = str(t.get("trade_id") or "")
|
||||||
|
close_comm_total = float(t.get("commission") or 0)
|
||||||
while close_lots_left > 0 and stacks[key]:
|
while close_lots_left > 0 and stacks[key]:
|
||||||
open_t = stacks[key][0]
|
open_t = stacks[key][0]
|
||||||
matched = min(close_lots_left, int(open_t.get("remaining") or 0))
|
open_rem = int(open_t.get("remaining") or 0)
|
||||||
|
matched = min(close_lots_left, open_rem)
|
||||||
if matched <= 0:
|
if matched <= 0:
|
||||||
stacks[key].pop(0)
|
stacks[key].pop(0)
|
||||||
continue
|
continue
|
||||||
open_t["remaining"] = int(open_t.get("remaining") or 0) - matched
|
open_comm_rem = float(open_t.get("commission_remaining") or 0)
|
||||||
|
open_comm_share = (
|
||||||
|
_allocate_commission(open_comm_rem, matched, open_rem)
|
||||||
|
if open_rem > 0 else 0.0
|
||||||
|
)
|
||||||
|
close_comm_share = _allocate_commission(
|
||||||
|
close_comm_total, matched, close_lots_total,
|
||||||
|
)
|
||||||
|
open_t["remaining"] = open_rem - matched
|
||||||
|
open_t["commission_remaining"] = round(
|
||||||
|
max(0.0, open_comm_rem - open_comm_share), 2,
|
||||||
|
)
|
||||||
if open_t["remaining"] <= 0:
|
if open_t["remaining"] <= 0:
|
||||||
stacks[key].pop(0)
|
stacks[key].pop(0)
|
||||||
close_lots_left -= matched
|
close_lots_left -= matched
|
||||||
open_trade_id = str(open_t.get("trade_id") or "")
|
open_trade_id = str(open_t.get("trade_id") or "")
|
||||||
ctp_key = f"{open_trade_id}|{close_trade_id}|{sym}|{pos_dir}|{matched}"
|
ctp_key = f"{open_trade_id}|{close_trade_id}|{sym}|{pos_dir}|{matched}"
|
||||||
|
trip_fee = round(open_comm_share + close_comm_share, 2)
|
||||||
trips.append({
|
trips.append({
|
||||||
"ctp_trade_key": ctp_key,
|
"ctp_trade_key": ctp_key,
|
||||||
"symbol": sym,
|
"symbol": sym,
|
||||||
@@ -101,6 +123,8 @@ def build_round_trips(trades: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|||||||
"close_time": close_time,
|
"close_time": close_time,
|
||||||
"open_trade_id": open_trade_id,
|
"open_trade_id": open_trade_id,
|
||||||
"close_trade_id": close_trade_id,
|
"close_trade_id": close_trade_id,
|
||||||
|
"fee": trip_fee,
|
||||||
|
"fee_from_ctp": trip_fee > 0,
|
||||||
})
|
})
|
||||||
return trips
|
return trips
|
||||||
|
|
||||||
@@ -202,9 +226,13 @@ def sync_trade_logs_from_ctp(
|
|||||||
direction, entry, sl_f, tp_f, lots, close_px, capital, ths,
|
direction, entry, sl_f, tp_f, lots, close_px, capital, ths,
|
||||||
)
|
)
|
||||||
pnl = float(metrics.get("float_pnl") or 0)
|
pnl = float(metrics.get("float_pnl") or 0)
|
||||||
fee = calc_round_trip_fee(
|
trip_fee = float(trip.get("fee") or 0)
|
||||||
ths, entry, close_px, lots, open_time, close_time, trading_mode=trading_mode,
|
if trip_fee > 0:
|
||||||
)
|
fee = round(trip_fee, 2)
|
||||||
|
else:
|
||||||
|
fee = calc_round_trip_fee(
|
||||||
|
ths, entry, close_px, lots, open_time, close_time, trading_mode=trading_mode,
|
||||||
|
)
|
||||||
pnl_net = round(pnl - fee, 2)
|
pnl_net = round(pnl - fee, 2)
|
||||||
margin_pct = metrics.get("position_pct")
|
margin_pct = metrics.get("position_pct")
|
||||||
equity_after = calc_equity_after(capital, pnl_net)
|
equity_after = calc_equity_after(capital, pnl_net)
|
||||||
|
|||||||
+46
-1
@@ -299,6 +299,37 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
row["position_pct"] = round(margin / capital * 100, 2)
|
row["position_pct"] = round(margin / capital * 100, 2)
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
|
def _persist_ctp_snapshot_to_monitors(
|
||||||
|
conn,
|
||||||
|
rows: list[dict],
|
||||||
|
mode: str,
|
||||||
|
) -> None:
|
||||||
|
"""将柜台校正后的保证金、仓位占比、已扣开仓手续费写入 trade_order_monitors。"""
|
||||||
|
if not ctp_status(mode).get("connected"):
|
||||||
|
return
|
||||||
|
ensure_monitor_order_columns(conn)
|
||||||
|
for row in rows:
|
||||||
|
mid = row.get("monitor_id")
|
||||||
|
if not mid or row.get("order_state") == "pending":
|
||||||
|
continue
|
||||||
|
margin = row.get("margin")
|
||||||
|
position_pct = row.get("position_pct")
|
||||||
|
open_fee = row.get("est_fee")
|
||||||
|
if margin is None and position_pct is None and open_fee is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
execute_retry(
|
||||||
|
conn,
|
||||||
|
"""UPDATE trade_order_monitors SET
|
||||||
|
margin=COALESCE(?, margin),
|
||||||
|
position_pct=COALESCE(?, position_pct),
|
||||||
|
open_fee=COALESCE(?, open_fee)
|
||||||
|
WHERE id=? AND status='active'""",
|
||||||
|
(margin, position_pct, open_fee, int(mid)),
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug("persist monitor ctp snapshot %s: %s", mid, exc)
|
||||||
|
|
||||||
def _ensure_monitors_from_ctp(conn, mode: str) -> None:
|
def _ensure_monitors_from_ctp(conn, mode: str) -> None:
|
||||||
"""CTP 有持仓但本地无监控时,自动补写一条 active 记录供展示。"""
|
"""CTP 有持仓但本地无监控时,自动补写一条 active 记录供展示。"""
|
||||||
if not ctp_status(mode).get("connected"):
|
if not ctp_status(mode).get("connected"):
|
||||||
@@ -742,10 +773,18 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
position_pct = None
|
position_pct = None
|
||||||
if margin and capital > 0:
|
if margin and capital > 0:
|
||||||
position_pct = round(float(margin) / float(capital) * 100, 2)
|
position_pct = round(float(margin) / float(capital) * 100, 2)
|
||||||
|
open_commission = _open_commission_from_ctp_trades(mode, sym, direction)
|
||||||
|
if open_commission is None:
|
||||||
|
fee_info = calc_fee_breakdown(
|
||||||
|
sym, entry, entry, lots, open_time_val or "", "",
|
||||||
|
trading_mode=mode,
|
||||||
|
)
|
||||||
|
open_commission = fee_info.get("open_fee")
|
||||||
execute_retry(
|
execute_retry(
|
||||||
conn,
|
conn,
|
||||||
"""UPDATE trade_order_monitors SET lots=?, entry_price=?,
|
"""UPDATE trade_order_monitors SET lots=?, entry_price=?,
|
||||||
open_time=?, margin=?, position_pct=?, mark_price=?, float_pnl=?
|
open_time=?, margin=?, position_pct=?, mark_price=?, float_pnl=?,
|
||||||
|
open_fee=?
|
||||||
WHERE id=?""",
|
WHERE id=?""",
|
||||||
(
|
(
|
||||||
lots,
|
lots,
|
||||||
@@ -755,6 +794,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
position_pct,
|
position_pct,
|
||||||
float(mark) if mark else None,
|
float(mark) if mark else None,
|
||||||
float_pnl,
|
float_pnl,
|
||||||
|
open_commission,
|
||||||
mid,
|
mid,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -854,6 +894,10 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
sym, entry, close_est, lots, open_time or now_iso, now_iso, trading_mode=mode,
|
sym, entry, close_est, lots, open_time or now_iso, now_iso, trading_mode=mode,
|
||||||
)
|
)
|
||||||
open_commission = _open_commission_from_ctp_trades(mode, sym, direction)
|
open_commission = _open_commission_from_ctp_trades(mode, sym, direction)
|
||||||
|
if open_commission is None and mon and mon.get("open_fee") is not None:
|
||||||
|
cached_fee = float(mon.get("open_fee") or 0)
|
||||||
|
if cached_fee > 0:
|
||||||
|
open_commission = cached_fee
|
||||||
if open_commission is not None:
|
if open_commission is not None:
|
||||||
display_fee = open_commission
|
display_fee = open_commission
|
||||||
fee_source = "ctp"
|
fee_source = "ctp"
|
||||||
@@ -1170,6 +1214,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
_ensure_monitors_from_ctp(conn, mode)
|
_ensure_monitors_from_ctp(conn, mode)
|
||||||
rows = _build_trading_live_rows(conn, fast=fast)
|
rows = _build_trading_live_rows(conn, fast=fast)
|
||||||
rows = _apply_account_margin_to_rows(rows, mode, capital)
|
rows = _apply_account_margin_to_rows(rows, mode, capital)
|
||||||
|
_persist_ctp_snapshot_to_monitors(conn, rows, mode)
|
||||||
pending_orders = _build_pending_orders(conn, mode)
|
pending_orders = _build_pending_orders(conn, mode)
|
||||||
risk = get_risk_status(conn, active_count=_effective_active_position_count(conn, mode))
|
risk = get_risk_status(conn, active_count=_effective_active_position_count(conn, mode))
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ MONITOR_ORDER_COLUMNS = (
|
|||||||
"ALTER TABLE trade_order_monitors ADD COLUMN float_pnl REAL",
|
"ALTER TABLE trade_order_monitors ADD COLUMN float_pnl REAL",
|
||||||
"ALTER TABLE trade_order_monitors ADD COLUMN vt_order_id TEXT",
|
"ALTER TABLE trade_order_monitors ADD COLUMN vt_order_id TEXT",
|
||||||
"ALTER TABLE trade_order_monitors ADD COLUMN order_price REAL",
|
"ALTER TABLE trade_order_monitors ADD COLUMN order_price REAL",
|
||||||
|
"ALTER TABLE trade_order_monitors ADD COLUMN open_fee REAL",
|
||||||
)
|
)
|
||||||
|
|
||||||
TRADE_RESULTS = ("止损", "止盈", "移动止盈", "保本止盈", "手动平仓")
|
TRADE_RESULTS = ("止损", "止盈", "移动止盈", "保本止盈", "手动平仓")
|
||||||
|
|||||||
Reference in New Issue
Block a user