diff --git a/modules/trading/install.py b/modules/trading/install.py index 0414944..80d4c05 100644 --- a/modules/trading/install.py +++ b/modules/trading/install.py @@ -59,6 +59,7 @@ from modules.trading.order_pending import ( from modules.core.db_conn import commit_retry, execute_retry from modules.trading.sl_tp_guard import ( cancel_monitor_exit_orders, + clear_close_pending, close_pending_active, ensure_monitor_order_columns, mark_close_pending, @@ -1170,6 +1171,8 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se direction: str, ) -> Optional[dict]: """只读:从已关闭监控补全止盈止损,不写库。""" + if should_skip_monitor_revive(sym, direction): + return {"symbol": sym, "direction": direction} if not mon: rsl, rtp, rtrail, rinitial = _restore_sl_tp_from_closed(conn, sym, direction) if rsl is None and rtp is None and not rtrail: @@ -1197,6 +1200,24 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se merged["initial_stop_loss"] = rinitial return merged + def _row_as_closing_state(row: dict) -> dict: + """手动/程序平仓已提交、柜台持仓未清零时的展示状态。""" + out = dict(row) + out["order_state"] = "closing" + out["source_label"] = "平仓处理中" + out["stop_loss"] = None + out["take_profit"] = None + out["sl_monitoring"] = False + out["tp_monitoring"] = False + out["sl_order_active"] = False + out["tp_order_active"] = False + out["pending_orders"] = [] + out["can_close"] = False + out["close_allowed"] = False + out["can_place_orders"] = False + out["trailing_be"] = False + return out + def _revive_closed_monitor(conn, symbol: str, direction: str) -> Optional[dict]: """柜台仍有持仓但本地监控被误关时,恢复最近一条同品种记录。""" if should_skip_monitor_revive(symbol, direction): @@ -2413,12 +2434,12 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se direction = p.get("direction") or "long" if not mon: mon = _find_pending_monitor(conn, ths, direction) - if not mon: + if not mon and not close_pending_active(ths, direction): if fast: mon = _find_active_monitor(conn, ths, direction) else: mon = _find_or_revive_monitor(conn, ths, direction) - if mon: + if mon and not close_pending_active(ths, direction): if fast: mon = _overlay_sl_tp_readonly(conn, mon, ths, direction) or mon else: @@ -2432,14 +2453,18 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se conn, mon.get("symbol") or ths, mon.get("direction") or direction, ) or mon mon = _restore_monitor_sl_tp_if_missing(conn, mon, ths, direction) or mon - elif fast: + elif fast and not close_pending_active(ths, direction): mon = _overlay_sl_tp_readonly(conn, None, ths, direction) + elif close_pending_active(ths, direction): + mon = mon or {"symbol": ths, "direction": direction} try: row = _compose_position_row( conn, mon=mon, ctp=p, mode=mode, capital=capital, now_iso=now_iso, fast=fast, ) if row: + if close_pending_active(ths, direction): + row = _row_as_closing_state(row) rows.append(row) except Exception as exc: logger.warning("compose ctp position row failed: %s", exc) @@ -3433,14 +3458,36 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se _close_all_monitors_for_sym_dir(conn, sym, direction) conn.commit() try: - from modules.ctp.ctp_trade_sync import sync_trade_logs_from_ctp - sync_trade_logs_from_ctp(conn, mode, capital=capital, trading_mode=mode) + on_user_initiated_close(conn, trading_day=trading_day_label()) conn.commit() except Exception as exc: - logger.debug("sync trades after close: %s", exc) + logger.debug("user initiated close hook: %s", exc) + cap_snapshot = capital conn.close() - _push_position_snapshot_async() - return jsonify({"ok": True, "message": "已平仓,交易记录已写入"}) + + def _after_close() -> None: + try: + bg = get_db() + try: + init_strategy_tables(bg) + from modules.ctp.ctp_trade_sync import sync_trade_logs_from_ctp + sync_trade_logs_from_ctp( + bg, mode, capital=cap_snapshot, trading_mode=mode, + ) + bg.commit() + finally: + bg.close() + except Exception as exc: + logger.debug("sync trades after close: %s", exc) + _push_position_snapshot_async(fast=True) + + threading.Thread(target=_after_close, daemon=True, name="close-finalize").start() + _push_position_snapshot_async(fast=True) + return jsonify({ + "ok": True, + "message": "平仓委托已提交", + "closing": True, + }) except ValueError as exc: conn.close() return jsonify({"ok": False, "error": str(exc)}), 400 diff --git a/modules/web/static/js/trade.js b/modules/web/static/js/trade.js index 82bf369..5dee4fe 100644 --- a/modules/web/static/js/trade.js +++ b/modules/web/static/js/trade.js @@ -1232,10 +1232,32 @@ ); } + function buildClosingCard(row) { + var dirBadge = row.direction_label || (row.direction === 'long' ? '做多' : '做空'); + var openT = (row.open_time || '').replace('T', ' ').slice(0, 16); + return ( + '