fix卖回VWAP必须落库并覆盖指数占位价,禁止预算反推开仓价。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -495,9 +495,16 @@ def close_option_by_bid1(
|
||||
|
||||
row_budget = _safe_float(row["budget_usdt"])
|
||||
row_open_idx = _safe_float(row["open_index_px"])
|
||||
if (row_open_idx is None or row_open_idx <= 0) and row_budget and paid > 1e-12:
|
||||
# 无开仓指数时用预算/已付反推
|
||||
row_open_idx = float(row_budget) / paid
|
||||
# 禁止 budget/premium 反推开仓价(会把缓冲算进均价,开仓价被抬飞)
|
||||
if (row_open_idx is None or row_open_idx <= 0) and row.get("bridge_id") is not None:
|
||||
try:
|
||||
from lib.options.options_spot_bridge_lib import bridge_buy_vwap
|
||||
|
||||
bv = bridge_buy_vwap(conn, int(row["bridge_id"]))
|
||||
if bv is not None and float(bv) > 0:
|
||||
row_open_idx = float(bv)
|
||||
except Exception:
|
||||
pass
|
||||
settled = settle_closed_pnl(
|
||||
premium_paid=paid,
|
||||
premium_received=recv,
|
||||
|
||||
@@ -511,6 +511,16 @@ def open_coin_option_buy_full(
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 开仓价必须在缩减 coin_bought 之前按买币 VWAP 冻住
|
||||
# (buy_usdt_spent/buy_coin_accum;禁止事后 budget/缩水权利金量反推)
|
||||
open_px_freeze = idx_open
|
||||
try:
|
||||
bv = _spot_bridge.bridge_buy_vwap(conn, bridge_id)
|
||||
if bv is not None and float(bv) > 0:
|
||||
open_px_freeze = float(bv)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_spot_bridge.update_bridge(
|
||||
conn,
|
||||
bridge_id,
|
||||
@@ -520,14 +530,6 @@ def open_coin_option_buy_full(
|
||||
message="期权持仓中",
|
||||
)
|
||||
|
||||
# 开仓价一次冻结:优先买币 VWAP(之后不再改);权利金币量 premium_paid 固定
|
||||
open_px_freeze = idx_open
|
||||
try:
|
||||
bv = _spot_bridge.bridge_buy_vwap(conn, bridge_id)
|
||||
if bv is not None and float(bv) > 0:
|
||||
open_px_freeze = float(bv)
|
||||
except Exception:
|
||||
pass
|
||||
trade_id = _insert_coin_trade(
|
||||
conn,
|
||||
inst_id=inst_id,
|
||||
|
||||
@@ -585,52 +585,48 @@ def sync_options_from_local_trades(
|
||||
|
||||
status_label = "已平"
|
||||
pnl_u = None
|
||||
# 价已冻则只用库内价;缺价才问桥 VWAP(避免刷新改均价)
|
||||
prices_frozen = (
|
||||
open_idx is not None
|
||||
and open_idx > 0
|
||||
and sell_idx is not None
|
||||
and sell_idx > 0
|
||||
# 始终问桥:买/卖回 VWAP 优先于库内指数占位价(防预算反推开仓价、平仓指数冒充卖回价)
|
||||
opx, cpx, _gate = resolve_spot_vwap_prices(
|
||||
conn,
|
||||
bridge_id=r["bridge_id"],
|
||||
open_index_px=open_idx,
|
||||
close_index_px=sell_idx,
|
||||
)
|
||||
use_bridge = None if prices_frozen else r["bridge_id"]
|
||||
pnl_u, pnl_src = resolve_true_pnl_usdt_from_trade(
|
||||
premium_paid=prem,
|
||||
premium_received=r["premium_received"],
|
||||
realized_pnl=r["realized_pnl"],
|
||||
open_index_px=open_idx if (open_idx is not None and open_idx > 0) else None,
|
||||
close_index_px=sell_idx,
|
||||
open_index_px=opx,
|
||||
close_index_px=cpx,
|
||||
open_fee=ofee,
|
||||
close_fee=cfee,
|
||||
premium_ccy=ccy,
|
||||
fee_ccy=str(r["premium_ccy"] or ccy or ""),
|
||||
conn=conn if use_bridge is not None else None,
|
||||
bridge_id=use_bridge,
|
||||
conn=conn,
|
||||
bridge_id=r["bridge_id"],
|
||||
)
|
||||
if pnl_src == "pending_sell":
|
||||
status_label = "待卖回"
|
||||
pnl_u = None
|
||||
elif pnl_u is not None and pnl_src not in ("none", "pending_sell"):
|
||||
try:
|
||||
opx, cpx = open_idx, sell_idx
|
||||
if not prices_frozen:
|
||||
opx, cpx, _g = resolve_spot_vwap_prices(
|
||||
conn,
|
||||
bridge_id=r["bridge_id"],
|
||||
open_index_px=open_idx,
|
||||
close_index_px=sell_idx,
|
||||
)
|
||||
# 真盈亏U每次刷新按固定量+价重算;均价只补缺不覆盖
|
||||
# VWAP 非空时覆盖指数占位;币量字段不改
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE options_trades
|
||||
SET realized_pnl_usdt = ?,
|
||||
open_index_px = COALESCE(open_index_px, ?),
|
||||
close_index_px = COALESCE(close_index_px, ?),
|
||||
open_index_px = COALESCE(?, open_index_px),
|
||||
close_index_px = COALESCE(?, close_index_px),
|
||||
pnl_settled = 1
|
||||
WHERE id = ?
|
||||
""",
|
||||
(pnl_u, opx, cpx, trade_id),
|
||||
)
|
||||
if opx is not None and opx > 0:
|
||||
open_idx = opx
|
||||
open_idx_for_prem = opx
|
||||
if cpx is not None and cpx > 0:
|
||||
sell_idx = cpx
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
|
||||
@@ -778,6 +778,13 @@ def sell_residual_after_option_flat(
|
||||
}
|
||||
recovered = _safe_float(sell.get("usdt_recovered"))
|
||||
sold_amt = _safe_float(sell.get("coin_sold"))
|
||||
# 缺 USDT 回报时用成交价×币量补,保证 close_sell VWAP 能落库
|
||||
if (recovered is None or recovered <= 0) and sold_amt is not None and sold_amt > 0:
|
||||
fill_px = _safe_float(sell.get("fill_px"))
|
||||
if fill_px is not None and fill_px > 0:
|
||||
recovered = round(float(sold_amt) * float(fill_px), 8)
|
||||
sell = dict(sell)
|
||||
sell["usdt_recovered"] = recovered
|
||||
# 平仓后卖回(可多笔):累加 close_sell_* 供卖回 VWAP;开仓多余卖回不走这里
|
||||
add_bridge_close_sell_fill(
|
||||
c,
|
||||
@@ -809,6 +816,7 @@ def sell_residual_after_option_flat(
|
||||
"bridge_id": bid,
|
||||
"bridge_status": BRIDGE_PENDING_SELL,
|
||||
}
|
||||
sell_vwap = bridge_sell_vwap(c, bid)
|
||||
update_bridge(
|
||||
c,
|
||||
bid,
|
||||
@@ -818,13 +826,26 @@ def sell_residual_after_option_flat(
|
||||
message="期权已平,币已卖回 USDT",
|
||||
close=True,
|
||||
)
|
||||
# 卖回 VWAP 写入关联已平仓(覆盖平仓时指数占位价)
|
||||
if sell_vwap is not None and float(sell_vwap) > 0:
|
||||
try:
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE options_trades
|
||||
SET close_index_px = ?
|
||||
WHERE bridge_id = ? AND status = 'closed'
|
||||
""",
|
||||
(float(sell_vwap), int(bid)),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
"ok": True,
|
||||
"sell": sell,
|
||||
"bridge_id": bid,
|
||||
"bridge_status": BRIDGE_CLOSED,
|
||||
"sell_complete": True,
|
||||
"sell_vwap": bridge_sell_vwap(c, bid),
|
||||
"sell_vwap": sell_vwap,
|
||||
}
|
||||
|
||||
if get_db is not None:
|
||||
|
||||
+13
-2
@@ -421,7 +421,11 @@ def _patch_spot_bridge_lib() -> None:
|
||||
if not is_sellable_spot_coin(ex, coin, amt):
|
||||
min_sz = spot_min_sell_sz(ex, coin)
|
||||
return {"ok": False, "msg": f"{coin} 可卖数量过小(低于现货最小 {min_sz:g})"}
|
||||
sell_sz = max(0.0, float(amt) * 0.999)
|
||||
# 平仓卖回(coin_amount=None)一次卖光;限量卖才留粉尘余量
|
||||
if coin_amount is None:
|
||||
sell_sz = float(amt)
|
||||
else:
|
||||
sell_sz = max(0.0, float(amt) * 0.999)
|
||||
if sell_sz <= 0 or not is_sellable_spot_coin(ex, coin, sell_sz):
|
||||
return {"ok": False, "msg": f"{coin} 可卖数量过小"}
|
||||
pub = _sim_public_exchange(ex)
|
||||
@@ -445,13 +449,20 @@ def _patch_spot_bridge_lib() -> None:
|
||||
notify_instance_balance_changed()
|
||||
except Exception:
|
||||
pass
|
||||
recovered = result.get("usdt_recovered")
|
||||
if recovered is None and result.get("fill_px") is not None:
|
||||
try:
|
||||
recovered = round(float(sell_sz) * float(result.get("fill_px")), 8)
|
||||
except (TypeError, ValueError):
|
||||
recovered = None
|
||||
return {
|
||||
"ok": True,
|
||||
"inst_id": result.get("inst_id") or "",
|
||||
"ord_id": f"sim-spot-sell-{result.get('fill_px') or 0}",
|
||||
"coin_sold": float(sell_sz),
|
||||
"fill_px": result.get("fill_px"),
|
||||
"sim": True,
|
||||
"usdt_recovered": result.get("usdt_recovered"),
|
||||
"usdt_recovered": recovered,
|
||||
"data": {"sCode": "0", "sMsg": "sim filled"},
|
||||
}
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user