增加定时分析功能
This commit is contained in:
+771
@@ -0,0 +1,771 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
持仓管理UI模块
|
||||
提供持仓股票的增删改查、批量分析、定时任务管理界面
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import pandas as pd
|
||||
from datetime import datetime
|
||||
from typing import List, Dict
|
||||
import time
|
||||
|
||||
from portfolio_manager import portfolio_manager
|
||||
from portfolio_scheduler import portfolio_scheduler
|
||||
|
||||
|
||||
def display_portfolio_manager():
|
||||
"""显示持仓管理主界面"""
|
||||
|
||||
st.markdown("## 📊 持仓定时分析")
|
||||
st.markdown("---")
|
||||
|
||||
# 创建标签页
|
||||
tab1, tab2, tab3, tab4 = st.tabs([
|
||||
"📝 持仓管理",
|
||||
"🔄 批量分析",
|
||||
"⏰ 定时任务",
|
||||
"📈 分析历史"
|
||||
])
|
||||
|
||||
with tab1:
|
||||
display_portfolio_stocks()
|
||||
|
||||
with tab2:
|
||||
display_batch_analysis()
|
||||
|
||||
with tab3:
|
||||
display_scheduler_management()
|
||||
|
||||
with tab4:
|
||||
display_analysis_history()
|
||||
|
||||
|
||||
def display_portfolio_stocks():
|
||||
"""显示持仓股票列表和管理"""
|
||||
|
||||
st.markdown("### 📝 持仓股票管理")
|
||||
|
||||
# 添加新股票表单
|
||||
with st.expander("➕ 添加持仓股票", expanded=False):
|
||||
display_add_stock_form()
|
||||
|
||||
# 获取所有持仓股票
|
||||
stocks = portfolio_manager.get_all_stocks()
|
||||
|
||||
if not stocks:
|
||||
st.info("暂无持仓股票,请添加股票代码开始管理。")
|
||||
return
|
||||
|
||||
# 显示统计
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
st.metric("持仓股票数", len(stocks))
|
||||
with col2:
|
||||
auto_monitor_count = sum(1 for s in stocks if s.get("auto_monitor"))
|
||||
st.metric("启用自动监测", auto_monitor_count)
|
||||
with col3:
|
||||
total_cost = sum(
|
||||
s.get("cost_price", 0) * s.get("quantity", 0)
|
||||
for s in stocks
|
||||
if s.get("cost_price") and s.get("quantity")
|
||||
)
|
||||
st.metric("总持仓成本", f"¥{total_cost:,.2f}")
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# 显示股票列表(卡片式布局)
|
||||
for stock in stocks:
|
||||
display_stock_card(stock)
|
||||
|
||||
|
||||
def display_stock_card(stock: Dict):
|
||||
"""显示单个股票卡片"""
|
||||
|
||||
code = stock.get("code", "")
|
||||
name = stock.get("name", "")
|
||||
cost_price = stock.get("cost_price")
|
||||
quantity = stock.get("quantity")
|
||||
note = stock.get("note", "")
|
||||
auto_monitor = stock.get("auto_monitor", True)
|
||||
created_at = stock.get("created_at", "")
|
||||
|
||||
# 创建卡片
|
||||
with st.container():
|
||||
col1, col2, col3, col4 = st.columns([3, 2, 2, 2])
|
||||
|
||||
with col1:
|
||||
st.markdown(f"**{code}** {name}")
|
||||
if note:
|
||||
st.caption(f"备注: {note}")
|
||||
|
||||
with col2:
|
||||
if cost_price and quantity:
|
||||
st.write(f"成本: ¥{cost_price:.2f}")
|
||||
st.caption(f"数量: {quantity}股")
|
||||
else:
|
||||
st.caption("未设置持仓")
|
||||
|
||||
with col3:
|
||||
if auto_monitor:
|
||||
st.success("🔔 自动监测")
|
||||
else:
|
||||
st.info("🔕 不监测")
|
||||
|
||||
with col4:
|
||||
col_edit, col_del = st.columns(2)
|
||||
with col_edit:
|
||||
if st.button("✏️", key=f"edit_{code}", help="编辑"):
|
||||
st.session_state[f"editing_{code}"] = True
|
||||
st.rerun()
|
||||
with col_del:
|
||||
if st.button("🗑️", key=f"del_{code}", help="删除"):
|
||||
portfolio_manager.delete_stock(code)
|
||||
st.success(f"已删除 {code}")
|
||||
time.sleep(0.5)
|
||||
st.rerun()
|
||||
|
||||
# 编辑表单(如果处于编辑状态)
|
||||
if st.session_state.get(f"editing_{code}"):
|
||||
with st.form(key=f"edit_form_{code}"):
|
||||
st.markdown(f"#### 编辑 {code}")
|
||||
|
||||
col_a, col_b = st.columns(2)
|
||||
with col_a:
|
||||
new_cost = st.number_input(
|
||||
"成本价",
|
||||
value=cost_price if cost_price else 0.0,
|
||||
min_value=0.0,
|
||||
step=0.01
|
||||
)
|
||||
new_quantity = st.number_input(
|
||||
"持仓数量",
|
||||
value=quantity if quantity else 0,
|
||||
min_value=0,
|
||||
step=100
|
||||
)
|
||||
|
||||
with col_b:
|
||||
new_note = st.text_area("备注", value=note, height=80)
|
||||
new_auto_monitor = st.checkbox("自动同步到监测", value=auto_monitor)
|
||||
|
||||
col_submit, col_cancel = st.columns(2)
|
||||
with col_submit:
|
||||
if st.form_submit_button("保存", type="primary"):
|
||||
portfolio_manager.update_stock(
|
||||
code,
|
||||
cost_price=new_cost if new_cost > 0 else None,
|
||||
quantity=new_quantity if new_quantity > 0 else None,
|
||||
note=new_note,
|
||||
auto_monitor=new_auto_monitor
|
||||
)
|
||||
del st.session_state[f"editing_{code}"]
|
||||
st.success("更新成功!")
|
||||
time.sleep(0.5)
|
||||
st.rerun()
|
||||
|
||||
with col_cancel:
|
||||
if st.form_submit_button("取消"):
|
||||
del st.session_state[f"editing_{code}"]
|
||||
st.rerun()
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
|
||||
def display_add_stock_form():
|
||||
"""显示添加股票表单"""
|
||||
|
||||
with st.form(key="add_stock_form"):
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
code = st.text_input(
|
||||
"股票代码*",
|
||||
placeholder="例如: 600519.SH 或 000001.SZ",
|
||||
help="必填,格式:代码.市场(SH/SZ/HK/US)"
|
||||
)
|
||||
name = st.text_input(
|
||||
"股票名称",
|
||||
placeholder="例如: 贵州茅台",
|
||||
help="可选,留空将自动获取"
|
||||
)
|
||||
|
||||
with col2:
|
||||
cost_price = st.number_input(
|
||||
"成本价",
|
||||
min_value=0.0,
|
||||
step=0.01,
|
||||
help="可选,用于计算收益"
|
||||
)
|
||||
quantity = st.number_input(
|
||||
"持仓数量",
|
||||
min_value=0,
|
||||
step=100,
|
||||
help="可选,单位:股"
|
||||
)
|
||||
|
||||
note = st.text_area("备注", height=80, placeholder="可选,记录买入理由等信息")
|
||||
auto_monitor = st.checkbox("分析后自动同步到监测", value=True)
|
||||
|
||||
if st.form_submit_button("➕ 添加股票", type="primary"):
|
||||
if not code:
|
||||
st.error("请输入股票代码")
|
||||
else:
|
||||
try:
|
||||
portfolio_manager.add_stock(
|
||||
code=code.strip().upper(),
|
||||
name=name.strip() if name else None,
|
||||
cost_price=cost_price if cost_price > 0 else None,
|
||||
quantity=quantity if quantity > 0 else None,
|
||||
note=note.strip() if note else None,
|
||||
auto_monitor=auto_monitor
|
||||
)
|
||||
st.success(f"✅ 已添加 {code} 到持仓列表")
|
||||
time.sleep(0.5)
|
||||
st.rerun()
|
||||
except Exception as e:
|
||||
st.error(f"添加失败: {str(e)}")
|
||||
|
||||
|
||||
def display_batch_analysis():
|
||||
"""显示批量分析功能"""
|
||||
|
||||
st.markdown("### 🔄 批量分析持仓股票")
|
||||
|
||||
stocks = portfolio_manager.get_all_stocks()
|
||||
|
||||
if not stocks:
|
||||
st.warning("暂无持仓股票,请先添加股票。")
|
||||
return
|
||||
|
||||
# 分析选项
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
st.metric("持仓股票数", len(stocks))
|
||||
|
||||
with col2:
|
||||
analysis_mode = st.selectbox(
|
||||
"分析模式",
|
||||
options=["sequential", "parallel"],
|
||||
format_func=lambda x: "顺序分析" if x == "sequential" else "并行分析",
|
||||
help="顺序分析较慢但稳定,并行分析更快但消耗更多资源"
|
||||
)
|
||||
|
||||
with col3:
|
||||
if analysis_mode == "parallel":
|
||||
max_workers = st.number_input(
|
||||
"并行线程数",
|
||||
min_value=2,
|
||||
max_value=10,
|
||||
value=3,
|
||||
help="同时分析的股票数量"
|
||||
)
|
||||
else:
|
||||
max_workers = 1
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# 同步和通知选项
|
||||
col_a, col_b = st.columns(2)
|
||||
|
||||
with col_a:
|
||||
auto_sync = st.checkbox(
|
||||
"自动同步到监测",
|
||||
value=True,
|
||||
help="分析完成后自动将评级结果同步到实时监测列表"
|
||||
)
|
||||
|
||||
with col_b:
|
||||
send_notification = st.checkbox(
|
||||
"发送完成通知",
|
||||
value=True,
|
||||
help="通过邮件或Webhook发送分析完成通知"
|
||||
)
|
||||
|
||||
# 立即分析按钮
|
||||
if st.button("🚀 立即开始分析", type="primary", use_container_width=True):
|
||||
with st.spinner("正在批量分析持仓股票..."):
|
||||
# 显示进度
|
||||
progress_bar = st.progress(0)
|
||||
status_text = st.empty()
|
||||
|
||||
# 执行批量分析
|
||||
try:
|
||||
# 定义进度回调函数
|
||||
def update_progress(current, total, code, status):
|
||||
progress_bar.progress(current / total)
|
||||
status_map = {
|
||||
"analyzing": "正在分析",
|
||||
"success": "✅ 完成",
|
||||
"failed": "❌ 失败",
|
||||
"error": "⚠️ 错误"
|
||||
}
|
||||
status_text.text(f"{status_map.get(status, '处理中')} {code} ({current}/{total})")
|
||||
|
||||
result = portfolio_manager.batch_analyze_portfolio(
|
||||
mode=analysis_mode,
|
||||
max_workers=max_workers,
|
||||
progress_callback=update_progress
|
||||
)
|
||||
|
||||
# 清除进度显示
|
||||
progress_bar.empty()
|
||||
status_text.empty()
|
||||
|
||||
# 显示结果
|
||||
st.success(f"✅ 批量分析完成!")
|
||||
|
||||
col_r1, col_r2, col_r3, col_r4 = st.columns(4)
|
||||
with col_r1:
|
||||
st.metric("总计", result.get("total", 0))
|
||||
with col_r2:
|
||||
st.metric("成功", result.get("succeeded", 0))
|
||||
with col_r3:
|
||||
st.metric("失败", result.get("failed", 0))
|
||||
with col_r4:
|
||||
st.metric("耗时", f"{result.get('elapsed_time', 0):.1f}秒")
|
||||
|
||||
# 保存分析结果到数据库
|
||||
saved_ids = portfolio_manager.save_analysis_results(result)
|
||||
st.info(f"💾 已保存 {len(saved_ids)} 条分析记录到数据库")
|
||||
|
||||
# 同步到监测
|
||||
sync_result = None # 初始化同步结果
|
||||
if auto_sync:
|
||||
with st.spinner("正在同步到监测列表..."):
|
||||
from monitor_db import monitor_db
|
||||
|
||||
# 准备同步数据
|
||||
monitors_to_sync = []
|
||||
for item in result.get("results", []):
|
||||
# 检查分析是否成功
|
||||
if not item.get("result", {}).get("success"):
|
||||
continue
|
||||
|
||||
code = item["code"]
|
||||
stock = portfolio_manager.db.get_stock_by_code(code)
|
||||
|
||||
# 只同步启用了自动监测的股票
|
||||
if not stock or not stock.get("auto_monitor"):
|
||||
continue
|
||||
|
||||
analysis_result = item["result"]
|
||||
stock_info = analysis_result.get("stock_info", {})
|
||||
final_decision = analysis_result.get("final_decision", {})
|
||||
|
||||
# 从final_decision中提取数据
|
||||
rating = final_decision.get("rating", "持有")
|
||||
entry_range = final_decision.get("entry_range", "")
|
||||
take_profit_str = final_decision.get("take_profit", "")
|
||||
stop_loss_str = final_decision.get("stop_loss", "")
|
||||
|
||||
# 解析进场区间(格式如"10.5-12.3")
|
||||
entry_min, entry_max = None, None
|
||||
if entry_range and isinstance(entry_range, str) and "-" in entry_range:
|
||||
try:
|
||||
parts = entry_range.split("-")
|
||||
entry_min = float(parts[0].strip())
|
||||
entry_max = float(parts[1].strip())
|
||||
except:
|
||||
pass
|
||||
|
||||
# 解析止盈止损(提取数字)
|
||||
import re
|
||||
take_profit, stop_loss = None, None
|
||||
if take_profit_str:
|
||||
try:
|
||||
numbers = re.findall(r'\d+\.?\d*', str(take_profit_str))
|
||||
if numbers:
|
||||
take_profit = float(numbers[0])
|
||||
except:
|
||||
pass
|
||||
|
||||
if stop_loss_str:
|
||||
try:
|
||||
numbers = re.findall(r'\d+\.?\d*', str(stop_loss_str))
|
||||
if numbers:
|
||||
stop_loss = float(numbers[0])
|
||||
except:
|
||||
pass
|
||||
|
||||
# 只有当所有必需字段都有效时才添加
|
||||
if entry_min and entry_max and take_profit and stop_loss:
|
||||
monitors_to_sync.append({
|
||||
"code": code,
|
||||
"name": stock_info.get("name", stock.get("name", "")),
|
||||
"rating": rating,
|
||||
"entry_min": entry_min,
|
||||
"entry_max": entry_max,
|
||||
"take_profit": take_profit,
|
||||
"stop_loss": stop_loss
|
||||
})
|
||||
|
||||
if monitors_to_sync:
|
||||
sync_result = monitor_db.batch_add_or_update_monitors(monitors_to_sync)
|
||||
st.info(f"📊 监测同步: 新增 {sync_result.get('added', 0)} 只, 更新 {sync_result.get('updated', 0)} 只")
|
||||
else:
|
||||
sync_result = {"added": 0, "updated": 0, "failed": 0, "total": 0}
|
||||
st.info("📊 无需同步监测列表(无启用自动监测的股票)")
|
||||
|
||||
# 发送通知
|
||||
if send_notification:
|
||||
from notification_service import notification_service
|
||||
notification_service.send_portfolio_analysis_notification(
|
||||
result,
|
||||
sync_result if auto_sync else None
|
||||
)
|
||||
st.info("✉️ 已发送完成通知")
|
||||
|
||||
# 显示详细结果
|
||||
st.markdown("### 分析结果详情")
|
||||
for item in result.get("results", []):
|
||||
display_analysis_result_card(item)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"批量分析失败: {str(e)}")
|
||||
import traceback
|
||||
st.code(traceback.format_exc())
|
||||
|
||||
|
||||
def display_analysis_result_card(item: Dict):
|
||||
"""显示单个分析结果卡片"""
|
||||
|
||||
code = item.get("code", "")
|
||||
result = item.get("result", {})
|
||||
|
||||
# 检查分析是否成功
|
||||
if result.get("success"):
|
||||
final_decision = result.get("final_decision", {})
|
||||
stock_info = result.get("stock_info", {})
|
||||
|
||||
# 使用正确的字段名
|
||||
rating = final_decision.get("rating", "未知")
|
||||
confidence = final_decision.get("confidence_level", "N/A")
|
||||
target_price = final_decision.get("target_price", "N/A")
|
||||
entry_range = final_decision.get("entry_range", "N/A")
|
||||
take_profit = final_decision.get("take_profit", "N/A")
|
||||
stop_loss = final_decision.get("stop_loss", "N/A")
|
||||
|
||||
# 评级颜色
|
||||
if "强烈买入" in rating or "买入" in rating:
|
||||
rating_color = "🟢"
|
||||
elif "卖出" in rating:
|
||||
rating_color = "🔴"
|
||||
else:
|
||||
rating_color = "🟡"
|
||||
|
||||
with st.expander(f"{rating_color} {code} {stock_info.get('name', '')} - {rating} (信心度: {confidence})"):
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.markdown("**进出场位置**")
|
||||
st.write(f"进场区间: {entry_range}")
|
||||
st.write(f"目标价: {target_price}")
|
||||
|
||||
with col2:
|
||||
st.markdown("**风控位置**")
|
||||
st.write(f"止盈位: {take_profit}")
|
||||
st.write(f"止损位: {stop_loss}")
|
||||
|
||||
# 投资建议
|
||||
advice = final_decision.get("advice", "")
|
||||
if advice:
|
||||
st.markdown("**投资建议**")
|
||||
st.info(advice)
|
||||
|
||||
else:
|
||||
# 分析失败
|
||||
error = result.get("error", "未知错误")
|
||||
with st.expander(f"🔴 {code} - 分析失败"):
|
||||
st.error(f"错误: {error}")
|
||||
|
||||
|
||||
def display_scheduler_management():
|
||||
"""显示定时任务管理"""
|
||||
|
||||
st.markdown("### ⏰ 定时任务管理")
|
||||
|
||||
# 调度器状态
|
||||
is_running = portfolio_scheduler.is_running()
|
||||
schedule_times = portfolio_scheduler.get_schedule_times()
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
if is_running:
|
||||
st.success("🟢 调度器运行中")
|
||||
else:
|
||||
st.error("🔴 调度器已停止")
|
||||
|
||||
with col2:
|
||||
st.info(f"⏰ 定时数量: {len(schedule_times)}个")
|
||||
|
||||
with col3:
|
||||
next_run = portfolio_scheduler.get_next_run_time()
|
||||
if next_run:
|
||||
st.info(f"⏭️ 下次运行: {next_run}")
|
||||
else:
|
||||
st.info("⏭️ 下次运行: 未设置")
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# 显示所有定时时间点
|
||||
st.markdown("#### 📋 已配置的定时时间")
|
||||
|
||||
if schedule_times:
|
||||
cols_per_row = 4
|
||||
for i in range(0, len(schedule_times), cols_per_row):
|
||||
cols = st.columns(cols_per_row)
|
||||
for j, col in enumerate(cols):
|
||||
idx = i + j
|
||||
if idx < len(schedule_times):
|
||||
time_str = schedule_times[idx]
|
||||
with col:
|
||||
col_time, col_del = st.columns([3, 1])
|
||||
with col_time:
|
||||
st.info(f"⏰ {time_str}")
|
||||
with col_del:
|
||||
if st.button("🗑️", key=f"del_time_{idx}", help="删除"):
|
||||
if len(schedule_times) > 1:
|
||||
portfolio_scheduler.remove_schedule_time(time_str)
|
||||
st.success(f"已删除 {time_str}")
|
||||
time.sleep(0.3)
|
||||
st.rerun()
|
||||
else:
|
||||
st.error("至少保留一个定时时间")
|
||||
else:
|
||||
st.warning("暂无定时配置")
|
||||
|
||||
# 添加新的定时时间
|
||||
with st.expander("➕ 添加定时时间", expanded=False):
|
||||
col_input, col_add = st.columns([3, 1])
|
||||
with col_input:
|
||||
new_time = st.time_input(
|
||||
"选择时间",
|
||||
value=datetime.strptime("15:05", "%H:%M").time(),
|
||||
help="添加新的每日分析时间"
|
||||
)
|
||||
with col_add:
|
||||
st.write("") # 占位,对齐按钮
|
||||
st.write("")
|
||||
if st.button("➕ 添加", type="primary", use_container_width=True):
|
||||
time_str = new_time.strftime("%H:%M")
|
||||
if portfolio_scheduler.add_schedule_time(time_str):
|
||||
st.success(f"已添加 {time_str}")
|
||||
time.sleep(0.3)
|
||||
st.rerun()
|
||||
else:
|
||||
st.warning(f"{time_str} 已存在")
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# 任务配置
|
||||
with st.form(key="scheduler_config_form"):
|
||||
st.markdown("#### 分析配置")
|
||||
|
||||
col_a, col_b = st.columns(2)
|
||||
|
||||
with col_a:
|
||||
analysis_mode = st.selectbox(
|
||||
"分析模式",
|
||||
options=["sequential", "parallel"],
|
||||
format_func=lambda x: "顺序分析" if x == "sequential" else "并行分析",
|
||||
index=0 if portfolio_scheduler.analysis_mode == "sequential" else 1
|
||||
)
|
||||
|
||||
with col_b:
|
||||
max_workers = st.number_input(
|
||||
"并行线程数",
|
||||
min_value=2,
|
||||
max_value=10,
|
||||
value=portfolio_scheduler.max_workers,
|
||||
disabled=(analysis_mode == "sequential"),
|
||||
help="仅在并行模式下生效"
|
||||
)
|
||||
|
||||
auto_sync_monitor = st.checkbox(
|
||||
"自动同步到监测",
|
||||
value=portfolio_scheduler.auto_monitor_sync,
|
||||
help="分析完成后自动将结果同步到实时监测列表"
|
||||
)
|
||||
send_notification = st.checkbox(
|
||||
"发送完成通知",
|
||||
value=portfolio_scheduler.notification_enabled,
|
||||
help="通过邮件或Webhook发送分析结果"
|
||||
)
|
||||
|
||||
col_update, col_reset = st.columns(2)
|
||||
|
||||
with col_update:
|
||||
if st.form_submit_button("💾 更新配置", type="primary"):
|
||||
portfolio_scheduler.update_config(
|
||||
analysis_mode=analysis_mode,
|
||||
max_workers=max_workers if analysis_mode == "parallel" else 1,
|
||||
auto_sync_monitor=auto_sync_monitor,
|
||||
send_notification=send_notification
|
||||
)
|
||||
st.success("配置已更新!")
|
||||
time.sleep(0.5)
|
||||
st.rerun()
|
||||
|
||||
with col_reset:
|
||||
if st.form_submit_button("🔄 恢复默认"):
|
||||
portfolio_scheduler.set_schedule_times(["09:30"])
|
||||
portfolio_scheduler.update_config(
|
||||
analysis_mode="sequential",
|
||||
max_workers=1,
|
||||
auto_sync_monitor=True,
|
||||
send_notification=True
|
||||
)
|
||||
st.success("已恢复默认配置!")
|
||||
time.sleep(0.5)
|
||||
st.rerun()
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# 控制按钮
|
||||
col_btn1, col_btn2, col_btn3 = st.columns(3)
|
||||
|
||||
with col_btn1:
|
||||
if is_running:
|
||||
if st.button("⏹️ 停止调度器", type="secondary", use_container_width=True):
|
||||
portfolio_scheduler.stop_scheduler()
|
||||
st.success("调度器已停止")
|
||||
time.sleep(0.5)
|
||||
st.rerun()
|
||||
else:
|
||||
if st.button("▶️ 启动调度器", type="primary", use_container_width=True):
|
||||
portfolio_scheduler.start_scheduler()
|
||||
st.success("调度器已启动")
|
||||
time.sleep(0.5)
|
||||
st.rerun()
|
||||
|
||||
with col_btn2:
|
||||
if st.button("🚀 立即执行一次", type="primary", use_container_width=True):
|
||||
with st.spinner("正在执行持仓分析..."):
|
||||
try:
|
||||
portfolio_scheduler.run_analysis_now()
|
||||
st.success("执行完成!请查看分析历史。")
|
||||
except Exception as e:
|
||||
st.error(f"执行失败: {str(e)}")
|
||||
|
||||
with col_btn3:
|
||||
if st.button("🔄 刷新状态", use_container_width=True):
|
||||
st.rerun()
|
||||
|
||||
|
||||
def display_analysis_history():
|
||||
"""显示分析历史"""
|
||||
|
||||
st.markdown("### 📈 分析历史记录")
|
||||
|
||||
stocks = portfolio_manager.get_all_stocks()
|
||||
|
||||
if not stocks:
|
||||
st.info("暂无持仓股票")
|
||||
return
|
||||
|
||||
# 选择股票
|
||||
stock_codes = [s["code"] for s in stocks]
|
||||
selected_code = st.selectbox(
|
||||
"选择股票",
|
||||
options=["全部"] + stock_codes,
|
||||
help="查看特定股票的分析历史"
|
||||
)
|
||||
|
||||
# 获取历史记录
|
||||
if selected_code == "全部":
|
||||
# 获取所有股票的最新历史
|
||||
all_history = []
|
||||
for stock in stocks:
|
||||
stock_id = stock["id"]
|
||||
history = portfolio_manager.db.get_latest_analysis_history(stock_id, limit=5)
|
||||
for h in history:
|
||||
h["code"] = stock["code"]
|
||||
h["name"] = stock["name"]
|
||||
all_history.extend(history)
|
||||
|
||||
# 按时间排序
|
||||
all_history.sort(key=lambda x: x.get("analysis_time", ""), reverse=True)
|
||||
history_list = all_history[:20] # 只显示最近20条
|
||||
else:
|
||||
# 获取指定股票的历史
|
||||
stock = next((s for s in stocks if s["code"] == selected_code), None)
|
||||
if stock:
|
||||
history_list = portfolio_manager.db.get_latest_analysis_history(
|
||||
stock["id"], limit=20
|
||||
)
|
||||
for h in history_list:
|
||||
h["code"] = stock["code"]
|
||||
h["name"] = stock["name"]
|
||||
else:
|
||||
history_list = []
|
||||
|
||||
if not history_list:
|
||||
st.info(f"暂无分析历史记录")
|
||||
return
|
||||
|
||||
# 显示历史记录
|
||||
st.markdown(f"共 {len(history_list)} 条记录")
|
||||
|
||||
for record in history_list:
|
||||
display_history_record(record)
|
||||
|
||||
|
||||
def display_history_record(record: Dict):
|
||||
"""显示单条历史记录"""
|
||||
|
||||
code = record.get("code", "")
|
||||
name = record.get("name", "")
|
||||
analysis_time = record.get("analysis_time", "")
|
||||
rating = record.get("rating", "未知")
|
||||
confidence = record.get("confidence", 0)
|
||||
current_price = record.get("current_price")
|
||||
target_price = record.get("target_price")
|
||||
entry_min = record.get("entry_min")
|
||||
entry_max = record.get("entry_max")
|
||||
take_profit = record.get("take_profit")
|
||||
stop_loss = record.get("stop_loss")
|
||||
summary = record.get("summary", "")
|
||||
|
||||
# 评级颜色
|
||||
if "强烈买入" in rating or "买入" in rating:
|
||||
rating_icon = "🟢"
|
||||
elif "卖出" in rating:
|
||||
rating_icon = "🔴"
|
||||
else:
|
||||
rating_icon = "🟡"
|
||||
|
||||
with st.expander(
|
||||
f"{rating_icon} {code} {name} - {rating} | {analysis_time}",
|
||||
expanded=False
|
||||
):
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
st.markdown("**价格信息**")
|
||||
if current_price:
|
||||
st.write(f"当时价格: ¥{current_price:.2f}")
|
||||
if target_price:
|
||||
st.write(f"目标价: ¥{target_price:.2f}")
|
||||
|
||||
with col2:
|
||||
st.markdown("**进场区间**")
|
||||
if entry_min and entry_max:
|
||||
st.write(f"¥{entry_min:.2f} ~ ¥{entry_max:.2f}")
|
||||
|
||||
with col3:
|
||||
st.markdown("**风控位置**")
|
||||
if take_profit:
|
||||
st.write(f"止盈: ¥{take_profit:.2f}")
|
||||
if stop_loss:
|
||||
st.write(f"止损: ¥{stop_loss:.2f}")
|
||||
|
||||
if summary:
|
||||
st.markdown("**分析摘要**")
|
||||
st.info(summary)
|
||||
|
||||
st.caption(f"置信度: {confidence}%")
|
||||
|
||||
Reference in New Issue
Block a user