add tuning lab view with navigation hints between tuning and backtest

This commit is contained in:
sam 2025-10-16 09:12:17 +08:00
parent c9c27cc5af
commit 2062a11181
6 changed files with 1129 additions and 10 deletions

12
app/ui/navigation.py Normal file
View File

@ -0,0 +1,12 @@
"""Helpers for navigating between top-level Streamlit menus."""
from __future__ import annotations
import streamlit as st
TOP_NAV_STATE_KEY = "top_nav"
def navigate_top_menu(label: str) -> None:
"""Set the active top navigation label and rerun the app."""
st.session_state[TOP_NAV_STATE_KEY] = label
st.experimental_rerun()

View File

@ -8,6 +8,7 @@ import streamlit as st
from app.utils.db import db_session from app.utils.db import db_session
from app.utils.logging import get_logger from app.utils.logging import get_logger
from app.ui.navigation import navigate_top_menu
LOGGER = get_logger(__name__) LOGGER = get_logger(__name__)
LOG_EXTRA = {"stage": "ui"} LOG_EXTRA = {"stage": "ui"}
@ -63,3 +64,16 @@ def default_backtest_range(window_days: int = 60) -> tuple[date, date]:
if start > latest: if start > latest:
start = latest start = latest
return start, latest return start, latest
def render_tuning_backtest_hints(current_label: Optional[str] = None) -> None:
"""Render navigation shortcuts that keep tuning and backtest flows connected."""
key_tag = (current_label or "global").replace("/", "_")
hint_box = st.container()
with hint_box:
col_go_bt, col_go_tune, col_text = st.columns([1, 1, 3])
if col_go_bt.button("回测与复盘", key=f"hint_nav_backtest_{key_tag}"):
navigate_top_menu("回测与复盘")
if col_go_tune.button("实验调参", key=f"hint_nav_tuning_{key_tag}"):
navigate_top_menu("实验调参")
col_text.caption("提示:调参完成后记得回测验证,回测发现问题也可随时跳回调参实验。")

View File

