fix: 统计日历服务端内嵌 bootstrap,首屏显示盈亏与笔数
与月统计同源 initial_calendar 写入页面,API 失败时仍渲染;四所日历路由独立注册并传入 get_db_fn。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1888,6 +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 = build_initial_stats_calendar(
|
||||||
|
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"trading_day": trading_day,
|
"trading_day": trading_day,
|
||||||
"total_opens_all": total_opens_all,
|
"total_opens_all": total_opens_all,
|
||||||
@@ -1896,6 +1902,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
"month": mm,
|
"month": mm,
|
||||||
"segments": segments,
|
"segments": segments,
|
||||||
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
||||||
|
"initial_calendar": initial_calendar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -9617,7 +9624,7 @@ try:
|
|||||||
_repo_root = Path(__file__).resolve().parent.parent
|
_repo_root = Path(__file__).resolve().parent.parent
|
||||||
if str(_repo_root) not in sys.path:
|
if str(_repo_root) not in sys.path:
|
||||||
sys.path.insert(0, str(_repo_root))
|
sys.path.insert(0, str(_repo_root))
|
||||||
from hub_bridge import install_on_app, register_trade_stats_calendar_route
|
from hub_bridge import install_on_app
|
||||||
|
|
||||||
install_on_app(
|
install_on_app(
|
||||||
app,
|
app,
|
||||||
@@ -9637,15 +9644,22 @@ try:
|
|||||||
render_main_page_fn=render_main_page,
|
render_main_page_fn=render_main_page,
|
||||||
login_required_fn=login_required,
|
login_required_fn=login_required,
|
||||||
)
|
)
|
||||||
|
except Exception as _hub_err:
|
||||||
|
print(f"[hub_bridge] binance: {_hub_err}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hub_bridge import register_trade_stats_calendar_route
|
||||||
|
|
||||||
register_trade_stats_calendar_route(
|
register_trade_stats_calendar_route(
|
||||||
app,
|
app,
|
||||||
login_required_fn=login_required,
|
login_required_fn=login_required,
|
||||||
load_pnls_fn=_load_completed_trade_pnls,
|
load_pnls_fn=_load_completed_trade_pnls,
|
||||||
row_matches_segment_fn=_pnl_row_matches_segment,
|
row_matches_segment_fn=_pnl_row_matches_segment,
|
||||||
reset_hour=TRADING_DAY_RESET_HOUR,
|
reset_hour=TRADING_DAY_RESET_HOUR,
|
||||||
|
get_db_fn=get_db,
|
||||||
)
|
)
|
||||||
except Exception as _hub_err:
|
except Exception as _cal_err:
|
||||||
print(f"[hub_bridge] binance: {_hub_err}")
|
print(f"[stats calendar] binance: {_cal_err}")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/strategy")
|
@app.route("/strategy")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
<script src="/static/instance_theme.js?v=36"></script>
|
<script src="/static/instance_theme.js?v=46"></script>
|
||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
@@ -243,8 +243,8 @@
|
|||||||
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
||||||
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=38">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=48">
|
||||||
<link rel="stylesheet" href="/static/trade_stats_calendar.css?v=3">
|
<link rel="stylesheet" href="/static/trade_stats_calendar.css?v=4">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
@@ -807,6 +807,9 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{% if stats_bundle.initial_calendar %}
|
||||||
|
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.initial_calendar | tojson }}</script>
|
||||||
|
{% 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">
|
||||||
<button type="button" id="stats-cal-prev" class="btn" title="上一月">‹</button>
|
<button type="button" id="stats-cal-prev" class="btn" title="上一月">‹</button>
|
||||||
@@ -844,14 +847,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/instance_ui.js?v=3"></script>
|
<script src="/static/instance_ui.js?v=4"></script>
|
||||||
<script src="/static/instance_records_mobile.js?v=2"></script>
|
<script src="/static/instance_records_mobile.js?v=2"></script>
|
||||||
<script src="/static/time_close_ui.js?v=2"></script>
|
<script src="/static/time_close_ui.js?v=2"></script>
|
||||||
<script src="/static/ai_review_render.js?v=2"></script>
|
<script src="/static/ai_review_render.js?v=2"></script>
|
||||||
<script src="/static/form_submit_guard.js?v=2"></script>
|
<script src="/static/form_submit_guard.js?v=2"></script>
|
||||||
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
||||||
<script src="/static/strategy_roll.js?v=5"></script>
|
<script src="/static/strategy_roll.js?v=5"></script>
|
||||||
<script src="/static/trade_stats_calendar.js?v=3"></script>
|
<script src="/static/trade_stats_calendar.js?v=4"></script>
|
||||||
<script>
|
<script>
|
||||||
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
||||||
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
||||||
|
|||||||
@@ -1883,6 +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 = build_initial_stats_calendar(
|
||||||
|
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"trading_day": trading_day,
|
"trading_day": trading_day,
|
||||||
"total_opens_all": total_opens_all,
|
"total_opens_all": total_opens_all,
|
||||||
@@ -1891,6 +1897,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
"month": mm,
|
"month": mm,
|
||||||
"segments": segments,
|
"segments": segments,
|
||||||
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
||||||
|
"initial_calendar": initial_calendar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -9537,7 +9544,7 @@ try:
|
|||||||
_repo_root = Path(__file__).resolve().parent.parent
|
_repo_root = Path(__file__).resolve().parent.parent
|
||||||
if str(_repo_root) not in sys.path:
|
if str(_repo_root) not in sys.path:
|
||||||
sys.path.insert(0, str(_repo_root))
|
sys.path.insert(0, str(_repo_root))
|
||||||
from hub_bridge import install_on_app, register_trade_stats_calendar_route
|
from hub_bridge import install_on_app
|
||||||
|
|
||||||
install_on_app(
|
install_on_app(
|
||||||
app,
|
app,
|
||||||
@@ -9558,15 +9565,22 @@ try:
|
|||||||
render_main_page_fn=render_main_page,
|
render_main_page_fn=render_main_page,
|
||||||
login_required_fn=login_required,
|
login_required_fn=login_required,
|
||||||
)
|
)
|
||||||
|
except Exception as _hub_err:
|
||||||
|
print(f"[hub_bridge] gate: {_hub_err}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hub_bridge import register_trade_stats_calendar_route
|
||||||
|
|
||||||
register_trade_stats_calendar_route(
|
register_trade_stats_calendar_route(
|
||||||
app,
|
app,
|
||||||
login_required_fn=login_required,
|
login_required_fn=login_required,
|
||||||
load_pnls_fn=_load_completed_trade_pnls,
|
load_pnls_fn=_load_completed_trade_pnls,
|
||||||
row_matches_segment_fn=_pnl_row_matches_segment,
|
row_matches_segment_fn=_pnl_row_matches_segment,
|
||||||
reset_hour=TRADING_DAY_RESET_HOUR,
|
reset_hour=TRADING_DAY_RESET_HOUR,
|
||||||
|
get_db_fn=get_db,
|
||||||
)
|
)
|
||||||
except Exception as _hub_err:
|
except Exception as _cal_err:
|
||||||
print(f"[hub_bridge] gate: {_hub_err}")
|
print(f"[stats calendar] gate: {_cal_err}")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/strategy")
|
@app.route("/strategy")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
<script src="/static/instance_theme.js?v=36"></script>
|
<script src="/static/instance_theme.js?v=46"></script>
|
||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
@@ -243,8 +243,8 @@
|
|||||||
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
||||||
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=38">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=48">
|
||||||
<link rel="stylesheet" href="/static/trade_stats_calendar.css?v=3">
|
<link rel="stylesheet" href="/static/trade_stats_calendar.css?v=4">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
@@ -774,6 +774,9 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{% if stats_bundle.initial_calendar %}
|
||||||
|
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.initial_calendar | tojson }}</script>
|
||||||
|
{% 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">
|
||||||
<button type="button" id="stats-cal-prev" class="btn" title="上一月">‹</button>
|
<button type="button" id="stats-cal-prev" class="btn" title="上一月">‹</button>
|
||||||
@@ -811,14 +814,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/instance_ui.js?v=3"></script>
|
<script src="/static/instance_ui.js?v=4"></script>
|
||||||
<script src="/static/instance_records_mobile.js?v=2"></script>
|
<script src="/static/instance_records_mobile.js?v=2"></script>
|
||||||
<script src="/static/time_close_ui.js?v=2"></script>
|
<script src="/static/time_close_ui.js?v=2"></script>
|
||||||
<script src="/static/ai_review_render.js?v=2"></script>
|
<script src="/static/ai_review_render.js?v=2"></script>
|
||||||
<script src="/static/form_submit_guard.js?v=2"></script>
|
<script src="/static/form_submit_guard.js?v=2"></script>
|
||||||
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
||||||
<script src="/static/strategy_roll.js?v=5"></script>
|
<script src="/static/strategy_roll.js?v=5"></script>
|
||||||
<script src="/static/trade_stats_calendar.js?v=3"></script>
|
<script src="/static/trade_stats_calendar.js?v=4"></script>
|
||||||
<script>
|
<script>
|
||||||
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
||||||
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
||||||
|
|||||||
@@ -1883,6 +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 = build_initial_stats_calendar(
|
||||||
|
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"trading_day": trading_day,
|
"trading_day": trading_day,
|
||||||
"total_opens_all": total_opens_all,
|
"total_opens_all": total_opens_all,
|
||||||
@@ -1891,6 +1897,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
"month": mm,
|
"month": mm,
|
||||||
"segments": segments,
|
"segments": segments,
|
||||||
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
||||||
|
"initial_calendar": initial_calendar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -9533,7 +9540,7 @@ try:
|
|||||||
_repo_root = Path(__file__).resolve().parent.parent
|
_repo_root = Path(__file__).resolve().parent.parent
|
||||||
if str(_repo_root) not in sys.path:
|
if str(_repo_root) not in sys.path:
|
||||||
sys.path.insert(0, str(_repo_root))
|
sys.path.insert(0, str(_repo_root))
|
||||||
from hub_bridge import install_on_app, register_trade_stats_calendar_route
|
from hub_bridge import install_on_app
|
||||||
|
|
||||||
install_on_app(
|
install_on_app(
|
||||||
app,
|
app,
|
||||||
@@ -9554,15 +9561,22 @@ try:
|
|||||||
render_main_page_fn=render_main_page,
|
render_main_page_fn=render_main_page,
|
||||||
login_required_fn=login_required,
|
login_required_fn=login_required,
|
||||||
)
|
)
|
||||||
|
except Exception as _hub_err:
|
||||||
|
print(f"[hub_bridge] gate_bot: {_hub_err}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hub_bridge import register_trade_stats_calendar_route
|
||||||
|
|
||||||
register_trade_stats_calendar_route(
|
register_trade_stats_calendar_route(
|
||||||
app,
|
app,
|
||||||
login_required_fn=login_required,
|
login_required_fn=login_required,
|
||||||
load_pnls_fn=_load_completed_trade_pnls,
|
load_pnls_fn=_load_completed_trade_pnls,
|
||||||
row_matches_segment_fn=_pnl_row_matches_segment,
|
row_matches_segment_fn=_pnl_row_matches_segment,
|
||||||
reset_hour=TRADING_DAY_RESET_HOUR,
|
reset_hour=TRADING_DAY_RESET_HOUR,
|
||||||
|
get_db_fn=get_db,
|
||||||
)
|
)
|
||||||
except Exception as _hub_err:
|
except Exception as _cal_err:
|
||||||
print(f"[hub_bridge] gate_bot: {_hub_err}")
|
print(f"[stats calendar] gate_bot: {_cal_err}")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/strategy")
|
@app.route("/strategy")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
<script src="/static/instance_theme.js?v=36"></script>
|
<script src="/static/instance_theme.js?v=46"></script>
|
||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
@@ -243,8 +243,8 @@
|
|||||||
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
||||||
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=38">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=48">
|
||||||
<link rel="stylesheet" href="/static/trade_stats_calendar.css?v=3">
|
<link rel="stylesheet" href="/static/trade_stats_calendar.css?v=4">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
@@ -774,6 +774,9 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{% if stats_bundle.initial_calendar %}
|
||||||
|
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.initial_calendar | tojson }}</script>
|
||||||
|
{% 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">
|
||||||
<button type="button" id="stats-cal-prev" class="btn" title="上一月">‹</button>
|
<button type="button" id="stats-cal-prev" class="btn" title="上一月">‹</button>
|
||||||
@@ -811,14 +814,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/instance_ui.js?v=3"></script>
|
<script src="/static/instance_ui.js?v=4"></script>
|
||||||
<script src="/static/instance_records_mobile.js?v=2"></script>
|
<script src="/static/instance_records_mobile.js?v=2"></script>
|
||||||
<script src="/static/time_close_ui.js?v=2"></script>
|
<script src="/static/time_close_ui.js?v=2"></script>
|
||||||
<script src="/static/ai_review_render.js?v=2"></script>
|
<script src="/static/ai_review_render.js?v=2"></script>
|
||||||
<script src="/static/form_submit_guard.js?v=2"></script>
|
<script src="/static/form_submit_guard.js?v=2"></script>
|
||||||
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
||||||
<script src="/static/strategy_roll.js?v=5"></script>
|
<script src="/static/strategy_roll.js?v=5"></script>
|
||||||
<script src="/static/trade_stats_calendar.js?v=3"></script>
|
<script src="/static/trade_stats_calendar.js?v=4"></script>
|
||||||
<script>
|
<script>
|
||||||
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
||||||
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
||||||
|
|||||||
@@ -1861,6 +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 = build_initial_stats_calendar(
|
||||||
|
pnls, now_dt, _pnl_row_matches_segment, reset_hour=TRADING_DAY_RESET_HOUR
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"trading_day": trading_day,
|
"trading_day": trading_day,
|
||||||
"total_opens_all": total_opens_all,
|
"total_opens_all": total_opens_all,
|
||||||
@@ -1869,6 +1875,7 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
|||||||
"month": mm,
|
"month": mm,
|
||||||
"segments": segments,
|
"segments": segments,
|
||||||
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
"stats_reset_hour": TRADING_DAY_RESET_HOUR,
|
||||||
|
"initial_calendar": initial_calendar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -8998,7 +9005,7 @@ try:
|
|||||||
_repo_root = Path(__file__).resolve().parent.parent
|
_repo_root = Path(__file__).resolve().parent.parent
|
||||||
if str(_repo_root) not in sys.path:
|
if str(_repo_root) not in sys.path:
|
||||||
sys.path.insert(0, str(_repo_root))
|
sys.path.insert(0, str(_repo_root))
|
||||||
from hub_bridge import install_on_app, register_trade_stats_calendar_route
|
from hub_bridge import install_on_app
|
||||||
|
|
||||||
install_on_app(
|
install_on_app(
|
||||||
app,
|
app,
|
||||||
@@ -9018,15 +9025,22 @@ try:
|
|||||||
render_main_page_fn=render_main_page,
|
render_main_page_fn=render_main_page,
|
||||||
login_required_fn=login_required,
|
login_required_fn=login_required,
|
||||||
)
|
)
|
||||||
|
except Exception as _hub_err:
|
||||||
|
print(f"[hub_bridge] okx: {_hub_err}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hub_bridge import register_trade_stats_calendar_route
|
||||||
|
|
||||||
register_trade_stats_calendar_route(
|
register_trade_stats_calendar_route(
|
||||||
app,
|
app,
|
||||||
login_required_fn=login_required,
|
login_required_fn=login_required,
|
||||||
load_pnls_fn=_load_completed_trade_pnls,
|
load_pnls_fn=_load_completed_trade_pnls,
|
||||||
row_matches_segment_fn=_pnl_row_matches_segment,
|
row_matches_segment_fn=_pnl_row_matches_segment,
|
||||||
reset_hour=TRADING_DAY_RESET_HOUR,
|
reset_hour=TRADING_DAY_RESET_HOUR,
|
||||||
|
get_db_fn=get_db,
|
||||||
)
|
)
|
||||||
except Exception as _hub_err:
|
except Exception as _cal_err:
|
||||||
print(f"[hub_bridge] okx: {_hub_err}")
|
print(f"[stats calendar] okx: {_cal_err}")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/strategy")
|
@app.route("/strategy")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
<script src="/static/instance_theme.js?v=36"></script>
|
<script src="/static/instance_theme.js?v=46"></script>
|
||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
@@ -243,8 +243,8 @@
|
|||||||
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
||||||
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=38">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=48">
|
||||||
<link rel="stylesheet" href="/static/trade_stats_calendar.css?v=3">
|
<link rel="stylesheet" href="/static/trade_stats_calendar.css?v=4">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
@@ -803,6 +803,9 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{% if stats_bundle.initial_calendar %}
|
||||||
|
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.initial_calendar | tojson }}</script>
|
||||||
|
{% 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">
|
||||||
<button type="button" id="stats-cal-prev" class="btn" title="上一月">‹</button>
|
<button type="button" id="stats-cal-prev" class="btn" title="上一月">‹</button>
|
||||||
@@ -840,14 +843,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/instance_ui.js?v=3"></script>
|
<script src="/static/instance_ui.js?v=4"></script>
|
||||||
<script src="/static/instance_records_mobile.js?v=2"></script>
|
<script src="/static/instance_records_mobile.js?v=2"></script>
|
||||||
<script src="/static/time_close_ui.js?v=2"></script>
|
<script src="/static/time_close_ui.js?v=2"></script>
|
||||||
<script src="/static/ai_review_render.js?v=2"></script>
|
<script src="/static/ai_review_render.js?v=2"></script>
|
||||||
<script src="/static/form_submit_guard.js?v=2"></script>
|
<script src="/static/form_submit_guard.js?v=2"></script>
|
||||||
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
||||||
<script src="/static/strategy_roll.js?v=5"></script>
|
<script src="/static/strategy_roll.js?v=5"></script>
|
||||||
<script src="/static/trade_stats_calendar.js?v=3"></script>
|
<script src="/static/trade_stats_calendar.js?v=4"></script>
|
||||||
<script>
|
<script>
|
||||||
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
||||||
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
||||||
|
|||||||
@@ -452,6 +452,9 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{% if stats_bundle.initial_calendar %}
|
||||||
|
<script type="application/json" id="stats-calendar-bootstrap">{{ stats_bundle.initial_calendar | tojson }}</script>
|
||||||
|
{% 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">
|
||||||
<button type="button" id="stats-cal-prev" class="btn" title="上一月">‹</button>
|
<button type="button" id="stats-cal-prev" class="btn" title="上一月">‹</button>
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
<script src="/static/instance_theme.js?v=37"></script>
|
<script src="/static/instance_theme.js?v=47"></script>
|
||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=2">
|
<link rel="stylesheet" href="/static/instance_page.css?v=2">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=38">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=48">
|
||||||
<link rel="stylesheet" href="/static/trade_stats_calendar.css?v=3">
|
<link rel="stylesheet" href="/static/trade_stats_calendar.css?v=4">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
<meta name="theme-color" content="#0b0d14">
|
<meta name="theme-color" content="#0b0d14">
|
||||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/instance_ui.js?v=3"></script>
|
<script src="/static/instance_ui.js?v=4"></script>
|
||||||
<script src="/static/instance_records_mobile.js?v=2"></script>
|
<script src="/static/instance_records_mobile.js?v=2"></script>
|
||||||
<script src="/static/time_close_ui.js?v=2"></script>
|
<script src="/static/time_close_ui.js?v=2"></script>
|
||||||
<script src="/static/ai_review_render.js?v=2"></script>
|
<script src="/static/ai_review_render.js?v=2"></script>
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
||||||
<script src="/static/strategy_roll.js?v=5"></script>
|
<script src="/static/strategy_roll.js?v=5"></script>
|
||||||
<script src="/static/key_monitor_form.js?v=2"></script>
|
<script src="/static/key_monitor_form.js?v=2"></script>
|
||||||
<script src="/static/trade_stats_calendar.js?v=3"></script>
|
<script src="/static/trade_stats_calendar.js?v=4"></script>
|
||||||
{% include 'embed_boot_scripts.html' %}
|
{% include 'embed_boot_scripts.html' %}
|
||||||
<script src="/static/instance_embed.js?v=5"></script>
|
<script src="/static/instance_embed.js?v=5"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+2
-1
@@ -92,6 +92,7 @@ def register_trade_stats_calendar_route(
|
|||||||
load_pnls_fn,
|
load_pnls_fn,
|
||||||
row_matches_segment_fn,
|
row_matches_segment_fn,
|
||||||
reset_hour: int,
|
reset_hour: int,
|
||||||
|
get_db_fn=None,
|
||||||
):
|
):
|
||||||
"""四所统计分析页:按月返回各交易日盈亏/笔数。"""
|
"""四所统计分析页:按月返回各交易日盈亏/笔数。"""
|
||||||
from flask import jsonify, request
|
from flask import jsonify, request
|
||||||
@@ -110,7 +111,7 @@ def register_trade_stats_calendar_route(
|
|||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
year = year or now.year
|
year = year or now.year
|
||||||
month = month or now.month
|
month = month or now.month
|
||||||
get_db = (app.config.get("HUB_CTX") or {}).get("get_db")
|
get_db = get_db_fn or (app.config.get("HUB_CTX") or {}).get("get_db")
|
||||||
if not get_db:
|
if not get_db:
|
||||||
return jsonify({"ok": False, "msg": "未配置数据库"}), 500
|
return jsonify({"ok": False, "msg": "未配置数据库"}), 500
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
|
|||||||
@@ -81,4 +81,8 @@ def trade_records_summary(conn, start_bj: str, end_bj: str, tr_ts: str) -> dict[
|
|||||||
|
|
||||||
|
|
||||||
def minimal_stats_bundle(reset_hour: int) -> dict[str, Any]:
|
def minimal_stats_bundle(reset_hour: int) -> dict[str, Any]:
|
||||||
return {"stats_reset_hour": reset_hour, "segments": []}
|
return {
|
||||||
|
"stats_reset_hour": reset_hour,
|
||||||
|
"segments": [],
|
||||||
|
"initial_calendar": None,
|
||||||
|
}
|
||||||
|
|||||||
@@ -89,6 +89,38 @@
|
|||||||
this.month = d.getMonth() + 1;
|
this.month = d.getMonth() + 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TradeStatsCalendar.prototype.applyPayload = function (data) {
|
||||||
|
if (!data) return;
|
||||||
|
var y = Number(data.year);
|
||||||
|
var m = Number(data.month);
|
||||||
|
if (Number.isFinite(y) && y > 0) this.year = y;
|
||||||
|
if (Number.isFinite(m) && m > 0) this.month = m;
|
||||||
|
this.days = this.parseResponse(data) || {};
|
||||||
|
this.monthPnlTotal = Number(data.month_pnl_total) || 0;
|
||||||
|
this.monthOpenCount = Number(data.month_open_count) || 0;
|
||||||
|
if (!this.monthOpenCount) {
|
||||||
|
var self = this;
|
||||||
|
Object.keys(this.days).forEach(function (k) {
|
||||||
|
if (dayHasTrade(self.days[k])) {
|
||||||
|
self.monthOpenCount += dayOpenCount(self.days[k]);
|
||||||
|
self.monthPnlTotal += dayPnl(self.days[k]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.monthPnlTotal = Math.round(this.monthPnlTotal * 10000) / 10000;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function readStatsCalendarBootstrap() {
|
||||||
|
var el = document.getElementById("stats-calendar-bootstrap");
|
||||||
|
if (!el || !el.textContent) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(el.textContent);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("[trade calendar] bootstrap parse", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TradeStatsCalendar.prototype.setSelectedDay = function (day) {
|
TradeStatsCalendar.prototype.setSelectedDay = function (day) {
|
||||||
this.selectedDay = day || "";
|
this.selectedDay = day || "";
|
||||||
this.render();
|
this.render();
|
||||||
@@ -192,23 +224,12 @@
|
|||||||
});
|
});
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
console.warn("[trade calendar] api", resp.status);
|
console.warn("[trade calendar] api", resp.status);
|
||||||
|
this.render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
data = await resp.json();
|
data = await resp.json();
|
||||||
}
|
}
|
||||||
this.days = this.parseResponse(data) || {};
|
this.applyPayload(data);
|
||||||
this.monthPnlTotal = Number(data && data.month_pnl_total) || 0;
|
|
||||||
this.monthOpenCount = Number(data && data.month_open_count) || 0;
|
|
||||||
if (!this.monthOpenCount) {
|
|
||||||
var self = this;
|
|
||||||
Object.keys(this.days).forEach(function (k) {
|
|
||||||
if (dayHasTrade(self.days[k])) {
|
|
||||||
self.monthOpenCount += dayOpenCount(self.days[k]);
|
|
||||||
self.monthPnlTotal += dayPnl(self.days[k]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.monthPnlTotal = Math.round(this.monthPnlTotal * 10000) / 10000;
|
|
||||||
}
|
|
||||||
this.render();
|
this.render();
|
||||||
if (this.onMonthChange) this.onMonthChange(this.year, this.month, this.days);
|
if (this.onMonthChange) this.onMonthChange(this.year, this.month, this.days);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -253,6 +274,16 @@
|
|||||||
global.initInstanceStatsCalendar = function () {
|
global.initInstanceStatsCalendar = function () {
|
||||||
var grid = document.getElementById("stats-calendar");
|
var grid = document.getElementById("stats-calendar");
|
||||||
if (!grid || !global.TradeStatsCalendar) return null;
|
if (!grid || !global.TradeStatsCalendar) return null;
|
||||||
|
var bootstrap = readStatsCalendarBootstrap();
|
||||||
|
if (
|
||||||
|
global.statsCalendarWidget &&
|
||||||
|
global.statsCalendarWidget.gridEl === grid
|
||||||
|
) {
|
||||||
|
if (bootstrap) global.statsCalendarWidget.applyPayload(bootstrap);
|
||||||
|
global.statsCalendarWidget.render();
|
||||||
|
void global.statsCalendarWidget.load();
|
||||||
|
return global.statsCalendarWidget;
|
||||||
|
}
|
||||||
global.statsCalendarWidget = new TradeStatsCalendar({
|
global.statsCalendarWidget = new TradeStatsCalendar({
|
||||||
gridEl: grid,
|
gridEl: grid,
|
||||||
titleEl: document.getElementById("stats-cal-title"),
|
titleEl: document.getElementById("stats-cal-title"),
|
||||||
@@ -273,6 +304,7 @@
|
|||||||
return (data && data.days) || {};
|
return (data && data.days) || {};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (bootstrap) global.statsCalendarWidget.applyPayload(bootstrap);
|
||||||
global.statsCalendarWidget.render();
|
global.statsCalendarWidget.render();
|
||||||
void global.statsCalendarWidget.load();
|
void global.statsCalendarWidget.load();
|
||||||
return global.statsCalendarWidget;
|
return global.statsCalendarWidget;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from trade_stats_calendar_lib import build_trade_stats_calendar
|
from datetime import datetime
|
||||||
|
|
||||||
|
from trade_stats_calendar_lib import build_initial_stats_calendar, build_trade_stats_calendar
|
||||||
|
|
||||||
|
|
||||||
def _row(**kwargs):
|
def _row(**kwargs):
|
||||||
@@ -54,6 +56,19 @@ class TradeStatsCalendarLibTests(unittest.TestCase):
|
|||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
build_trade_stats_calendar([], 2026, 13, "all", _matches_all)
|
build_trade_stats_calendar([], 2026, 13, "all", _matches_all)
|
||||||
|
|
||||||
|
def test_initial_calendar_uses_current_month(self):
|
||||||
|
pnls = [(2.5, None, "2026-06-20", _row())]
|
||||||
|
payload = build_initial_stats_calendar(
|
||||||
|
pnls,
|
||||||
|
datetime(2026, 6, 26, 12, 0),
|
||||||
|
_matches_all,
|
||||||
|
reset_hour=8,
|
||||||
|
)
|
||||||
|
self.assertEqual(payload["year"], 2026)
|
||||||
|
self.assertEqual(payload["month"], 6)
|
||||||
|
self.assertEqual(payload["month_open_count"], 1)
|
||||||
|
self.assertIn("2026-06-20", payload["days"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -71,3 +71,22 @@ def build_trade_stats_calendar(
|
|||||||
"month_pnl_total": round(month_pnl, 4),
|
"month_pnl_total": round(month_pnl, 4),
|
||||||
"month_open_count": month_count,
|
"month_open_count": month_count,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_initial_stats_calendar(
|
||||||
|
pnls: list[tuple],
|
||||||
|
now_dt: datetime,
|
||||||
|
row_matches_fn: Callable[[Any, str], bool],
|
||||||
|
*,
|
||||||
|
reset_hour: int = 8,
|
||||||
|
segment_key: str = "all",
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""统计页首屏内嵌日历(当前自然月、默认品类)。"""
|
||||||
|
return build_trade_stats_calendar(
|
||||||
|
pnls,
|
||||||
|
now_dt.year,
|
||||||
|
now_dt.month,
|
||||||
|
segment_key,
|
||||||
|
row_matches_fn,
|
||||||
|
reset_hour=reset_hour,
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user