diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 6ead378..66f8490 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -2440,6 +2440,11 @@ def insert_trade_record( opened_at_ms=open_ts_ms, closed_at_ms=close_ts_ms, ) + # 中控只拉 /api/trade_records,平仓当下也尝试回填交易所盈亏(内部 25s 节流) + try: + sync_trade_records_from_exchange(conn, force=False) + except Exception: + pass return tid @@ -6942,7 +6947,11 @@ def sync_trade_records_from_exchange(conn, force=False): matched += 1 stats["matched"] = matched stats["ok"] = True - _LAST_EXCHANGE_PNL_SYNC_AT = now + # 仍有未匹配且历史非空:缩短节流,避免平仓后历史稍晚入库时卡在「估」 + if matched < stats["pending"] and hist: + _LAST_EXCHANGE_PNL_SYNC_AT = now - 15.0 + else: + _LAST_EXCHANGE_PNL_SYNC_AT = now try: conn.commit() except Exception: @@ -9378,6 +9387,7 @@ register_trade_records_api( filter_trade_records_excluding_miss=filter_trade_records_excluding_miss, app_tz=APP_TZ, format_price_fn=format_price_for_symbol, + sync_exchange_pnl_fn=lambda conn: sync_trade_records_from_exchange(conn, force=False), ) def _dashboard_enrich_orders(items): diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index cd93061..6d81610 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -2359,6 +2359,11 @@ def insert_trade_record( opened_at_ms=open_ts_ms, closed_at_ms=close_ts_ms, ) + # 中控只拉 /api/trade_records,平仓当下也尝试回填交易所盈亏(内部 25s 节流) + try: + sync_trade_records_from_exchange(conn, force=False) + except Exception: + pass return tid @@ -4090,7 +4095,11 @@ def sync_trade_records_from_exchange(conn, force=False): matched += 1 stats["matched"] = matched stats["ok"] = True - _LAST_EXCHANGE_PNL_SYNC_AT = now + # 仍有未匹配且历史非空:缩短节流,避免平仓后历史稍晚入库时卡在「估」 + if matched < stats["pending"] and hist: + _LAST_EXCHANGE_PNL_SYNC_AT = now - 15.0 + else: + _LAST_EXCHANGE_PNL_SYNC_AT = now try: conn.commit() except Exception: @@ -9066,6 +9075,7 @@ register_trade_records_api( filter_trade_records_excluding_miss=filter_trade_records_excluding_miss, app_tz=APP_TZ, format_price_fn=format_price_for_symbol, + sync_exchange_pnl_fn=lambda conn: sync_trade_records_from_exchange(conn, force=False), ) diff --git a/lib/instance/records_api_register.py b/lib/instance/records_api_register.py index dd96ffd..ee05ae3 100644 --- a/lib/instance/records_api_register.py +++ b/lib/instance/records_api_register.py @@ -19,7 +19,12 @@ def register_trade_records_api( filter_trade_records_excluding_miss: Callable[[list], list], app_tz: Any, format_price_fn: Callable[[Any, Any], str] | None = None, + sync_exchange_pnl_fn: Callable[[Any], Any] | None = None, ) -> None: + """ + sync_exchange_pnl_fn(conn): 可选,列表前节流回填交易所已实现盈亏. + 中控只走本 API,不经实例整页渲染,必须在此触发,否则盈亏U会一直显示「估」. + """ from lib.instance.records_list_lib import list_trade_records_page @app.route("/api/trade_records") @@ -40,6 +45,11 @@ def register_trade_records_api( offset = 0 conn = get_db() try: + if sync_exchange_pnl_fn is not None: + try: + sync_exchange_pnl_fn(conn) + except Exception: + pass payload = list_trade_records_page( conn, start_bj, diff --git a/tests/test_records_api_sync_hook.py b/tests/test_records_api_sync_hook.py new file mode 100644 index 0000000..2fdc721 --- /dev/null +++ b/tests/test_records_api_sync_hook.py @@ -0,0 +1,50 @@ +"""records_api_register: list API can trigger exchange pnl sync.""" + +from __future__ import annotations + +import unittest +from unittest.mock import MagicMock + +from flask import Flask + +from lib.instance.records_api_register import register_trade_records_api + + +class RecordsApiSyncHookTest(unittest.TestCase): + def test_list_calls_sync_hook(self): + app = Flask(__name__) + sync_calls = [] + + def _login(fn): + return fn + + def _get_db(): + conn = MagicMock() + conn.execute.return_value.fetchall.return_value = [] + return conn + + def _win(): + return {"start_utc": None, "end_utc": None} + + register_trade_records_api( + app, + login_required=_login, + get_db=_get_db, + list_window_from_request=_win, + utc_window_to_bj_sql_strings=lambda *a, **k: ("2026-01-01", "2026-12-31"), + sql_list_time_field=lambda *a, **k: "COALESCE(closed_at, created_at, opened_at)", + to_effective_trade_dict=lambda r: dict(r) if hasattr(r, "keys") else r, + filter_trade_records_excluding_miss=lambda xs: xs, + app_tz=None, + sync_exchange_pnl_fn=lambda conn: sync_calls.append(conn), + ) + client = app.test_client() + resp = client.get("/api/trade_records") + self.assertEqual(resp.status_code, 200) + self.assertEqual(len(sync_calls), 1) + body = resp.get_json() + self.assertTrue(body.get("ok")) + + +if __name__ == "__main__": + unittest.main()