This commit is contained in:
sam 2025-10-05 21:02:07 +08:00
parent a492d6a9f7
commit 8a4ca05155

View File

@ -2470,6 +2470,130 @@ def render_llm_settings() -> None:
st.caption("部门配置存储为独立 LLM 参数,执行时会自动套用对应 Provider 的连接信息。") st.caption("部门配置存储为独立 LLM 参数,执行时会自动套用对应 Provider 的连接信息。")
def render_market_visualization() -> None:
"""Render a standalone market data visualization dashboard."""
st.header("股票行情可视化")
options = _load_stock_options()
default_code = options[0] if options else "000001.SZ"
if options:
selection = st.selectbox("选择股票", options, index=0)
ts_code = _parse_ts_code(selection)
LOGGER.debug("选择股票:%s", ts_code, extra=LOG_EXTRA)
else:
ts_code = st.text_input("输入股票代码(如 000001.SZ", value=default_code).strip().upper()
LOGGER.debug("输入股票:%s", ts_code, extra=LOG_EXTRA)
col_start, col_end = st.columns(2)
default_start = date.today() - timedelta(days=180)
start_date = col_start.date_input("开始日期", value=default_start, key="viz_start")
end_date = col_end.date_input("结束日期", value=date.today(), key="viz_end")
LOGGER.debug("行情可视化日期范围:%s-%s", start_date, end_date, extra=LOG_EXTRA)
if start_date > end_date:
LOGGER.warning("无效日期范围:%s>%s", start_date, end_date, extra=LOG_EXTRA)
st.error("开始日期不能晚于结束日期")
return
with st.spinner("正在加载行情数据..."):
try:
df = _load_daily_frame(ts_code, start_date, end_date)
except Exception as exc: # noqa: BLE001
LOGGER.exception("加载行情数据失败", extra=LOG_EXTRA)
st.error(f"读取数据失败:{exc}")
return
if df.empty:
LOGGER.warning("指定区间无行情数据:%s %s-%s", ts_code, start_date, end_date, extra=LOG_EXTRA)
st.warning("未查询到该区间的交易数据,请确认数据库已拉取对应日线。")
return
price_df = df[["close"]].rename(columns={"close": "收盘价"})
volume_df = df[["vol"]].rename(columns={"vol": "成交量(手)"})
sampled = price_df.resample("3D").last().dropna() if price_df.shape[0] > 180 else price_df
volume_sampled = volume_df.resample("3D").mean().dropna() if volume_df.shape[0] > 180 else volume_df
first_close = sampled.iloc[0, 0]
last_close = sampled.iloc[-1, 0]
delta_abs = last_close - first_close
delta_pct = (delta_abs / first_close * 100) if first_close else 0.0
metric_col1, metric_col2, metric_col3 = st.columns(3)
metric_col1.metric("最新收盘价", f"{last_close:.2f}", delta=f"{delta_abs:+.2f}")
metric_col2.metric("区间涨跌幅", f"{delta_pct:+.2f}%")
metric_col3.metric("平均成交量", f"{volume_sampled['成交量(手)'].mean():.0f}")
df_reset = df.reset_index().rename(columns={
"trade_date": "交易日",
"open": "开盘价",
"high": "最高价",
"low": "最低价",
"close": "收盘价",
"vol": "成交量(手)",
"amount": "成交额(千元)",
})
df_reset["成交额(千元)"] = df_reset["成交额(千元)"] / 1000
numeric_columns = ["开盘价", "最高价", "最低价", "收盘价", "成交量(手)", "成交额(千元)"]
for col in numeric_columns:
if col in df_reset.columns:
df_reset[col] = pd.to_numeric(df_reset[col], errors="coerce")
df_reset["交易日"] = pd.to_datetime(df_reset["交易日"])
candle_fig = go.Figure(
data=[
go.Candlestick(
x=df_reset["交易日"],
open=df_reset["开盘价"],
high=df_reset["最高价"],
low=df_reset["最低价"],
close=df_reset["收盘价"],
name="K线",
)
]
)
candle_fig.update_layout(height=420, margin=dict(l=10, r=10, t=40, b=10))
st.plotly_chart(candle_fig, width="stretch")
vol_fig = px.bar(
df_reset,
x="交易日",
y="成交量(手)",
labels={"成交量(手)": "成交量(手)"},
title="成交量",
)
vol_fig.update_layout(height=280, margin=dict(l=10, r=10, t=40, b=10))
st.plotly_chart(vol_fig, width="stretch")
amt_fig = px.bar(
df_reset,
x="交易日",
y="成交额(千元)",
labels={"成交额(千元)": "成交额(千元)"},
title="成交额",
)
amt_fig.update_layout(height=280, margin=dict(l=10, r=10, t=40, b=10))
st.plotly_chart(amt_fig, width="stretch")
df_reset["月份"] = df_reset["交易日"].dt.to_period("M").astype(str)
df_reset["收盘价"] = pd.to_numeric(df_reset["收盘价"], errors="coerce")
box_fig = px.box(
df_reset,
x="月份",
y="收盘价",
points="outliers",
title="月度收盘价分布",
)
box_fig.update_layout(height=320, margin=dict(l=10, r=10, t=40, b=10))
st.plotly_chart(box_fig, width="stretch")
st.caption("提示:成交量单位为手,成交额以千元显示。箱线图按月展示收盘价分布。")
st.dataframe(df_reset.tail(20), width="stretch")
LOGGER.info("行情可视化完成,展示行数=%s", len(df_reset), extra=LOG_EXTRA)
def render_tests() -> None: def render_tests() -> None:
LOGGER.info("渲染自检页面", extra=LOG_EXTRA) LOGGER.info("渲染自检页面", extra=LOG_EXTRA)
st.header("自检测试") st.header("自检测试")
@ -2627,138 +2751,6 @@ def render_tests() -> None:
finally: finally:
progress_bar.progress(1.0) progress_bar.progress(1.0)
st.divider()
st.subheader("股票行情可视化")
options = _load_stock_options()
default_code = options[0] if options else "000001.SZ"
if options:
selection = st.selectbox("选择股票", options, index=0)
ts_code = _parse_ts_code(selection)
LOGGER.debug("选择股票:%s", ts_code, extra=LOG_EXTRA)
else:
ts_code = st.text_input("输入股票代码(如 000001.SZ", value=default_code).strip().upper()
LOGGER.debug("输入股票:%s", ts_code, extra=LOG_EXTRA)
viz_col1, viz_col2 = st.columns(2)
default_start = date.today() - timedelta(days=180)
start_date = viz_col1.date_input("开始日期", value=default_start, key="viz_start")
end_date = viz_col2.date_input("结束日期", value=date.today(), key="viz_end")
LOGGER.debug("行情可视化日期范围:%s-%s", start_date, end_date, extra=LOG_EXTRA)
if start_date > end_date:
LOGGER.warning("无效日期范围:%s>%s", start_date, end_date, extra=LOG_EXTRA)
st.error("开始日期不能晚于结束日期")
return
with st.spinner("正在加载行情数据..."):
try:
df = _load_daily_frame(ts_code, start_date, end_date)
except Exception as exc: # noqa: BLE001
LOGGER.exception("加载行情数据失败", extra=LOG_EXTRA)
st.error(f"读取数据失败:{exc}")
return
if df.empty:
LOGGER.warning("指定区间无行情数据:%s %s-%s", ts_code, start_date, end_date, extra=LOG_EXTRA)
st.warning("未查询到该区间的交易数据,请确认数据库已拉取对应日线。")
return
price_df = df[["close"]].rename(columns={"close": "收盘价"})
volume_df = df[["vol"]].rename(columns={"vol": "成交量(手)"})
if price_df.shape[0] > 180:
sampled = price_df.resample('3D').last().dropna()
else:
sampled = price_df
if volume_df.shape[0] > 180:
volume_sampled = volume_df.resample('3D').mean().dropna()
else:
volume_sampled = volume_df
first_close = sampled.iloc[0, 0]
last_close = sampled.iloc[-1, 0]
delta_abs = last_close - first_close
delta_pct = (delta_abs / first_close * 100) if first_close else 0.0
metric_col1, metric_col2, metric_col3 = st.columns(3)
metric_col1.metric("最新收盘价", f"{last_close:.2f}", delta=f"{delta_abs:+.2f}")
metric_col2.metric("区间涨跌幅", f"{delta_pct:+.2f}%")
metric_col3.metric("平均成交量", f"{volume_sampled['成交量(手)'].mean():.0f}")
df_reset = df.reset_index().rename(columns={
"trade_date": "交易日",
"open": "开盘价",
"high": "最高价",
"low": "最低价",
"close": "收盘价",
"vol": "成交量(手)",
"amount": "成交额(千元)",
})
df_reset["成交额(千元)"] = df_reset["成交额(千元)"] / 1000
# 确保所有列的数据类型正确避免PyArrow序列化错误
numeric_columns = ["开盘价", "最高价", "最低价", "收盘价", "成交量(手)", "成交额(千元)"]
for col in numeric_columns:
if col in df_reset.columns:
df_reset[col] = pd.to_numeric(df_reset[col], errors='coerce')
# 确保日期列是datetime类型
df_reset["交易日"] = pd.to_datetime(df_reset["交易日"])
candle_fig = go.Figure(
data=[
go.Candlestick(
x=df_reset["交易日"],
open=df_reset["开盘价"],
high=df_reset["最高价"],
low=df_reset["最低价"],
close=df_reset["收盘价"],
name="K线",
)
]
)
candle_fig.update_layout(height=420, margin=dict(l=10, r=10, t=40, b=10))
st.plotly_chart(candle_fig, width='stretch')
vol_fig = px.bar(
df_reset,
x="交易日",
y="成交量(手)",
labels={"成交量(手)": "成交量(手)"},
title="成交量",
)
vol_fig.update_layout(height=280, margin=dict(l=10, r=10, t=40, b=10))
st.plotly_chart(vol_fig, width='stretch')
amt_fig = px.bar(
df_reset,
x="交易日",
y="成交额(千元)",
labels={"成交额(千元)": "成交额(千元)"},
title="成交额",
)
amt_fig.update_layout(height=280, margin=dict(l=10, r=10, t=40, b=10))
st.plotly_chart(amt_fig, width='stretch')
df_reset["月份"] = df_reset["交易日"].dt.to_period("M").astype(str)
# 确保收盘价列是数值类型
df_reset["收盘价"] = pd.to_numeric(df_reset["收盘价"], errors='coerce')
box_fig = px.box(
df_reset,
x="月份",
y="收盘价",
points="outliers",
title="月度收盘价分布",
)
box_fig.update_layout(height=320, margin=dict(l=10, r=10, t=40, b=10))
st.plotly_chart(box_fig, width='stretch')
st.caption("提示:成交量单位为手,成交额以千元显示。箱线图按月展示收盘价分布。")
st.dataframe(df_reset.tail(20), width='stretch')
LOGGER.info("行情可视化完成,展示行数=%s", len(df_reset), extra=LOG_EXTRA)
st.divider() st.divider()
st.subheader("LLM 接口测试") st.subheader("LLM 接口测试")
st.json(llm_config_snapshot()) st.json(llm_config_snapshot())
@ -2889,10 +2881,10 @@ def main() -> None:
st.error(f"❌ 自动数据更新失败:{exc}") st.error(f"❌ 自动数据更新失败:{exc}")
render_global_dashboard() render_global_dashboard()
tabs = st.tabs(["今日计划", "回测与复盘", "日志钻取", "数据与设置", "自检测试"]) tabs = st.tabs(["今日计划", "回测与复盘", "行情可视化", "日志钻取", "数据与设置", "自检测试"])
LOGGER.debug( LOGGER.debug(
"Tabs 初始化完成:%s", "Tabs 初始化完成:%s",
["今日计划", "回测与复盘", "日志钻取", "数据与设置", "自检测试"], ["今日计划", "回测与复盘", "行情可视化", "日志钻取", "数据与设置", "自检测试"],
extra=LOG_EXTRA, extra=LOG_EXTRA,
) )
with tabs[0]: with tabs[0]:
@ -2900,8 +2892,10 @@ def main() -> None:
with tabs[1]: with tabs[1]:
render_backtest_review() render_backtest_review()
with tabs[2]: with tabs[2]:
render_log_viewer() render_market_visualization()
with tabs[3]: with tabs[3]:
render_log_viewer()
with tabs[4]:
st.header("系统设置") st.header("系统设置")
settings_tabs = st.tabs(["配置概览", "LLM 设置", "投资组合", "数据源"]) settings_tabs = st.tabs(["配置概览", "LLM 设置", "投资组合", "数据源"])
@ -2917,7 +2911,7 @@ def main() -> None:
with settings_tabs[3]: with settings_tabs[3]:
render_data_settings() render_data_settings()
with tabs[4]: with tabs[5]:
render_tests() render_tests()