修复审计P1:基数纠偏用开仓止损并清空币数量、Gate保证金/手动平仓成交价/全仓实盘空仓校验、OKX·Binance占位函数、快照超时。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+72
-15
@@ -2922,6 +2922,39 @@ def get_active_position_count(conn):
|
||||
return int(conn.execute("SELECT COUNT(*) FROM order_monitors WHERE status='active'").fetchone()[0])
|
||||
|
||||
|
||||
def count_nonzero_live_exchange_positions():
|
||||
"""交易所非零持仓条数;API 不可用时返回 None(不阻断,仅 DB 判定)."""
|
||||
if not exchange_private_api_configured():
|
||||
return None
|
||||
try:
|
||||
ensure_markets_loaded()
|
||||
rows = exchange.fetch_positions(None, {"settle": "usdt"}) or []
|
||||
except Exception:
|
||||
try:
|
||||
rows = exchange.fetch_positions() or []
|
||||
except Exception:
|
||||
return None
|
||||
n = 0
|
||||
for p in rows or []:
|
||||
try:
|
||||
if _position_row_effective_contracts(p) > 0:
|
||||
n += 1
|
||||
except Exception:
|
||||
continue
|
||||
return n
|
||||
|
||||
|
||||
def full_margin_flat_check(conn):
|
||||
"""全仓复利开仓前:本地监控仓 + 交易所实盘仓均须为空."""
|
||||
ok, msg = full_margin_requires_flat_position(get_active_position_count(conn))
|
||||
if not ok:
|
||||
return ok, msg
|
||||
ex_n = count_nonzero_live_exchange_positions()
|
||||
if ex_n is not None and ex_n > 0:
|
||||
return False, "交易所仍有持仓,全仓杠杆模式请先平仓后再开"
|
||||
return True, ""
|
||||
|
||||
|
||||
def clear_key_sizing_snapshot_if_flat(conn, session_date):
|
||||
if get_active_position_count(conn) > 0:
|
||||
return
|
||||
@@ -3770,17 +3803,19 @@ def parse_ccxt_position_metrics(position, order_leverage=None):
|
||||
return None
|
||||
p = position
|
||||
info = p.get("info", {}) or {}
|
||||
# Gate 全仓:ccxt 的 initialMargin 常为空;collateral 来自 API 的 margin,与 App「保证金」一致
|
||||
initial = _coerce_float(p.get("collateral"), p.get("initialMargin"), p.get("margin"))
|
||||
# 与 Binance 对齐:优先 initialMargin;collateral 常含未实现盈亏,仅作末位兜底
|
||||
initial = _coerce_float(p.get("initialMargin"), p.get("margin"))
|
||||
if initial is None or initial <= 0:
|
||||
initial = _coerce_float(
|
||||
info.get("margin"),
|
||||
info.get("initial_margin"),
|
||||
info.get("cross_margin"),
|
||||
info.get("iso_margin"),
|
||||
info.get("initial_margin"),
|
||||
info.get("position_margin"),
|
||||
info.get("initialMargin"),
|
||||
info.get("margin"),
|
||||
)
|
||||
if initial is None or initial <= 0:
|
||||
initial = _coerce_float(p.get("collateral"))
|
||||
notional = _coerce_float(p.get("notional"), p.get("notionalValue"))
|
||||
if notional is None or notional <= 0:
|
||||
notional = _coerce_float(info.get("value"))
|
||||
@@ -5354,7 +5389,7 @@ def _add_trigger_entry_key_monitor(
|
||||
if not ok_intent:
|
||||
return False, intent_msg
|
||||
if is_full_margin_mode(POSITION_SIZING_MODE):
|
||||
ok_flat, flat_msg = full_margin_requires_flat_position(get_active_position_count(conn))
|
||||
ok_flat, flat_msg = full_margin_flat_check(conn)
|
||||
if not ok_flat:
|
||||
return False, flat_msg
|
||||
if count_pending_trigger_entries(conn, trading_day) > 0:
|
||||
@@ -5503,7 +5538,7 @@ def _market_open_for_trigger_entry(
|
||||
|
||||
risk_percent = max(0.01, float(RISK_PERCENT))
|
||||
if is_full_margin_mode(POSITION_SIZING_MODE):
|
||||
ok_flat, flat_msg = full_margin_requires_flat_position(get_active_position_count(conn))
|
||||
ok_flat, flat_msg = full_margin_flat_check(conn)
|
||||
if not ok_flat:
|
||||
return False, flat_msg, None
|
||||
leverage = leverage_for_full_margin(symbol, BTC_LEVERAGE, ALT_LEVERAGE)
|
||||
@@ -8662,7 +8697,7 @@ def add_order():
|
||||
risk_percent = max(0.01, float(RISK_PERCENT))
|
||||
risk_amount = round(capital_base * risk_percent / 100.0, 2)
|
||||
if is_full_margin_mode(POSITION_SIZING_MODE):
|
||||
ok_flat, flat_msg = full_margin_requires_flat_position(get_active_position_count(conn))
|
||||
ok_flat, flat_msg = full_margin_flat_check(conn)
|
||||
if not ok_flat:
|
||||
conn.close()
|
||||
flash(flat_msg)
|
||||
@@ -9152,21 +9187,43 @@ def del_order(id):
|
||||
return redirect("/")
|
||||
if row["status"] == "active":
|
||||
try:
|
||||
p = get_price(row["symbol"]) or float(row["trigger_price"])
|
||||
opened_at = get_opened_at_value(row)
|
||||
opened_at_ms = _to_ms_with_fallback(
|
||||
row["opened_at_ms"] if "opened_at_ms" in row.keys() else None, opened_at
|
||||
)
|
||||
margin_capital = row["margin_capital"] or DAILY_START_CAPITAL
|
||||
leverage = row["leverage"] or infer_leverage(row["symbol"])
|
||||
close_resp = close_exchange_order(row)
|
||||
close_order_id = close_resp.get("id", "")
|
||||
cancel_gate_swap_trigger_orders(row["exchange_symbol"] or normalize_exchange_symbol(row["symbol"]))
|
||||
exit_p = extract_trade_price_from_order(close_resp)
|
||||
closed_at = app_now_str()
|
||||
hold_seconds = calc_hold_seconds(opened_at, app_now())
|
||||
if not exit_p or float(exit_p) <= 0:
|
||||
tr_fill = fetch_latest_closing_fill(
|
||||
row["exchange_symbol"] or normalize_exchange_symbol(row["symbol"]),
|
||||
row["direction"],
|
||||
opened_at,
|
||||
opened_at_ms=opened_at_ms,
|
||||
)
|
||||
if tr_fill and tr_fill.get("price"):
|
||||
try:
|
||||
exit_p = float(tr_fill["price"])
|
||||
except (TypeError, ValueError):
|
||||
exit_p = None
|
||||
ts = tr_fill.get("timestamp")
|
||||
if ts:
|
||||
closed_at = ms_to_app_local_str(int(ts))
|
||||
p = exit_p or get_price(row["symbol"]) or float(row["trigger_price"])
|
||||
closed_at_dt = parse_dt_for_trading_day(closed_at) or app_now()
|
||||
hold_seconds = calc_hold_seconds(opened_at, closed_at_dt)
|
||||
pnl_amount = calc_pnl(
|
||||
row["direction"],
|
||||
row["trigger_price"],
|
||||
p,
|
||||
row["margin_capital"] or DAILY_START_CAPITAL,
|
||||
row["leverage"] or infer_leverage(row["symbol"])
|
||||
margin_capital,
|
||||
leverage,
|
||||
)
|
||||
close_resp = close_exchange_order(row)
|
||||
close_order_id = close_resp.get("id", "")
|
||||
cancel_gate_swap_trigger_orders(row["exchange_symbol"] or normalize_exchange_symbol(row["symbol"]))
|
||||
session_date = row["session_date"] or get_trading_day()
|
||||
session_date = row["session_date"] or get_trading_day(closed_at_dt)
|
||||
session_capital = update_session_capital(conn, session_date, pnl_amount)
|
||||
row_snap = conn.execute("SELECT * FROM order_monitors WHERE id=?", (id,)).fetchone() or row
|
||||
insert_trade_record(
|
||||
|
||||
Reference in New Issue
Block a user