From 519a4144e65726f4f3c6812ab4fb038a161042db Mon Sep 17 00:00:00 2001 From: aszerW Date: Fri, 8 May 2026 21:20:48 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E7=A7=BB=E9=99=A4=20Streamlit=20?= =?UTF-8?q?=E5=8F=AF=E8=A7=86=E5=8C=96=E5=BA=94=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 streamlit_app.py - 删除 dashboard_app.py - 删除 requirements_streamlit.txt - 删除 start_streamlit.sh --- requirements_streamlit.txt | 5 - start_streamlit.sh | 24 --- streamlit_app.py | 366 ------------------------------------- 3 files changed, 395 deletions(-) delete mode 100644 requirements_streamlit.txt delete mode 100755 start_streamlit.sh delete mode 100644 streamlit_app.py diff --git a/requirements_streamlit.txt b/requirements_streamlit.txt deleted file mode 100644 index d98edd8..0000000 --- a/requirements_streamlit.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Streamlit 可视化应用依赖 -streamlit>=1.28.0 -plotly>=5.15.0 -pandas>=2.0.0 -requests>=2.31.0 diff --git a/start_streamlit.sh b/start_streamlit.sh deleted file mode 100755 index 7e96f18..0000000 --- a/start_streamlit.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# Streamlit 应用启动脚本 - -cd "$(dirname "$0")" - -echo "🚀 启动 ETF轮动策略可视化应用..." -echo "" - -# 检查虚拟环境 -if [ -d "venv" ]; then - source venv/bin/activate -fi - -# 安装依赖(如果需要) -# pip install -r requirements_streamlit.txt - -# 设置环境变量 -export API_BASE_URL=${API_BASE_URL:-"https://k3s.tokenpluse.xyz"} - -echo "📊 正在启动 Streamlit 服务..." -echo "🌐 API地址: $API_BASE_URL" -echo "" - -streamlit run streamlit_app.py --server.port 8501 --server.address 0.0.0.0 diff --git a/streamlit_app.py b/streamlit_app.py deleted file mode 100644 index 45b0ad8..0000000 --- a/streamlit_app.py +++ /dev/null @@ -1,366 +0,0 @@ -""" -ETF轮动策略回测结果可视化 -============================ -使用 Streamlit 展示策略回测结果 - -运行: streamlit run streamlit_app.py -""" - -import streamlit as st -import pandas as pd -import plotly.express as px -import plotly.graph_objects as go -from plotly.subplots import make_subplots -import requests -from datetime import datetime -import os - -# 页面配置 -st.set_page_config( - page_title="ETF轮动策略回测结果", - page_icon="📈", - layout="wide", - initial_sidebar_state="expanded" -) - -# 样式 -st.markdown(""" - -""", unsafe_allow_html=True) - -# 标题 -st.markdown('

📈 ETF轮动策略回测结果

