Files
aiagents-stock/low_price_bull_monitor_ui.py
T
2025-12-12 19:46:38 +08:00

294 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
低价擒牛策略监控UI模块
"""
import streamlit as st
import pandas as pd
from datetime import datetime
from low_price_bull_monitor import low_price_bull_monitor
from low_price_bull_service import low_price_bull_service
def display_monitor_panel():
"""显示策略监控面板"""
st.markdown("## 📊 策略监控中心")
st.markdown("---")
# 服务状态
status = low_price_bull_service.get_status()
col1, col2, col3, col4 = st.columns(4)
with col1:
service_status = "🟢 运行中" if status['running'] else "🔴 已停止"
st.metric("服务状态", service_status)
with col2:
st.metric("监控股票", f"{status['monitored_count']} 只")
with col3:
st.metric("待处理提醒", f"{status['pending_alerts']} 条")
with col4:
st.metric("扫描间隔", f"{status['scan_interval']} 秒")
st.markdown("---")
# 服务控制
col_start, col_stop, col_config = st.columns(3)
with col_start:
if st.button("▶️ 启动监控服务", type="primary", disabled=status['running']):
if low_price_bull_service.start():
st.success("✅ 监控服务已启动")
st.rerun()
else:
st.error("❌ 启动失败")
with col_stop:
if st.button("⏸️ 停止监控服务", type="secondary", disabled=not status['running']):
if low_price_bull_service.stop():
st.success("✅ 监控服务已停止")
st.rerun()
else:
st.error("❌ 停止失败")
with col_config:
with st.popover("⚙️ 监控配置"):
st.markdown("**扫描间隔**")
new_interval = st.number_input(
"扫描间隔(秒)",
min_value=10,
max_value=600,
value=status['scan_interval'],
step=10,
label_visibility="collapsed"
)
if st.button("保存配置"):
low_price_bull_service.scan_interval = new_interval
st.success("✅ 配置已保存")
st.markdown("---")
# 标签页
tab1, tab2, tab3 = st.tabs(["📋 监控列表", "🔔 卖出提醒", "📜 历史记录"])
with tab1:
display_monitored_stocks()
with tab2:
display_sell_alerts()
with tab3:
display_alert_history()
def display_monitored_stocks():
"""显示监控中的股票列表"""
stocks = low_price_bull_monitor.get_monitored_stocks()
if not stocks:
st.info("暂无监控中的股票")
return
st.markdown(f"### 📋 监控列表(共{len(stocks)}只)")
# 转换为DataFrame
df = pd.DataFrame(stocks)
# 显示表格
display_df = df[['stock_code', 'stock_name', 'buy_price', 'buy_date', 'holding_days', 'add_time']].copy()
display_df.columns = ['股票代码', '股票名称', '买入价格', '买入日期', '持有天数', '加入时间']
st.dataframe(display_df, width='content', height=400)
# 批量移除
st.markdown("---")
st.markdown("### 🗑️ 批量管理")
selected_codes = st.multiselect(
"选择要移除的股票",
options=[f"{s['stock_code']} {s['stock_name']}" for s in stocks],
format_func=lambda x: x
)
if selected_codes and st.button("🗑️ 移除选中股票", type="secondary"):
for item in selected_codes:
code = item.split()[0]
success, msg = low_price_bull_monitor.remove_stock(code, "手动移除")
if success:
st.success(msg)
else:
st.error(msg)
st.rerun()
def display_sell_alerts():
"""显示待处理的卖出提醒"""
alerts = low_price_bull_monitor.get_pending_alerts()
if not alerts:
st.info("暂无待处理的卖出提醒")
return
st.markdown(f"### 🔔 卖出提醒(共{len(alerts)}条)")
for alert in alerts:
with st.expander(
f"🔔 {alert['stock_code']} {alert['stock_name']} - {alert['alert_reason']}",
expanded=True
):
col1, col2 = st.columns(2)
with col1:
st.markdown("#### 📊 提醒信息")
st.markdown(f"**股票代码**: {alert['stock_code']}")
st.markdown(f"**股票名称**: {alert['stock_name']}")
st.markdown(f"**提醒类型**: {_get_alert_type_name(alert['alert_type'])}")
st.markdown(f"**提醒原因**: {alert['alert_reason']}")
st.markdown(f"**提醒时间**: {alert['alert_time']}")
with col2:
st.markdown("#### 💰 市场数据")
# 处理当前价格(避免bytes类型错误)
current_price = alert.get('current_price')
if current_price is not None:
try:
price_val = float(current_price)
st.markdown(f"**当前价格**: {price_val:.2f}元")
except (ValueError, TypeError):
st.markdown(f"**当前价格**: {current_price}")
# 处理MA5和MA20(避免bytes类型错误)
ma5 = alert.get('ma5')
ma20 = alert.get('ma20')
if ma5 is not None and ma20 is not None:
try:
ma5_val = float(ma5)
ma20_val = float(ma20)
st.markdown(f"**MA5**: {ma5_val:.2f}")
st.markdown(f"**MA20**: {ma20_val:.2f}")
st.markdown(f"**均线差**: {(ma5_val - ma20_val):.2f}")
except (ValueError, TypeError):
st.markdown(f"**MA5**: {ma5}")
st.markdown(f"**MA20**: {ma20}")
# 处理持有天数
holding_days = alert.get('holding_days')
if holding_days is not None:
try:
days_val = int(holding_days)
st.markdown(f"**持有天数**: {days_val}天")
except (ValueError, TypeError):
st.markdown(f"**持有天数**: {holding_days}")
# 操作按钮
st.markdown("---")
col_action1, col_action2 = st.columns(2)
with col_action1:
if st.button(f"✅ 已处理 - {alert['stock_code']}", key=f"done_{alert['id']}"):
low_price_bull_monitor.mark_alert_sent(alert['id'])
low_price_bull_monitor.remove_stock(alert['stock_code'], "已处理提醒")
st.success(f"✅ 已标记为已处理并移除 {alert['stock_code']}")
st.rerun()
with col_action2:
if st.button(f"❌ 忽略 - {alert['stock_code']}", key=f"ignore_{alert['id']}"):
low_price_bull_monitor.mark_alert_sent(alert['id'])
st.success(f"✅ 已忽略提醒(股票保留在监控列表)")
st.rerun()
def display_alert_history():
"""显示历史提醒记录"""
alerts = low_price_bull_monitor.get_history_alerts(limit=50)
if not alerts:
st.info("暂无历史提醒记录")
return
st.markdown("### 📜 历史提醒记录")
# 转换为DataFrame
df = pd.DataFrame(alerts)
# 选择显示列
display_cols = ['stock_code', 'stock_name', 'alert_type', 'alert_reason', 'current_price', 'holding_days', 'alert_time', 'is_sent']
display_df = df[display_cols].copy()
# 重命名列
display_df.columns = ['股票代码', '股票名称', '提醒类型', '提醒原因', '当前价格', '持有天数', '提醒时间', '已发送']
# 格式化
display_df['提醒类型'] = display_df['提醒类型'].apply(_get_alert_type_name)
display_df['已发送'] = display_df['已发送'].apply(lambda x: '✅' if x == 1 else '❌')
st.dataframe(display_df, width='content', height=400)
# 清理按钮
st.markdown("---")
if st.button("🗑️ 清理30天前的记录"):
low_price_bull_monitor.clear_old_alerts(days=30)
st.success("✅ 已清理旧记录")
st.rerun()
def _get_alert_type_name(alert_type: str) -> str:
"""获取提醒类型名称"""
type_map = {
'holding_days': '持股到期',
'ma_cross': 'MA均线死叉'
}
return type_map.get(alert_type, alert_type)
def add_stock_to_monitor_button(stock_code: str, stock_name: str, price: float = None):
"""
添加股票到监控按钮(在股票详情中使用)
Args:
stock_code: 股票代码
stock_name: 股票名称
price: 买入价格
"""
# 检查是否已在监控
stocks = low_price_bull_monitor.get_monitored_stocks()
is_monitored = any(s['stock_code'] == stock_code for s in stocks)
if is_monitored:
st.info(f"✅ {stock_code} 已在监控列表中")
if st.button(f"🗑️ 移出监控 - {stock_code}", key=f"remove_{stock_code}"):
success, msg = low_price_bull_monitor.remove_stock(stock_code, "手动移除")
if success:
st.success(msg)
st.rerun()
else:
st.error(msg)
else:
if st.button(f" 加入策略监控 - {stock_code}", type="primary", key=f"add_{stock_code}"):
if price is None:
price = 0.0 # 如果没有价格,使用0
success, msg = low_price_bull_monitor.add_stock(
stock_code=stock_code,
stock_name=stock_name,
buy_price=price,
buy_date=datetime.now().strftime("%Y-%m-%d")
)
if success:
st.success(msg)
st.info("💡 提示:请在左侧菜单进入'策略监控'查看监控状态")
st.rerun()
else:
st.error(msg)