@ -16,7 +16,7 @@ from app.ingest.checker import run_boot_check
from app.ingest.rss import ingest_configured_rss from app.ingest.rss import ingest_configured_rss
from app.ui.portfolio_config import render_portfolio_config from app.ui.portfolio_config import render_portfolio_config
from app.ui.progress_state import render_factor_progress from app.ui.progress_state import render_factor_progress
from app.ui.shared import LOGGER, LOG_EXTRA from app.ui.shared import LOGGER, LOG_EXTRA, render_tuning_backtest_hints
from app.ui.views import ( from app.ui.views import (
render_backtest_review, render_backtest_review,
render_config_overview, render_config_overview,
@ -30,9 +30,11 @@ from app.ui.views import (
render_tests, render_tests,
render_today_plan, render_today_plan,
render_factor_calculation, render_factor_calculation,
render_tuning_lab,
) )
from app.utils.config import get_config from app.utils.config import get_config
from app.ui.navigation import TOP_NAV_STATE_KEY
def main() -> None: def main() -> None:
LOGGER.info("初始化 Streamlit UI", extra=LOG_EXTRA) LOGGER.info("初始化 Streamlit UI", extra=LOG_EXTRA)
@ -74,15 +76,25 @@ def main() -> None:
render_global_dashboard() render_global_dashboard()
# --- 顶部导航(第三方组件 streamlit-option-menu --- # --- 顶部导航(第三方组件 streamlit-option-menu ---
top_labels = ["今日计划", "投资池/仓位", "回测与复盘", "行情可视化", "日志钻取", "数据与设置", "自检测试"] top_labels = ["今日计划", "投资池/仓位", "回测与复盘", "实验调参", "行情可视化", "日志钻取", "数据与设置", "自检测试"]
if TOP_NAV_STATE_KEY not in st.session_state:
st.session_state[TOP_NAV_STATE_KEY] = top_labels[0]
try:
default_index = top_labels.index(st.session_state[TOP_NAV_STATE_KEY])
except ValueError:
default_index = 0
selected_top = option_menu( selected_top = option_menu(
menu_title=None, menu_title=None,
options=top_labels, options=top_labels,
icons=["calendar", "briefcase", "bar-chart", "activity", "file-text", "gear", "bug"], icons=["calendar", "briefcase", "bar-chart", "cpu", "activity", "file-text", "gear", "bug"],
orientation="horizontal", orientation="horizontal",
default_index=default_index,
) )
st.session_state[TOP_NAV_STATE_KEY] = selected_top
LOGGER.debug("Top menu selected: %s", selected_top, extra=LOG_EXTRA) LOGGER.debug("Top menu selected: %s", selected_top, extra=LOG_EXTRA)
render_tuning_backtest_hints(selected_top)
# --- 仅渲染当前选中页(懒加载) --- # --- 仅渲染当前选中页(懒加载) ---
if selected_top == "今日计划": if selected_top == "今日计划":
render_today_plan() render_today_plan()
@ -107,6 +119,9 @@ def main() -> None:
else: else:
render_factor_calculation() render_factor_calculation()
elif selected_top == "实验调参":
render_tuning_lab()
elif selected_top == "行情可视化": elif selected_top == "行情可视化":
render_market_visualization() render_market_visualization()

View File

@ -10,6 +10,7 @@ from .tests import render_tests
from .dashboard import render_global_dashboard, update_dashboard_sidebar from .dashboard import render_global_dashboard, update_dashboard_sidebar
from .stock_eval import render_stock_evaluation from .stock_eval import render_stock_evaluation
from .factor_calculation import render_factor_calculation from .factor_calculation import render_factor_calculation
from .tuning import render_tuning_lab
__all__ = [ __all__ = [
"render_today_plan", "render_today_plan",
@ -25,4 +26,5 @@ __all__ = [
"update_dashboard_sidebar", "update_dashboard_sidebar",
"render_stock_evaluation", "render_stock_evaluation",
"render_factor_calculation", "render_factor_calculation",
"render_tuning_lab",
] ]

View File

@ -12,21 +12,15 @@ import numpy as np
from app.agents.base import AgentContext from app.agents.base import AgentContext
from app.agents.game import Decision from app.agents.game import Decision
from app.agents.registry import default_agents
from app.agents.protocols import GameStructure from app.agents.protocols import GameStructure
from app.backtest.decision_env import DecisionEnv, ParameterSpec
from app.backtest.optimizer import BanditConfig, EpsilonGreedyBandit
from app.rl import TORCH_AVAILABLE, DecisionEnvAdapter, PPOConfig, train_ppo
from app.backtest.engine import BacktestEngine, PortfolioState, BtConfig, run_backtest from app.backtest.engine import BacktestEngine, PortfolioState, BtConfig, run_backtest
from app.ingest.checker import run_boot_check from app.ingest.checker import run_boot_check
from app.ingest.tushare import run_ingestion from app.ingest.tushare import run_ingestion
from app.llm.client import run_llm from app.llm.client import run_llm
from app.llm.metrics import reset as reset_llm_metrics from app.llm.metrics import reset as reset_llm_metrics
from app.llm.metrics import snapshot as snapshot_llm_metrics from app.llm.metrics import snapshot as snapshot_llm_metrics
from app.llm.templates import TemplateRegistry
from app.utils import alerts from app.utils import alerts
from app.utils.config import get_config, save_config from app.utils.config import get_config, save_config
from app.utils.tuning import log_tuning_result
from app.utils.portfolio import ( from app.utils.portfolio import (
get_candidate_pool, get_candidate_pool,
get_portfolio_settings_snapshot, get_portfolio_settings_snapshot,
@ -206,7 +200,7 @@ def render_backtest_review() -> None:
extra=LOG_EXTRA, extra=LOG_EXTRA,
) )
tab_backtest, tab_rl = st.tabs(["回测验证", "强化学习调参"]) tab_backtest, tab_tuning = st.tabs(["回测复盘", "实验调参"])
with tab_backtest: with tab_backtest:
st.markdown("#### 回测执行") st.markdown("#### 回测执行")

1082
app/ui/views/tuning.py Normal file

File diff suppressed because it is too large Load Diff