', unsafe_allow_html=True) -st.markdown("---") - -# 侧边栏配置 -st.sidebar.header("⚙️ 配置") - -# 数据API配置 -api_base = st.sidebar.text_input( - "数据API地址", - value=os.getenv('API_BASE_URL', 'https://k3s.tokenpluse.xyz'), - help="Flask API服务地址" -) - -# 加载数据 -@st.cache_data(ttl=3600) -def load_summary_data(): - """加载策略汇总数据""" - try: - df = pd.read_csv('results/report_summary.csv') - # 转换百分比字符串为数值 - for col in ['胜率', '平均收益', '累计收益', '最大单次收益', '最大单次亏损']: - if col in df.columns: - df[col] = df[col].str.rstrip('%').astype(float) - return df - except Exception as e: - st.error(f"加载汇总数据失败: {e}") - return None - -@st.cache_data(ttl=3600) -def load_trades_data(): - """加载交易记录数据""" - try: - df = pd.read_csv('results/report_trades.csv') - df['进场日期'] = pd.to_datetime(df['进场日期']) - df['出场日期'] = pd.to_datetime(df['出场日期']) - return df - except Exception as e: - st.error(f"加载交易数据失败: {e}") - return None - -# 加载数据 -summary_df = load_summary_data() -trades_df = load_trades_data() - -# 主页面标签页 -tab1, tab2, tab3, tab4 = st.tabs(["📊 策略概览", "📈 收益分析", "🔄 调仓记录", "🔍 品种详情"]) - -# ============ Tab 1: 策略概览 ============ -with tab1: - st.header("策略绩效概览") - - if summary_df is not None: - # 关键指标 - col1, col2, col3, col4 = st.columns(4) - - with col1: - total_trades = summary_df['调仓次数'].sum() - st.metric("总调仓次数", f"{total_trades:,}") - - with col2: - avg_win_rate = summary_df['胜率'].mean() - st.metric("平均胜率", f"{avg_win_rate:.2f}%") - - with col3: - best_return = summary_df['累计收益'].max() - best_code = summary_df.loc[summary_df['累计收益'].idxmax(), '品种代码'] - st.metric("最佳品种收益", f"{best_return:.2f}%", f"{best_code}") - - with col4: - worst_return = summary_df['累计收益'].min() - worst_code = summary_df.loc[summary_df['累计收益'].idxmin(), '品种代码'] - st.metric("最差品种收益", f"{worst_return:.2f}%", f"{worst_code}") - - st.markdown("---") - - # 品种收益对比 - fig = px.bar( - summary_df.sort_values('累计收益', ascending=True), - x='累计收益', - y='品种名称', - orientation='h', - color='累计收益', - color_continuous_scale=['red', 'yellow', 'green'], - title="各品种累计收益对比", - labels={'累计收益': '累计收益 (%)', '品种名称': ''} - ) - fig.update_layout(height=400) - st.plotly_chart(fig, use_container_width=True) - - # 胜率 vs 平均收益散点图 - fig2 = px.scatter( - summary_df, - x='胜率', - y='平均收益', - size='调仓次数', - color='品种名称', - hover_data=['品种代码', '累计收益'], - title="胜率 vs 平均收益(气泡大小=调仓次数)", - labels={'胜率': '胜率 (%)', '平均收益': '平均收益 (%)'} - ) - st.plotly_chart(fig2, use_container_width=True) - - # 数据表格 - st.subheader("详细数据") - st.dataframe( - summary_df.style.format({ - '胜率': '{:.2f}%', - '平均收益': '{:.2f}%', - '累计收益': '{:.2f}%', - '最大单次收益': '{:.2f}%', - '最大单次亏损': '{:.2f}%' - }), - use_container_width=True - ) - else: - st.warning("暂无汇总数据") - -# ============ Tab 2: 收益分析 ============ -with tab2: - st.header("收益分析") - - if trades_df is not None: - # 按月份统计收益 - trades_df['年月'] = trades_df['出场日期'].dt.to_period('M') - monthly_returns = trades_df.groupby('年月').agg({ - '持仓收益': 'sum', - '品种代码': 'count' - }).rename(columns={'品种代码': '交易次数'}) - monthly_returns.index = monthly_returns.index.astype(str) - - # 月度收益柱状图 - fig = px.bar( - monthly_returns.reset_index(), - x='年月', - y='持仓收益', - title="月度累计收益", - labels={'持仓收益': '收益 (%)', '年月': '月份'}, - color='持仓收益', - color_continuous_scale=['red', 'yellow', 'green'] - ) - st.plotly_chart(fig, use_container_width=True) - - # 收益分布直方图 - fig2 = px.histogram( - trades_df, - x='持仓收益', - nbins=50, - title="单次交易收益分布", - labels={'持仓收益': '收益 (%)', 'count': '次数'} - ) - fig2.add_vline(x=0, line_dash="dash", line_color="red") - st.plotly_chart(fig2, use_container_width=True) - - # 累计收益曲线 - trades_sorted = trades_df.sort_values('出场日期') - trades_sorted['累计收益_曲线'] = trades_sorted['持仓收益'].cumsum() - - fig3 = px.line( - trades_sorted, - x='出场日期', - y='累计收益_曲线', - title="策略累计收益曲线", - labels={'累计收益_曲线': '累计收益 (%)', '出场日期': '日期'} - ) - st.plotly_chart(fig3, use_container_width=True) - else: - st.warning("暂无交易数据") - -# ============ Tab 3: 调仓记录 ============ -with tab3: - st.header("调仓记录") - - if trades_df is not None: - # 筛选器 - col1, col2, col3 = st.columns(3) - - with col1: - selected_codes = st.multiselect( - "选择品种", - options=trades_df['品种代码'].unique(), - default=[] - ) - - with col2: - min_return = st.slider( - "最小收益", - min_value=float(trades_df['持仓收益'].min()), - max_value=float(trades_df['持仓收益'].max()), - value=float(trades_df['持仓收益'].min()) - ) - - with col3: - min_days = st.slider( - "最小持仓天数", - min_value=1, - max_value=int(trades_df['持仓天数'].max()), - value=1 - ) - - # 过滤数据 - filtered_df = trades_df.copy() - if selected_codes: - filtered_df = filtered_df[filtered_df['品种代码'].isin(selected_codes)] - filtered_df = filtered_df[ - (filtered_df['持仓收益'] >= min_return) & - (filtered_df['持仓天数'] >= min_days) - ] - - # 显示表格 - st.subheader(f"筛选结果 ({len(filtered_df)} 条记录)") - - # 添加颜色标记 - def color_return(val): - color = 'green' if val > 0 else 'red' - return f'color: {color}' - - styled_df = filtered_df.style.applymap( - color_return, - subset=['持仓收益'] - ).format({ - '进场价格': '{:.2f}', - '出场价格': '{:.2f}', - '持仓收益': '{:.2f}%', - '进场净值': '{:.4f}', - '出场净值': '{:.4f}', - '净值贡献': '{:.4f}' - }) - - st.dataframe(styled_df, use_container_width=True, height=500) - - # 导出按钮 - csv = filtered_df.to_csv(index=False).encode('utf-8') - st.download_button( - label="📥 导出筛选结果", - data=csv, - file_name=f"trades_filtered_{datetime.now().strftime('%Y%m%d')}.csv", - mime="text/csv" - ) - else: - st.warning("暂无调仓记录") - -# ============ Tab 4: 品种详情 ============ -with tab4: - st.header("品种详情分析") - - if summary_df is not None and trades_df is not None: - selected_code = st.selectbox( - "选择品种", - options=summary_df['品种代码'].tolist(), - format_func=lambda x: f"{x} - {summary_df[summary_df['品种代码']==x]['品种名称'].iloc[0]}" - ) - - if selected_code: - # 品种基本信息 - code_info = summary_df[summary_df['品种代码'] == selected_code].iloc[0] - - col1, col2, col3, col4 = st.columns(4) - with col1: - st.metric("调仓次数", int(code_info['调仓次数'])) - with col2: - st.metric("胜率", f"{code_info['胜率']:.2f}%") - with col3: - st.metric("累计收益", f"{code_info['累计收益']:.2f}%") - with col4: - st.metric("平均持仓天数", f"{code_info['平均持仓天数']:.1f}天") - - # 该品种的交易记录 - code_trades = trades_df[trades_df['品种代码'] == selected_code].sort_values('出场日期') - - if len(code_trades) > 0: - # 收益时间序列 - code_trades['累计收益_品种'] = code_trades['持仓收益'].cumsum() - - fig = make_subplots( - rows=2, cols=1, - subplot_titles=('单次收益', '累计收益'), - vertical_spacing=0.1 - ) - - fig.add_trace( - go.Bar( - x=code_trades['出场日期'], - y=code_trades['持仓收益'], - name='单次收益', - marker_color=['green' if x > 0 else 'red' for x in code_trades['持仓收益']] - ), - row=1, col=1 - ) - - fig.add_trace( - go.Scatter( - x=code_trades['出场日期'], - y=code_trades['累计收益_品种'], - name='累计收益', - mode='lines+markers' - ), - row=2, col=1 - ) - - fig.update_layout(height=600, showlegend=False) - st.plotly_chart(fig, use_container_width=True) - - # 持仓天数分布 - fig2 = px.box( - code_trades, - y='持仓天数', - title=f"{code_info['品种名称']} 持仓天数分布" - ) - st.plotly_chart(fig2, use_container_width=True) - else: - st.info("该品种暂无交易记录") - else: - st.warning("暂无数据") - -# 底部信息 -st.markdown("---") -st.markdown(""" -
-

ETF轮动策略回测系统 | 数据更新时间: {}

-
-""".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')), unsafe_allow_html=True)