fix多笔买/卖回用VWAP计入真盈亏,卖完再结算并标记待卖回。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -96,6 +96,153 @@ def ensure_bridge_table(conn: sqlite3.Connection) -> None:
|
||||
ON options_spot_bridge(status)
|
||||
"""
|
||||
)
|
||||
# 多笔买卖 VWAP:买侧累加(含缓冲);平仓后卖回单独累加(不含开仓多余卖回)
|
||||
for ddl in (
|
||||
"ALTER TABLE options_spot_bridge ADD COLUMN buy_usdt_spent REAL",
|
||||
"ALTER TABLE options_spot_bridge ADD COLUMN buy_coin_accum REAL",
|
||||
"ALTER TABLE options_spot_bridge ADD COLUMN close_sell_coin REAL",
|
||||
"ALTER TABLE options_spot_bridge ADD COLUMN close_sell_usdt REAL",
|
||||
):
|
||||
try:
|
||||
conn.execute(ddl)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def add_bridge_buy_fill(
|
||||
conn: sqlite3.Connection,
|
||||
bridge_id: int,
|
||||
*,
|
||||
usdt_spent: float | None,
|
||||
coin_bought: float | None,
|
||||
) -> None:
|
||||
"""累加开仓买币成交,供买侧 VWAP=ΣUSDT/Σ币."""
|
||||
ensure_bridge_table(conn)
|
||||
u = _safe_float(usdt_spent)
|
||||
c = _safe_float(coin_bought)
|
||||
if bridge_id is None or u is None or c is None or u <= 0 or c <= 0:
|
||||
return
|
||||
row = conn.execute(
|
||||
"SELECT buy_usdt_spent, buy_coin_accum FROM options_spot_bridge WHERE id=?",
|
||||
(int(bridge_id),),
|
||||
).fetchone()
|
||||
if not row:
|
||||
return
|
||||
prev_u = _safe_float(row["buy_usdt_spent"] if hasattr(row, "keys") else row[0]) or 0.0
|
||||
prev_c = _safe_float(row["buy_coin_accum"] if hasattr(row, "keys") else row[1]) or 0.0
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE options_spot_bridge
|
||||
SET buy_usdt_spent = ?, buy_coin_accum = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
""",
|
||||
(round(prev_u + float(u), 8), round(prev_c + float(c), 8), int(bridge_id)),
|
||||
)
|
||||
|
||||
|
||||
def add_bridge_close_sell_fill(
|
||||
conn: sqlite3.Connection,
|
||||
bridge_id: int,
|
||||
*,
|
||||
usdt_recovered: float | None,
|
||||
coin_sold: float | None,
|
||||
) -> None:
|
||||
"""累加平仓后卖回成交(多笔),供卖回 VWAP=ΣUSDT/Σ币;同时累加 usdt_recovered."""
|
||||
ensure_bridge_table(conn)
|
||||
u = _safe_float(usdt_recovered)
|
||||
c = _safe_float(coin_sold)
|
||||
if bridge_id is None:
|
||||
return
|
||||
if u is not None and u > 0:
|
||||
add_bridge_usdt_recovered(conn, int(bridge_id), u)
|
||||
if (u is None or u <= 0) and (c is None or c <= 0):
|
||||
return
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT close_sell_usdt, close_sell_coin, coin_sold
|
||||
FROM options_spot_bridge WHERE id=?
|
||||
""",
|
||||
(int(bridge_id),),
|
||||
).fetchone()
|
||||
if not row:
|
||||
return
|
||||
prev_u = _safe_float(row["close_sell_usdt"] if hasattr(row, "keys") else row[0]) or 0.0
|
||||
prev_c = _safe_float(row["close_sell_coin"] if hasattr(row, "keys") else row[1]) or 0.0
|
||||
prev_sold = _safe_float(row["coin_sold"] if hasattr(row, "keys") else row[2]) or 0.0
|
||||
new_u = prev_u + (float(u) if u is not None and u > 0 else 0.0)
|
||||
new_c = prev_c + (float(c) if c is not None and c > 0 else 0.0)
|
||||
new_sold = prev_sold + (float(c) if c is not None and c > 0 else 0.0)
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE options_spot_bridge
|
||||
SET close_sell_usdt = ?, close_sell_coin = ?, coin_sold = ?,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
""",
|
||||
(round(new_u, 8), round(new_c, 8), round(new_sold, 8), int(bridge_id)),
|
||||
)
|
||||
|
||||
|
||||
def bridge_buy_vwap(conn: sqlite3.Connection, bridge_id: int | None) -> float | None:
|
||||
"""开仓买币 VWAP(USDT/币);多笔加权.忽略后续把 coin_bought 改成权利金量."""
|
||||
if bridge_id is None:
|
||||
return None
|
||||
ensure_bridge_table(conn)
|
||||
row = conn.execute(
|
||||
"SELECT buy_usdt_spent, buy_coin_accum, budget_usdt, coin_bought FROM options_spot_bridge WHERE id=?",
|
||||
(int(bridge_id),),
|
||||
).fetchone()
|
||||
if not row:
|
||||
return None
|
||||
spent = _safe_float(row["buy_usdt_spent"] if hasattr(row, "keys") else row[0])
|
||||
coins = _safe_float(row["buy_coin_accum"] if hasattr(row, "keys") else row[1])
|
||||
if spent is not None and coins is not None and spent > 0 and coins > 0:
|
||||
return round(float(spent) / float(coins), 8)
|
||||
# 旧桥无累加字段:用预算/买入量近似单笔
|
||||
budget = _safe_float(row["budget_usdt"] if hasattr(row, "keys") else row[2])
|
||||
bought = _safe_float(row["coin_bought"] if hasattr(row, "keys") else row[3])
|
||||
if budget is not None and bought is not None and budget > 0 and bought > 0:
|
||||
return round(float(budget) / float(bought), 8)
|
||||
return None
|
||||
|
||||
|
||||
def bridge_sell_vwap(conn: sqlite3.Connection, bridge_id: int | None) -> float | None:
|
||||
"""平仓后卖回 VWAP(USDT/币);仅 close_sell_* 多笔加权,不含开仓多余卖回."""
|
||||
if bridge_id is None:
|
||||
return None
|
||||
ensure_bridge_table(conn)
|
||||
row = conn.execute(
|
||||
"SELECT close_sell_usdt, close_sell_coin, usdt_recovered, coin_sold, status FROM options_spot_bridge WHERE id=?",
|
||||
(int(bridge_id),),
|
||||
).fetchone()
|
||||
if not row:
|
||||
return None
|
||||
u = _safe_float(row["close_sell_usdt"] if hasattr(row, "keys") else row[0])
|
||||
c = _safe_float(row["close_sell_coin"] if hasattr(row, "keys") else row[1])
|
||||
if u is not None and c is not None and u > 0 and c > 0:
|
||||
return round(float(u) / float(c), 8)
|
||||
# 旧桥无分拆字段且已关桥:用总收回/总卖出近似(可能含开仓多余卖回)
|
||||
st = str(row["status"] if hasattr(row, "keys") else row[4] or "")
|
||||
if st == BRIDGE_CLOSED:
|
||||
ru = _safe_float(row["usdt_recovered"] if hasattr(row, "keys") else row[2])
|
||||
sc = _safe_float(row["coin_sold"] if hasattr(row, "keys") else row[3])
|
||||
if ru is not None and sc is not None and ru > 0 and sc > 0:
|
||||
return round(float(ru) / float(sc), 8)
|
||||
return None
|
||||
|
||||
|
||||
def bridge_sell_complete(conn: sqlite3.Connection, bridge_id: int | None) -> bool:
|
||||
"""平仓卖回是否已完成(桥已关闭)."""
|
||||
if bridge_id is None:
|
||||
return False
|
||||
ensure_bridge_table(conn)
|
||||
row = conn.execute(
|
||||
"SELECT status FROM options_spot_bridge WHERE id=?",
|
||||
(int(bridge_id),),
|
||||
).fetchone()
|
||||
if not row:
|
||||
return False
|
||||
return str(row["status"] if hasattr(row, "keys") else row[0] or "") == BRIDGE_CLOSED
|
||||
|
||||
|
||||
def list_open_bridges(conn: sqlite3.Connection) -> list[dict[str, Any]]:
|
||||
@@ -546,11 +693,37 @@ def sell_residual_after_option_flat(
|
||||
|
||||
# 一次一仓空仓后卖光可用(含盈利币);禁止用 coin_bought/权利金封顶
|
||||
# 此处无业务连接:模拟盘 convert 可安全另开写钱包
|
||||
usdt_before_sell = None
|
||||
try:
|
||||
from lib.options.options_account_pnl_lib import fetch_trading_usdt
|
||||
|
||||
usdt_before_sell = fetch_trading_usdt({"fetch_options_balances": None}, ex)
|
||||
except Exception:
|
||||
usdt_before_sell = None
|
||||
sell = spot_market_sell_coin_to_usdt(
|
||||
ex,
|
||||
underlying=underlying,
|
||||
coin_amount=None,
|
||||
)
|
||||
# 实盘订单回报常无 usdt_recovered:用交易账户 USDT 增量补
|
||||
if sell.get("ok") and _safe_float(sell.get("usdt_recovered")) is None:
|
||||
try:
|
||||
from lib.exchange.okx_options_lib import invalidate_options_balance_cache
|
||||
from lib.options.options_account_pnl_lib import fetch_trading_usdt
|
||||
|
||||
invalidate_options_balance_cache()
|
||||
usdt_after_sell = fetch_trading_usdt({"fetch_options_balances": None}, ex)
|
||||
if (
|
||||
usdt_before_sell is not None
|
||||
and usdt_after_sell is not None
|
||||
and float(usdt_after_sell) > float(usdt_before_sell)
|
||||
):
|
||||
sell = dict(sell)
|
||||
sell["usdt_recovered"] = round(
|
||||
float(usdt_after_sell) - float(usdt_before_sell), 8
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
if bid is None:
|
||||
if not sell.get("ok"):
|
||||
msg = str(sell.get("msg") or "")
|
||||
@@ -609,8 +782,14 @@ def sell_residual_after_option_flat(
|
||||
"sell_complete": False,
|
||||
}
|
||||
recovered = _safe_float(sell.get("usdt_recovered"))
|
||||
if recovered is not None and recovered > 0:
|
||||
add_bridge_usdt_recovered(c, bid, recovered)
|
||||
sold_amt = _safe_float(sell.get("coin_sold"))
|
||||
# 平仓后卖回(可多笔):累加 close_sell_* 供卖回 VWAP;开仓多余卖回不走这里
|
||||
add_bridge_close_sell_fill(
|
||||
c,
|
||||
bid,
|
||||
usdt_recovered=recovered,
|
||||
coin_sold=sold_amt,
|
||||
)
|
||||
# 卖后仍有可卖 → 不关桥、不结算(盈利币未卖回时曾误冻亏损)
|
||||
try:
|
||||
left = fetch_trading_coin_available(ex, underlying)
|
||||
@@ -623,7 +802,6 @@ def sell_residual_after_option_flat(
|
||||
bid,
|
||||
status=BRIDGE_PENDING_SELL,
|
||||
sell_ord_id=str(sell.get("ord_id") or ""),
|
||||
coin_sold=_safe_float(sell.get("coin_sold")),
|
||||
inst_id=inst_id,
|
||||
message=left_msg,
|
||||
)
|
||||
@@ -641,7 +819,6 @@ def sell_residual_after_option_flat(
|
||||
bid,
|
||||
status=BRIDGE_CLOSED,
|
||||
sell_ord_id=str(sell.get("ord_id") or ""),
|
||||
coin_sold=_safe_float(sell.get("coin_sold")),
|
||||
inst_id=inst_id,
|
||||
message="期权已平,币已卖回 USDT",
|
||||
close=True,
|
||||
@@ -652,6 +829,7 @@ def sell_residual_after_option_flat(
|
||||
"bridge_id": bid,
|
||||
"bridge_status": BRIDGE_CLOSED,
|
||||
"sell_complete": True,
|
||||
"sell_vwap": bridge_sell_vwap(c, bid),
|
||||
}
|
||||
|
||||
if get_db is not None:
|
||||
|
||||
Reference in New Issue
Block a user