CoolFace
Apppublic

Qionk/a-share-quant

sourceHugging Faceupdated 4mo agoView on Hugging Face
0likes
app.py205 linesDownload Raw Back to root
1"""2A股量化辅助决策仪表盘3启动: streamlit run app.py4"""5 6import streamlit as st7import pandas as pd8import numpy as np9import plotly.graph_objects as go10from src.data import load_config, load_panel_data, load_index_data, get_stock_pool11from src.factors import compute_all_factors, evaluate_factors12from src.signal import generate_signals13from src.backtest import backtest, calc_metrics14 15st.set_page_config(page_title="A股量化辅助决策", layout="wide")16st.title("A股量化辅助决策仪表盘")17 18 19# ── 数据加载(带缓存)──────────────────────────────────────20 21 22@st.cache_data(ttl=3600)23def load_all():24    config = load_config()25    pool = get_stock_pool(config)26    panel = load_panel_data(config, codes=pool["code"].tolist())27    index_data = load_index_data(config)28    return config, pool, panel, index_data29 30 31try:32    config, pool, panel, index_data = load_all()33except Exception as e:34    st.error(f"数据加载失败: {e}")35    st.info("请先运行 `python run.py update` 获取数据")36    st.stop()37 38if not panel:39    st.warning("无数据,请先运行 `python run.py update`")40    st.stop()41 42factors, breadth = compute_all_factors(panel, config)43signals, scores, regime = generate_signals(factors, breadth, config)44 45 46# ── 页签 ─────────────────────────────────────────────────────47 48tab1, tab2, tab3, tab4 = st.tabs(["今日信号", "回测分析", "因子检验", "市场情绪"])49 50 51# ═══════ Tab 1: 今日信号 ═══════52 53with tab1:54    latest = scores.index[-1]55    st.subheader(f"{latest.strftime('%Y-%m-%d')} 关注池")56 57    # 市场状态58    c1, c2 = st.columns(2)59    regime_map = {"normal": "正常", "caution": "谨慎", "bear": "回避"}60    c1.metric("市场状态", regime_map.get(regime.iloc[-1], regime.iloc[-1]))61    c2.metric("市场宽度", f"{breadth.iloc[-1]:.1%}")62 63    # Top N64    today_scores = scores.loc[latest].dropna().sort_values(ascending=False)65    top = today_scores.head(config["signal"]["top_n"])66 67    rows = []68    for code in top.index:69        rows.append({70            "代码": code,71            "综合评分": f"{top[code]:.3f}",72            "相对强度": f'{factors["relative_strength"].loc[latest].get(code, np.nan):.2f}',73            "趋势得分": f'{factors["trend"].loc[latest].get(code, np.nan):.2f}',74            "信号确认": "是" if signals.loc[latest].get(code, False) else "否",75        })76    st.dataframe(pd.DataFrame(rows), use_container_width=True, hide_index=True)77 78    # 单只股票 K 线79    st.subheader("个股走势")80    selected = st.selectbox("选择股票", top.index.tolist())81    if selected and selected in panel["close"].columns:82        price = panel["close"][selected].dropna().tail(120)83        fig = go.Figure(go.Scatter(x=price.index, y=price.values, mode="lines", name=selected))84        for w in config["factors"]["ma_windows"]:85            ma = price.rolling(w).mean()86            fig.add_trace(go.Scatter(x=ma.index, y=ma.values, mode="lines",87                                     name=f"MA{w}", line=dict(dash="dash")))88        fig.update_layout(title=f"{selected} 近 120 日走势", height=400,89                          xaxis_title="日期", yaxis_title="价格(后复权)")90        st.plotly_chart(fig, use_container_width=True)91 92 93# ═══════ Tab 2: 回测分析 ═══════94 95with tab2:96    st.subheader("策略回测")97 98    results = backtest(signals, scores, panel["close"], config)99 100    if not results["daily_returns"].empty:101        metrics = calc_metrics(results["daily_returns"])102 103        # 指标卡片104        cols = st.columns(4)105        items = list(metrics.items())106        for i, (k, v) in enumerate(items[:4]):107            cols[i].metric(k, v)108        if len(items) > 4:109            cols2 = st.columns(4)110            for i, (k, v) in enumerate(items[4:8]):111                cols2[i].metric(k, v)112 113        # 净值曲线114        fig = go.Figure()115        fig.add_trace(go.Scatter(116            x=results["daily_returns"].index,117            y=results["daily_returns"]["value"],118            mode="lines", name="策略净值",119        ))120        fig.update_layout(title="策略净值曲线", height=400,121                          xaxis_title="日期", yaxis_title="净值")122        st.plotly_chart(fig, use_container_width=True)123 124        # 回撤曲线125        val = results["daily_returns"]["value"]126        dd = (val - val.cummax()) / val.cummax()127        fig2 = go.Figure()128        fig2.add_trace(go.Scatter(129            x=dd.index, y=dd.values,130            fill="tozeroy", fillcolor="rgba(255,0,0,0.1)",131            line=dict(color="red"), name="回撤",132        ))133        fig2.update_layout(title="回撤曲线", height=300,134                           xaxis_title="日期", yaxis_title="回撤")135        st.plotly_chart(fig2, use_container_width=True)136 137        # 交易日志138        if not results["trade_log"].empty:139            with st.expander("交易日志(最近 50 条)"):140                st.dataframe(results["trade_log"].tail(50), use_container_width=True, hide_index=True)141    else:142        st.warning("回测数据不足")143 144 145# ═══════ Tab 3: 因子检验 ═══════146 147with tab3:148    st.subheader("因子 Rank IC 分析")149 150    ic_results = evaluate_factors(factors, panel["close"])151 152    if ic_results:153        summary = pd.DataFrame({154            name: {155                "IC 均值": f'{r["ic_mean"]:.4f}',156                "IC 标准差": f'{r["ic_std"]:.4f}',157                "ICIR": f'{r["icir"]:.4f}',158                "IC>0 占比": f'{r["ic_positive_pct"]:.1%}',159            }160            for name, r in ic_results.items()161        }).T162        st.dataframe(summary, use_container_width=True)163 164        sel = st.selectbox("选择因子查看 IC 时序", list(ic_results.keys()))165        if sel:166            ic_s = ic_results[sel]["ic_series"]167            fig = go.Figure(go.Bar(x=ic_s.index, y=ic_s.values, name="IC"))168            fig.add_hline(y=ic_s.mean(), line_dash="dash", line_color="red",169                          annotation_text=f"均值: {ic_s.mean():.4f}")170            fig.update_layout(title=f"{sel} - IC 时序", height=400,171                              xaxis_title="日期", yaxis_title="IC")172            st.plotly_chart(fig, use_container_width=True)173    else:174        st.info("因子数据不足,无法评估")175 176 177# ═══════ Tab 4: 市场情绪 ═══════178 179with tab4:180    st.subheader("市场宽度指标")181 182    fig = go.Figure()183    fig.add_trace(go.Scatter(x=breadth.index, y=breadth.values, mode="lines", name="市场宽度"))184    fig.add_hline(y=config["market"]["breadth_threshold_high"],185                  line_dash="dash", line_color="green", annotation_text="多头阈值")186    fig.add_hline(y=config["market"]["breadth_threshold_low"],187                  line_dash="dash", line_color="red", annotation_text="空头阈值")188    fig.update_layout(title="市场宽度(站上 20 日均线股票占比)", height=400,189                      xaxis_title="日期", yaxis_title="占比")190    st.plotly_chart(fig, use_container_width=True)191 192    # 指数 K 线193    if not index_data.empty:194        fig2 = go.Figure(go.Candlestick(195            x=index_data.index,196            open=index_data["open"], high=index_data["high"],197            low=index_data["low"], close=index_data["close"],198            name=config["market"]["index_code"],199        ))200        fig2.update_layout(201            title=f'指数走势 ({config["market"]["index_code"]})',202            height=450, xaxis_rangeslider_visible=False,203        )204        st.plotly_chart(fig2, use_container_width=True)205