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