From f5104fe68bed38ae50ddf3b77e6f8a9e32b45be4 Mon Sep 17 00:00:00 2001 From: dekun Date: Wed, 16 Sep 2026 05:48:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BF=BB=E5=80=8D/=E7=9B=AE?= =?UTF-8?q?=E6=A0=87=E4=BD=8D=E5=B9=B3=E4=BB=93=E6=8A=8A=E5=A4=87=E6=B3=A8?= =?UTF-8?q?=E5=86=99=E8=BF=9B=E5=B9=B3=E4=BB=93=E6=97=B6=E9=97=B4=EF=BC=9A?= =?UTF-8?q?=E7=BA=A0=E6=AD=A3=20SQL=20=E5=8F=82=E6=95=B0=E9=A1=BA=E5=BA=8F?= =?UTF-8?q?=E5=B9=B6=E8=87=AA=E5=8A=A8=E5=AF=B9=E8=B0=83=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E5=9D=8F=E6=95=B0=E6=8D=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- lib/options/options_close_exec_lib.py | 8 ++-- lib/options/options_review_lib.py | 7 ++- lib/options/options_time_lib.py | 61 +++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/lib/options/options_close_exec_lib.py b/lib/options/options_close_exec_lib.py index 56ddea6..add5082 100644 --- a/lib/options/options_close_exec_lib.py +++ b/lib/options/options_close_exec_lib.py @@ -355,7 +355,10 @@ def close_option_by_bid1( allocated += recv pnl = round(recv - paid, 4) note_sql = "" - params: list[Any] = [px, recv, pnl, oid or None] + from lib.options.options_time_lib import options_app_now_str + + # 占位顺序必须与 SQL 一致: close_quote/recv/pnl/ord/closed_at/[signal_note]/id + params: list[Any] = [px, recv, pnl, oid or None, options_app_now_str()] if signal_note and i == len(open_rows) - 1: note_sql = """, signal_note = CASE @@ -363,9 +366,6 @@ def close_option_by_bid1( ELSE signal_note END""" params.append(signal_note) - from lib.options.options_time_lib import options_app_now_str - - params.append(options_app_now_str()) params.append(int(row["id"])) conn.execute( f""" diff --git a/lib/options/options_review_lib.py b/lib/options/options_review_lib.py index cbdd0ab..335ed42 100644 --- a/lib/options/options_review_lib.py +++ b/lib/options/options_review_lib.py @@ -643,9 +643,13 @@ def ensure_local_review_synced( backfill_exchange_pnl: bool = True, ) -> dict[str, Any]: """列表/统计前轻量刷新本地源;有交易所时先用历史仓位盈亏覆盖本地再导入复盘.""" - from lib.options.options_time_lib import migrate_options_utc_wall_clock_to_app + from lib.options.options_time_lib import ( + migrate_options_utc_wall_clock_to_app, + repair_swapped_closed_at_signal_note, + ) ts_migrate = migrate_options_utc_wall_clock_to_app(conn) + note_swap = repair_swapped_closed_at_signal_note(conn) if backfill_exchange_pnl and ex is not None: try: from lib.exchange.okx_options_lib import fetch_all_option_positions_history @@ -664,6 +668,7 @@ def ensure_local_review_synced( out = sync_all_review_sources(conn, ex=ex, from_exchange=False) if isinstance(out, dict): out["ts_migrate"] = ts_migrate + out["note_swap"] = note_swap return out diff --git a/lib/options/options_time_lib.py b/lib/options/options_time_lib.py index 83b2fcb..39af29f 100644 --- a/lib/options/options_time_lib.py +++ b/lib/options/options_time_lib.py @@ -13,6 +13,7 @@ _APP_TZ = ZoneInfo( or "Asia/Shanghai" ) _MIGRATE_FLAG = "options_ts_utc_wall_to_app_done" +_SWAP_REPAIR_FLAG = "options_closed_at_note_swap_done" def options_app_tz() -> ZoneInfo: @@ -119,3 +120,63 @@ def migrate_options_utc_wall_clock_to_app(conn: sqlite3.Connection) -> dict[str, set_sync_state(conn, _MIGRATE_FLAG, "1") return {"skipped": 0, "trades": trades, "review": review, "hidden": hidden} + + +def repair_swapped_closed_at_signal_note(conn: sqlite3.Connection) -> dict[str, int]: + """修复 close_option_by_bid1 曾把 signal_note 写入 closed_at 的坏行. + + 典型表现: closed_at='翻倍出场'/'目标位平仓', signal_note 反而是时间戳. + 幂等:同步状态 options_closed_at_note_swap_done=1 后仍扫描可修行(新坏行可再修). + """ + from lib.options.options_review_db import init_options_review_tables + from lib.options.options_review_lib import set_sync_state + + init_options_review_tables(conn) + trades_fixed = 0 + review_fixed = 0 + try: + from lib.options.options_db import init_options_tables + + init_options_tables(conn) + rows = conn.execute( + """ + SELECT id, closed_at, signal_note + FROM options_trades + WHERE status = 'closed' + AND closed_at IS NOT NULL + AND TRIM(closed_at) != '' + """ + ).fetchall() + for row in rows: + closed = str(row["closed_at"] or "").strip() + note = str(row["signal_note"] or "").strip() + if options_wall_clock_to_ms(closed) is not None: + continue + if options_wall_clock_to_ms(note) is None: + continue + conn.execute( + """ + UPDATE options_trades + SET closed_at = ?, signal_note = ? + WHERE id = ? + """, + (note, closed, int(row["id"])), + ) + trades_fixed += 1 + hk = f"local_opt:{int(row['id'])}" + cur = conn.execute( + """ + UPDATE options_review_trades + SET closed_at = ? + WHERE history_key = ? + AND (closed_at IS NULL OR TRIM(closed_at) = '' OR closed_at = ?) + """, + (note, hk, closed), + ) + review_fixed += int(cur.rowcount or 0) + except Exception: + pass + + # 复盘表里仍残留非时间戳 closed_at 的,若能从 local_opt 源修好则已处理;否则仅标记完成 + set_sync_state(conn, _SWAP_REPAIR_FLAG, "1") + return {"trades": trades_fixed, "review": review_fixed}