fix: 修复统计日历 bootstrap 导致整站 500
日历数据改为安全 JSON 内嵌,仅统计页构建;构建失败时降级为空,避免拖垮其他页面。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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 切日)计入。"""
|
"""日 / 周 / 月 统计:平仓按北京时间交易日(默认 8:00 切日)计入。"""
|
||||||
now_dt = now_dt or app_now()
|
now_dt = now_dt or app_now()
|
||||||
pnls = _load_completed_trade_pnls(conn)
|
pnls = _load_completed_trade_pnls(conn)
|
||||||
@@ -1888,9 +1888,12 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
|
|
||||||
dm, wm, mm = slice_metrics("all")
|
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(
|
initial_calendar, calendar_bootstrap_json = build_stats_calendar_bootstrap(
|
||||||
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1903,6 +1906,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
"segments": segments,
|
"segments": segments,
|
||||||
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
||||||
"initial_calendar": initial_calendar,
|
"initial_calendar": initial_calendar,
|
||||||
|
"calendar_bootstrap_json": calendar_bootstrap_json,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -7040,7 +7044,7 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
else []
|
else []
|
||||||
)
|
)
|
||||||
stats_bundle = (
|
stats_bundle = (
|
||||||
compute_stats_bundle(conn, trading_day, now)
|
compute_stats_bundle(conn, trading_day, now, with_calendar=(page == "stats"))
|
||||||
if plan.stats_bundle
|
if plan.stats_bundle
|
||||||
else minimal_stats_bundle(TRADING_DAY_RESET_HOUR)
|
else minimal_stats_bundle(TRADING_DAY_RESET_HOUR)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -807,8 +807,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% if stats_bundle.initial_calendar %}
|
{% if stats_bundle.calendar_bootstrap_json %}
|
||||||
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.initial_calendar | tojson }}</script>
|
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.calendar_bootstrap_json | safe }}</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="stats-calendar-wrap" class="trade-cal-wrap stats-calendar-wrap">
|
<div id="stats-calendar-wrap" class="trade-cal-wrap stats-calendar-wrap">
|
||||||
<div class="trade-cal-head">
|
<div class="trade-cal-head">
|
||||||
|
|||||||
@@ -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 切日)计入。"""
|
"""日 / 周 / 月 统计:平仓按北京时间交易日(默认 8:00 切日)计入。"""
|
||||||
now_dt = now_dt or app_now()
|
now_dt = now_dt or app_now()
|
||||||
pnls = _load_completed_trade_pnls(conn)
|
pnls = _load_completed_trade_pnls(conn)
|
||||||
@@ -1883,9 +1883,12 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
|
|
||||||
dm, wm, mm = slice_metrics("all")
|
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(
|
initial_calendar, calendar_bootstrap_json = build_stats_calendar_bootstrap(
|
||||||
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1898,6 +1901,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
"segments": segments,
|
"segments": segments,
|
||||||
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
||||||
"initial_calendar": initial_calendar,
|
"initial_calendar": initial_calendar,
|
||||||
|
"calendar_bootstrap_json": calendar_bootstrap_json,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -6882,7 +6886,7 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
else []
|
else []
|
||||||
)
|
)
|
||||||
stats_bundle = (
|
stats_bundle = (
|
||||||
compute_stats_bundle(conn, trading_day, now)
|
compute_stats_bundle(conn, trading_day, now, with_calendar=(page == "stats"))
|
||||||
if plan.stats_bundle
|
if plan.stats_bundle
|
||||||
else minimal_stats_bundle(TRADING_DAY_RESET_HOUR)
|
else minimal_stats_bundle(TRADING_DAY_RESET_HOUR)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -774,8 +774,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% if stats_bundle.initial_calendar %}
|
{% if stats_bundle.calendar_bootstrap_json %}
|
||||||
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.initial_calendar | tojson }}</script>
|
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.calendar_bootstrap_json | safe }}</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="stats-calendar-wrap" class="trade-cal-wrap stats-calendar-wrap">
|
<div id="stats-calendar-wrap" class="trade-cal-wrap stats-calendar-wrap">
|
||||||
<div class="trade-cal-head">
|
<div class="trade-cal-head">
|
||||||
|
|||||||
@@ -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 切日)计入。"""
|
"""日 / 周 / 月 统计:平仓按北京时间交易日(默认 8:00 切日)计入。"""
|
||||||
now_dt = now_dt or app_now()
|
now_dt = now_dt or app_now()
|
||||||
pnls = _load_completed_trade_pnls(conn)
|
pnls = _load_completed_trade_pnls(conn)
|
||||||
@@ -1883,9 +1883,12 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
|
|
||||||
dm, wm, mm = slice_metrics("all")
|
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(
|
initial_calendar, calendar_bootstrap_json = build_stats_calendar_bootstrap(
|
||||||
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1898,6 +1901,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
"segments": segments,
|
"segments": segments,
|
||||||
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
||||||
"initial_calendar": initial_calendar,
|
"initial_calendar": initial_calendar,
|
||||||
|
"calendar_bootstrap_json": calendar_bootstrap_json,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -6882,7 +6886,7 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
else []
|
else []
|
||||||
)
|
)
|
||||||
stats_bundle = (
|
stats_bundle = (
|
||||||
compute_stats_bundle(conn, trading_day, now)
|
compute_stats_bundle(conn, trading_day, now, with_calendar=(page == "stats"))
|
||||||
if plan.stats_bundle
|
if plan.stats_bundle
|
||||||
else minimal_stats_bundle(TRADING_DAY_RESET_HOUR)
|
else minimal_stats_bundle(TRADING_DAY_RESET_HOUR)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -774,8 +774,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% if stats_bundle.initial_calendar %}
|
{% if stats_bundle.calendar_bootstrap_json %}
|
||||||
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.initial_calendar | tojson }}</script>
|
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.calendar_bootstrap_json | safe }}</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="stats-calendar-wrap" class="trade-cal-wrap stats-calendar-wrap">
|
<div id="stats-calendar-wrap" class="trade-cal-wrap stats-calendar-wrap">
|
||||||
<div class="trade-cal-head">
|
<div class="trade-cal-head">
|
||||||
|
|||||||
@@ -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 切日)计入。"""
|
"""日 / 周 / 月 统计:平仓按北京时间交易日(默认 8:00 切日)计入。"""
|
||||||
now_dt = now_dt or app_now()
|
now_dt = now_dt or app_now()
|
||||||
pnls = _load_completed_trade_pnls(conn)
|
pnls = _load_completed_trade_pnls(conn)
|
||||||
@@ -1861,9 +1861,12 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
|
|
||||||
dm, wm, mm = slice_metrics("all")
|
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(
|
initial_calendar, calendar_bootstrap_json = build_stats_calendar_bootstrap(
|
||||||
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1876,6 +1879,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
"segments": segments,
|
"segments": segments,
|
||||||
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
||||||
"initial_calendar": initial_calendar,
|
"initial_calendar": initial_calendar,
|
||||||
|
"calendar_bootstrap_json": calendar_bootstrap_json,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -6381,7 +6385,7 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
else []
|
else []
|
||||||
)
|
)
|
||||||
stats_bundle = (
|
stats_bundle = (
|
||||||
compute_stats_bundle(conn, trading_day, now)
|
compute_stats_bundle(conn, trading_day, now, with_calendar=(page == "stats"))
|
||||||
if plan.stats_bundle
|
if plan.stats_bundle
|
||||||
else minimal_stats_bundle(TRADING_DAY_RESET_HOUR)
|
else minimal_stats_bundle(TRADING_DAY_RESET_HOUR)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -803,8 +803,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% if stats_bundle.initial_calendar %}
|
{% if stats_bundle.calendar_bootstrap_json %}
|
||||||
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.initial_calendar | tojson }}</script>
|
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.calendar_bootstrap_json | safe }}</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="stats-calendar-wrap" class="trade-cal-wrap stats-calendar-wrap">
|
<div id="stats-calendar-wrap" class="trade-cal-wrap stats-calendar-wrap">
|
||||||
<div class="trade-cal-head">
|
<div class="trade-cal-head">
|
||||||
|
|||||||
@@ -452,8 +452,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% if stats_bundle.initial_calendar %}
|
{% if stats_bundle.calendar_bootstrap_json %}
|
||||||
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.initial_calendar | tojson }}</script>
|
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.calendar_bootstrap_json | safe }}</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="stats-calendar-wrap" class="trade-cal-wrap stats-calendar-wrap">
|
<div id="stats-calendar-wrap" class="trade-cal-wrap stats-calendar-wrap">
|
||||||
<div class="trade-cal-head">
|
<div class="trade-cal-head">
|
||||||
|
|||||||
@@ -85,4 +85,5 @@ def minimal_stats_bundle(reset_hour: int) -> dict[str, Any]:
|
|||||||
"stats_reset_hour": reset_hour,
|
"stats_reset_hour": reset_hour,
|
||||||
"segments": [],
|
"segments": [],
|
||||||
"initial_calendar": None,
|
"initial_calendar": None,
|
||||||
|
"calendar_bootstrap_json": None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ from types import SimpleNamespace
|
|||||||
|
|
||||||
from datetime import datetime
|
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):
|
def _row(**kwargs):
|
||||||
@@ -69,6 +73,18 @@ class TradeStatsCalendarLibTests(unittest.TestCase):
|
|||||||
self.assertEqual(payload["month_open_count"], 1)
|
self.assertEqual(payload["month_open_count"], 1)
|
||||||
self.assertIn("2026-06-20", payload["days"])
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""按交易日聚合实例 trade_records 盈亏,供统计分析页日历 API 使用。"""
|
"""按交易日聚合实例 trade_records 盈亏,供统计分析页日历 API 使用。"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
@@ -90,3 +91,25 @@ def build_initial_stats_calendar(
|
|||||||
row_matches_fn,
|
row_matches_fn,
|
||||||
reset_hour=reset_hour,
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user