diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index 371a2f3..575292e 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -1857,7 +1857,7 @@ def _compute_period_metrics(trades): } -def compute_stats_bundle(conn, trading_day, now_dt=None): +def compute_stats_bundle(conn, trading_day, now_dt=None, *, with_calendar=False): """日 / 周 / 月 统计:平仓按北京时间交易日(默认 8:00 切日)计入。""" now_dt = now_dt or app_now() pnls = _load_completed_trade_pnls(conn) @@ -1888,11 +1888,14 @@ def compute_stats_bundle(conn, trading_day, now_dt=None): dm, wm, mm = slice_metrics("all") - from trade_stats_calendar_lib import build_initial_stats_calendar + initial_calendar = None + calendar_bootstrap_json = None + if with_calendar: + from trade_stats_calendar_lib import build_stats_calendar_bootstrap - initial_calendar = build_initial_stats_calendar( - pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR - ) + initial_calendar, calendar_bootstrap_json = build_stats_calendar_bootstrap( + pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR + ) return { "trading_day": trading_day, @@ -1903,6 +1906,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None): "segments": segments, "stats_reset_hour": TRADING_DAY_RESET_HOUR, "initial_calendar": initial_calendar, + "calendar_bootstrap_json": calendar_bootstrap_json, } @@ -7040,7 +7044,7 @@ def render_main_page(page="trade", embed_mode=None): else [] ) stats_bundle = ( - compute_stats_bundle(conn, trading_day, now) + compute_stats_bundle(conn, trading_day, now, with_calendar=(page == "stats")) if plan.stats_bundle else minimal_stats_bundle(TRADING_DAY_RESET_HOUR) ) diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index db01ef8..0bc5ff8 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -807,8 +807,8 @@ - {% if stats_bundle.initial_calendar %} - + {% if stats_bundle.calendar_bootstrap_json %} + {% endif %}
diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 1c6257c..299d9e8 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -1846,7 +1846,7 @@ def _compute_period_metrics(trades): } -def compute_stats_bundle(conn, trading_day, now_dt=None): +def compute_stats_bundle(conn, trading_day, now_dt=None, *, with_calendar=False): """日 / 周 / 月 统计:平仓按北京时间交易日(默认 8:00 切日)计入。""" now_dt = now_dt or app_now() pnls = _load_completed_trade_pnls(conn) @@ -1883,11 +1883,14 @@ def compute_stats_bundle(conn, trading_day, now_dt=None): dm, wm, mm = slice_metrics("all") - from trade_stats_calendar_lib import build_initial_stats_calendar + initial_calendar = None + calendar_bootstrap_json = None + if with_calendar: + from trade_stats_calendar_lib import build_stats_calendar_bootstrap - initial_calendar = build_initial_stats_calendar( - pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR - ) + initial_calendar, calendar_bootstrap_json = build_stats_calendar_bootstrap( + pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR + ) return { "trading_day": trading_day, @@ -1898,6 +1901,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None): "segments": segments, "stats_reset_hour": TRADING_DAY_RESET_HOUR, "initial_calendar": initial_calendar, + "calendar_bootstrap_json": calendar_bootstrap_json, } @@ -6882,7 +6886,7 @@ def render_main_page(page="trade", embed_mode=None): else [] ) stats_bundle = ( - compute_stats_bundle(conn, trading_day, now) + compute_stats_bundle(conn, trading_day, now, with_calendar=(page == "stats")) if plan.stats_bundle else minimal_stats_bundle(TRADING_DAY_RESET_HOUR) ) diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index ad44612..f6f69bb 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -774,8 +774,8 @@
- {% if stats_bundle.initial_calendar %} - + {% if stats_bundle.calendar_bootstrap_json %} + {% endif %}
diff --git a/crypto_monitor_gate_bot/app.py b/crypto_monitor_gate_bot/app.py index 21e8b41..1f6e486 100644 --- a/crypto_monitor_gate_bot/app.py +++ b/crypto_monitor_gate_bot/app.py @@ -1846,7 +1846,7 @@ def _compute_period_metrics(trades): } -def compute_stats_bundle(conn, trading_day, now_dt=None): +def compute_stats_bundle(conn, trading_day, now_dt=None, *, with_calendar=False): """日 / 周 / 月 统计:平仓按北京时间交易日(默认 8:00 切日)计入。""" now_dt = now_dt or app_now() pnls = _load_completed_trade_pnls(conn) @@ -1883,11 +1883,14 @@ def compute_stats_bundle(conn, trading_day, now_dt=None): dm, wm, mm = slice_metrics("all") - from trade_stats_calendar_lib import build_initial_stats_calendar + initial_calendar = None + calendar_bootstrap_json = None + if with_calendar: + from trade_stats_calendar_lib import build_stats_calendar_bootstrap - initial_calendar = build_initial_stats_calendar( - pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR - ) + initial_calendar, calendar_bootstrap_json = build_stats_calendar_bootstrap( + pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR + ) return { "trading_day": trading_day, @@ -1898,6 +1901,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None): "segments": segments, "stats_reset_hour": TRADING_DAY_RESET_HOUR, "initial_calendar": initial_calendar, + "calendar_bootstrap_json": calendar_bootstrap_json, } @@ -6882,7 +6886,7 @@ def render_main_page(page="trade", embed_mode=None): else [] ) stats_bundle = ( - compute_stats_bundle(conn, trading_day, now) + compute_stats_bundle(conn, trading_day, now, with_calendar=(page == "stats")) if plan.stats_bundle else minimal_stats_bundle(TRADING_DAY_RESET_HOUR) ) diff --git a/crypto_monitor_gate_bot/templates/index.html b/crypto_monitor_gate_bot/templates/index.html index ad44612..f6f69bb 100644 --- a/crypto_monitor_gate_bot/templates/index.html +++ b/crypto_monitor_gate_bot/templates/index.html @@ -774,8 +774,8 @@
- {% if stats_bundle.initial_calendar %} - + {% if stats_bundle.calendar_bootstrap_json %} + {% endif %}
diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 3cd1331..37030be 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -1830,7 +1830,7 @@ def _compute_period_metrics(trades): } -def compute_stats_bundle(conn, trading_day, now_dt=None): +def compute_stats_bundle(conn, trading_day, now_dt=None, *, with_calendar=False): """日 / 周 / 月 统计:平仓按北京时间交易日(默认 8:00 切日)计入。""" now_dt = now_dt or app_now() pnls = _load_completed_trade_pnls(conn) @@ -1861,11 +1861,14 @@ def compute_stats_bundle(conn, trading_day, now_dt=None): dm, wm, mm = slice_metrics("all") - from trade_stats_calendar_lib import build_initial_stats_calendar + initial_calendar = None + calendar_bootstrap_json = None + if with_calendar: + from trade_stats_calendar_lib import build_stats_calendar_bootstrap - initial_calendar = build_initial_stats_calendar( - pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR - ) + initial_calendar, calendar_bootstrap_json = build_stats_calendar_bootstrap( + pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR + ) return { "trading_day": trading_day, @@ -1876,6 +1879,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None): "segments": segments, "stats_reset_hour": TRADING_DAY_RESET_HOUR, "initial_calendar": initial_calendar, + "calendar_bootstrap_json": calendar_bootstrap_json, } @@ -6381,7 +6385,7 @@ def render_main_page(page="trade", embed_mode=None): else [] ) stats_bundle = ( - compute_stats_bundle(conn, trading_day, now) + compute_stats_bundle(conn, trading_day, now, with_calendar=(page == "stats")) if plan.stats_bundle else minimal_stats_bundle(TRADING_DAY_RESET_HOUR) ) diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index 024f3fb..c8a8919 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -803,8 +803,8 @@
- {% if stats_bundle.initial_calendar %} - + {% if stats_bundle.calendar_bootstrap_json %} + {% endif %}
diff --git a/embed_templates/embed_page_fragment.html b/embed_templates/embed_page_fragment.html index 5edb643..52c6ab9 100644 --- a/embed_templates/embed_page_fragment.html +++ b/embed_templates/embed_page_fragment.html @@ -452,8 +452,8 @@
- {% if stats_bundle.initial_calendar %} - + {% if stats_bundle.calendar_bootstrap_json %} + {% endif %}
diff --git a/instance_embed_context_lib.py b/instance_embed_context_lib.py index 9b774a2..2bbfd89 100644 --- a/instance_embed_context_lib.py +++ b/instance_embed_context_lib.py @@ -85,4 +85,5 @@ def minimal_stats_bundle(reset_hour: int) -> dict[str, Any]: "stats_reset_hour": reset_hour, "segments": [], "initial_calendar": None, + "calendar_bootstrap_json": None, } diff --git a/tests/test_trade_stats_calendar_lib.py b/tests/test_trade_stats_calendar_lib.py index 61375b0..10a191c 100644 --- a/tests/test_trade_stats_calendar_lib.py +++ b/tests/test_trade_stats_calendar_lib.py @@ -3,7 +3,11 @@ from types import SimpleNamespace from datetime import datetime -from trade_stats_calendar_lib import build_initial_stats_calendar, build_trade_stats_calendar +from trade_stats_calendar_lib import ( + build_initial_stats_calendar, + build_stats_calendar_bootstrap, + build_trade_stats_calendar, +) def _row(**kwargs): @@ -69,6 +73,18 @@ class TradeStatsCalendarLibTests(unittest.TestCase): self.assertEqual(payload["month_open_count"], 1) self.assertIn("2026-06-20", payload["days"]) + def test_bootstrap_json_roundtrip(self): + pnls = [(2.5, None, "2026-06-20", _row())] + payload, raw = build_stats_calendar_bootstrap( + pnls, + datetime(2026, 6, 26, 12, 0), + _matches_all, + reset_hour=8, + ) + self.assertIsNotNone(payload) + self.assertIsNotNone(raw) + self.assertIn('"month_open_count":1', raw.replace(" ", "")) + if __name__ == "__main__": unittest.main() diff --git a/trade_stats_calendar_lib.py b/trade_stats_calendar_lib.py index a2f5020..21fd11e 100644 --- a/trade_stats_calendar_lib.py +++ b/trade_stats_calendar_lib.py @@ -1,6 +1,7 @@ """按交易日聚合实例 trade_records 盈亏,供统计分析页日历 API 使用。""" from __future__ import annotations +import json from datetime import datetime, timedelta from typing import Any, Callable @@ -90,3 +91,25 @@ def build_initial_stats_calendar( row_matches_fn, reset_hour=reset_hour, ) + + +def build_stats_calendar_bootstrap( + pnls: list[tuple], + now_dt: datetime, + row_matches_fn: Callable[[Any, str], bool], + *, + reset_hour: int = 8, + segment_key: str = "all", +) -> tuple[dict[str, Any] | None, str | None]: + """返回 (payload, json_str);失败时 (None, None),供模板安全内嵌。""" + try: + payload = build_initial_stats_calendar( + pnls, + now_dt, + row_matches_fn, + reset_hour=reset_hour, + segment_key=segment_key, + ) + return payload, json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + except Exception: + return None, None