#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
股票监测管理模块
支持添加、删除、编辑监测股票
卡片式布局,支持关键位置监测
"""
import streamlit as st
import time
import uuid
from datetime import datetime, timedelta
from typing import Dict, List
import json
from monitor_db import monitor_db
from monitor_service import monitor_service
from notification_service import notification_service
from stock_data import StockDataFetcher
from miniqmt_interface import miniqmt, get_miniqmt_status, QuantStrategyConfig
def display_monitor_manager():
"""显示监测管理主页面"""
st.markdown("## 📊 股票监测管理")
st.markdown("---")
# 检查是否有跳转提示
if 'monitor_jump_highlight' in st.session_state:
symbol = st.session_state.monitor_jump_highlight
st.success(f"✅ {symbol} 已成功加入监测列表!您可以在下方查看。")
del st.session_state.monitor_jump_highlight
# 监测服务状态
display_monitor_status()
# 添加新股票监测
display_add_stock_section()
# 监测股票列表
display_monitored_stocks()
# 通知管理
display_notification_management()
def display_monitor_status():
"""显示监测服务状态"""
col1, col2, col3, col4, col5, col6 = st.columns(6)
with col1:
if monitor_service.running:
st.success("🟢 运行中")
else:
st.error("🔴 已停止")
with col2:
stocks = monitor_db.get_monitored_stocks()
st.metric("监测股票", len(stocks))
with col3:
notifications = monitor_db.get_pending_notifications()
st.metric("待处理通知", len(notifications))
with col4:
# 显示MiniQMT状态
qmt_status = get_miniqmt_status()
if qmt_status['ready']:
st.success("🤖 QMT在线")
else:
st.info("🤖 QMT离线")
with col5:
if monitor_service.running:
if st.button("⏹️ 停止监测", type="secondary"):
monitor_service.stop_monitoring()
st.success("✅ 监测服务已停止")
st.rerun()
else:
if st.button("▶️ 启动监测", type="primary"):
monitor_service.start_monitoring()
st.success("✅ 监测服务已启动")
st.rerun()
with col6:
if st.button("🔄 刷新状态"):
st.rerun()
# 显示定时调度状态和配置
display_scheduler_section()
def display_add_stock_section():
"""显示添加股票监测区域"""
st.markdown("### ➕ 添加股票监测")
with st.expander("点击展开添加股票监测", expanded=False):
col1, col2 = st.columns([1, 1])
with col1:
# 股票信息输入
st.subheader("📈 股票信息")
symbol = st.text_input("股票代码", placeholder="例如: AAPL, 000001", help="支持美股和A股代码")
name = st.text_input("股票名称", placeholder="例如: 苹果公司", help="可选,用于显示")
# 获取股票基本信息
if symbol:
if st.button("🔍 获取股票信息"):
with st.spinner("正在获取股票信息..."):
fetcher = StockDataFetcher()
stock_info = fetcher.get_stock_info(symbol)
if "error" not in stock_info:
st.success("✅ 股票信息获取成功")
st.session_state.temp_stock_info = stock_info
else:
st.error(f"❌ {stock_info['error']}")
with col2:
# 监测设置
st.subheader("⚙️ 监测设置")
# 关键位置设置
st.markdown("**🎯 关键位置设置**")
entry_min = st.number_input("进场区间最低价", value=0.0, step=0.01, format="%.2f")
entry_max = st.number_input("进场区间最高价", value=0.0, step=0.01, format="%.2f")
take_profit = st.number_input("止盈价位", value=0.0, step=0.01, format="%.2f", help="可选")
stop_loss = st.number_input("止损价位", value=0.0, step=0.01, format="%.2f", help="可选")
# 监测参数
st.markdown("**⏰ 监测参数**")
check_interval = st.slider("监测间隔(分钟)", 5, 120, 30)
notification_enabled = st.checkbox("启用通知", value=True)
# 投资评级
rating = st.selectbox("投资评级", ["买入", "持有", "卖出"], index=0)
# 量化交易设置
st.markdown("**🤖 量化交易(MiniQMT)**")
quant_enabled = st.checkbox("启用量化自动交易", value=False, help="需要先配置MiniQMT连接")
if quant_enabled:
max_position_pct = st.slider("最大仓位比例", 0.05, 0.5, 0.2, 0.05, help="单只股票最大占总资金的比例")
auto_stop_loss = st.checkbox("自动止损", value=True)
auto_take_profit = st.checkbox("自动止盈", value=True)
# 添加按钮
if st.button("✅ 添加监测", type="primary", use_container_width=True):
if symbol and entry_min > 0 and entry_max > 0 and entry_max > entry_min:
try:
# 准备数据
entry_range = {"min": entry_min, "max": entry_max}
# 准备量化配置
quant_config = None
if quant_enabled:
quant_config = {
'max_position_pct': max_position_pct,
'auto_stop_loss': auto_stop_loss,
'auto_take_profit': auto_take_profit,
'min_trade_amount': 5000
}
# 添加到数据库
stock_id = monitor_db.add_monitored_stock(
symbol=symbol,
name=name or symbol,
rating=rating,
entry_range=entry_range,
take_profit=take_profit if take_profit > 0 else None,
stop_loss=stop_loss if stop_loss > 0 else None,
check_interval=check_interval,
notification_enabled=notification_enabled,
quant_enabled=quant_enabled,
quant_config=quant_config
)
st.success(f"✅ 已成功添加 {symbol} 到监测列表")
st.balloons()
# 立即更新一次价格
monitor_service.manual_update_stock(stock_id)
# 清空表单
st.rerun()
except Exception as e:
st.error(f"❌ 添加失败: {str(e)}")
else:
st.error("❌ 请填写完整的股票信息和有效的进场区间")
def display_monitored_stocks():
"""显示监测股票列表 - 卡片式布局"""
st.markdown("### 📋 监测股票列表")
stocks = monitor_db.get_monitored_stocks()
if not stocks:
st.info("📭 暂无监测股票,请添加股票开始监测")
return
# 筛选和搜索
col1, col2, col3 = st.columns([2, 1, 1])
with col1:
search_term = st.text_input("🔍 搜索股票", placeholder="输入股票代码或名称")
with col2:
rating_filter = st.selectbox("评级筛选", ["全部", "买入", "持有", "卖出"])
with col3:
if st.button("🔄 刷新列表"):
st.rerun()
# 筛选股票
filtered_stocks = stocks
if search_term:
filtered_stocks = [s for s in stocks if search_term.lower() in s['symbol'].lower() or search_term.lower() in s['name'].lower()]
if rating_filter != "全部":
filtered_stocks = [s for s in filtered_stocks if s['rating'] == rating_filter]
if not filtered_stocks:
st.warning("🔍 未找到匹配的股票")
return
# 卡片式布局 - 每行显示2个卡片
for i in range(0, len(filtered_stocks), 2):
cols = st.columns(2)
for j, col in enumerate(cols):
if i + j < len(filtered_stocks):
stock = filtered_stocks[i + j]
with col:
display_stock_card(stock)
# 显示编辑对话框
if 'editing_stock_id' in st.session_state:
display_edit_dialog(st.session_state.editing_stock_id)
# 显示删除确认对话框
if 'deleting_stock_id' in st.session_state:
display_delete_confirm_dialog(st.session_state.deleting_stock_id)
def display_stock_card(stock: Dict):
"""显示单个股票监测卡片"""
with st.container():
# 卡片头部
st.markdown(f"""
""", unsafe_allow_html=True)
# 股票基本信息
col1, col2 = st.columns([2, 1])
with col1:
st.markdown(f"**{stock['symbol']}** - {stock['name']}")
# 评级显示
rating_color = {
'买入': '🟢',
'持有': '🟡',
'卖出': '🔴'
}
st.markdown(f"评级: {rating_color.get(stock['rating'], '⚪')} {stock['rating']}")
with col2:
if stock['current_price'] and stock['current_price'] != 'N/A':
st.metric("当前价格", f"¥{stock['current_price']}")
else:
st.metric("当前价格", "等待更新")
# 关键位置信息
st.markdown("**🎯 关键位置**")
entry_range = stock['entry_range']
col1, col2, col3 = st.columns(3)
with col1:
st.info(f"**进场区间**\n¥{entry_range['min']} - ¥{entry_range['max']}")
with col2:
if stock['take_profit']:
st.success(f"**止盈位**\n¥{stock['take_profit']}")
else:
st.info("**止盈位**\n未设置")
with col3:
if stock['stop_loss']:
st.error(f"**止损位**\n¥{stock['stop_loss']}")
else:
st.info("**止损位**\n未设置")
# 监测状态
st.markdown("**📊 监测状态**")
col1, col2, col3 = st.columns(3)
with col1:
st.caption(f"监测间隔: {stock['check_interval']}分钟")
with col2:
if stock['last_checked']:
last_checked = datetime.fromisoformat(stock['last_checked'])
st.caption(f"最后检查: {last_checked.strftime('%m-%d %H:%M')}")
else:
st.caption("最后检查: 从未检查")
with col3:
status = "🟢 启用" if stock['notification_enabled'] else "🔴 禁用"
st.caption(f"通知: {status}")
# 显示量化状态
if stock.get('quant_enabled', False):
st.caption("🤖 量化: 🟢 启用")
else:
st.caption("🤖 量化: 🔴 禁用")
# 操作按钮
st.markdown("**🔧 操作**")
col1, col2, col3, col4 = st.columns(4)
with col1:
if st.button("🔄 更新", key=f"update_{stock['id']}"):
if monitor_service.manual_update_stock(stock['id']):
st.success("✅ 更新成功")
else:
st.error("❌ 更新失败")
with col2:
if st.button("✏️ 编辑", key=f"edit_{stock['id']}"):
st.session_state.editing_stock_id = stock['id']
st.rerun()
with col3:
# 切换通知状态
current_status = stock['notification_enabled']
if current_status:
if st.button("🔕 禁用", key=f"notify_{stock['id']}"):
monitor_db.toggle_notification(stock['id'], False)
st.success("✅ 已禁用通知")
st.rerun()
else:
if st.button("🔔 启用", key=f"notify_{stock['id']}"):
monitor_db.toggle_notification(stock['id'], True)
st.success("✅ 已启用通知")
st.rerun()
with col4:
if st.button("🗑️ 删除", key=f"delete_{stock['id']}"):
st.session_state.deleting_stock_id = stock['id']
st.rerun()
st.markdown("
", unsafe_allow_html=True)
def display_edit_dialog(stock_id: int):
"""显示编辑股票对话框"""
stock = monitor_db.get_stock_by_id(stock_id)
if not stock:
st.error("❌ 股票不存在")
del st.session_state.editing_stock_id
return
st.markdown("---")
st.markdown(f"### ✏️ 编辑监测 - {stock['symbol']} {stock['name']}")
with st.form(key=f"edit_form_{stock_id}"):
col1, col2 = st.columns([1, 1])
with col1:
st.subheader("🎯 关键位置")
entry_range = stock['entry_range']
entry_min = st.number_input("进场区间最低价", value=float(entry_range['min']), step=0.01, format="%.2f")
entry_max = st.number_input("进场区间最高价", value=float(entry_range['max']), step=0.01, format="%.2f")
take_profit = st.number_input("止盈价位", value=float(stock['take_profit']) if stock['take_profit'] else 0.0, step=0.01, format="%.2f")
stop_loss = st.number_input("止损价位", value=float(stock['stop_loss']) if stock['stop_loss'] else 0.0, step=0.01, format="%.2f")
with col2:
st.subheader("⚙️ 监测设置")
check_interval = st.slider("监测间隔(分钟)", 5, 120, stock['check_interval'])
rating = st.selectbox("投资评级", ["买入", "持有", "卖出"],
index=["买入", "持有", "卖出"].index(stock['rating']) if stock['rating'] in ["买入", "持有", "卖出"] else 0)
notification_enabled = st.checkbox("启用通知", value=stock['notification_enabled'])
# 量化交易设置
st.markdown("**🤖 量化交易**")
quant_enabled = st.checkbox("启用量化自动交易", value=stock.get('quant_enabled', False))
if quant_enabled:
quant_config = stock.get('quant_config', {})
max_position_pct = st.slider("最大仓位比例", 0.05, 0.5,
quant_config.get('max_position_pct', 0.2), 0.05)
auto_stop_loss = st.checkbox("自动止损", value=quant_config.get('auto_stop_loss', True))
auto_take_profit = st.checkbox("自动止盈", value=quant_config.get('auto_take_profit', True))
col1, col2, col3 = st.columns(3)
with col1:
submit = st.form_submit_button("✅ 保存修改", type="primary", use_container_width=True)
with col2:
cancel = st.form_submit_button("❌ 取消", use_container_width=True)
if submit:
if entry_min > 0 and entry_max > 0 and entry_max > entry_min:
try:
# 更新数据库
new_entry_range = {"min": entry_min, "max": entry_max}
# 准备量化配置
new_quant_config = None
if quant_enabled:
new_quant_config = {
'max_position_pct': max_position_pct,
'auto_stop_loss': auto_stop_loss,
'auto_take_profit': auto_take_profit,
'min_trade_amount': 5000
}
monitor_db.update_monitored_stock(
stock_id=stock_id,
rating=rating,
entry_range=new_entry_range,
take_profit=take_profit if take_profit > 0 else None,
stop_loss=stop_loss if stop_loss > 0 else None,
check_interval=check_interval,
notification_enabled=notification_enabled,
quant_enabled=quant_enabled,
quant_config=new_quant_config
)
st.success("✅ 修改已保存")
del st.session_state.editing_stock_id
st.rerun()
except Exception as e:
st.error(f"❌ 保存失败: {str(e)}")
else:
st.error("❌ 请输入有效的进场区间")
if cancel:
del st.session_state.editing_stock_id
st.rerun()
def display_delete_confirm_dialog(stock_id: int):
"""显示删除确认对话框"""
stock = monitor_db.get_stock_by_id(stock_id)
if not stock:
st.error("❌ 股票不存在或已被删除")
if 'deleting_stock_id' in st.session_state:
del st.session_state.deleting_stock_id
st.rerun()
return
st.markdown("---")
st.markdown(f"### ⚠️ 确认删除")
st.warning(f"""
您确定要删除以下监测吗?
**股票代码**: {stock['symbol']}
**股票名称**: {stock['name']}
**投资评级**: {stock['rating']}
此操作不可撤销!
""")
col1, col2, col3 = st.columns([1, 1, 1])
with col1:
if st.button("🗑️ 确认删除", type="primary", use_container_width=True, key=f"confirm_delete_{stock_id}"):
try:
result = monitor_db.remove_monitored_stock(stock_id)
if result:
# 清理session state
if 'deleting_stock_id' in st.session_state:
del st.session_state.deleting_stock_id
st.success("✅ 已成功删除监测")
st.balloons()
time.sleep(0.8) # 短暂延迟,让用户看到成功消息
st.rerun()
else:
st.error("❌ 删除失败:股票不存在或已被删除")
time.sleep(1)
if 'deleting_stock_id' in st.session_state:
del st.session_state.deleting_stock_id
st.rerun()
except Exception as e:
st.error(f"❌ 删除失败:{str(e)}")
time.sleep(1)
if 'deleting_stock_id' in st.session_state:
del st.session_state.deleting_stock_id
st.rerun()
with col2:
if st.button("❌ 取消", use_container_width=True, key=f"cancel_delete_{stock_id}"):
del st.session_state.deleting_stock_id
st.rerun()
def display_notification_management():
"""显示通知管理"""
st.markdown("### 🔔 通知管理")
# 显示MiniQMT量化交易状态
display_miniqmt_status()
st.markdown("---")
# 通知设置
col1, col2 = st.columns([1, 1])
with col1:
st.subheader("📧 邮件通知设置")
# 获取当前邮件配置状态
email_config = notification_service.get_email_config_status()
# 显示配置状态
if email_config['configured']:
st.success("✅ 邮件配置已完成")
else:
st.warning("⚠️ 邮件未配置或配置不完整")
# 显示配置信息
st.info(f"""
**当前配置:**
- SMTP服务器: {email_config['smtp_server']}
- SMTP端口: {email_config['smtp_port']}
- 发送邮箱: {email_config['email_from']}
- 接收邮箱: {email_config['email_to']}
- 启用状态: {'是' if email_config['enabled'] else '否'}
""")
st.markdown("---")
st.markdown("**⚙️ 配置说明**")
st.caption("""
在 `.env` 文件中配置以下参数:
```
EMAIL_ENABLED=true
SMTP_SERVER=smtp.qq.com
SMTP_PORT=587
EMAIL_FROM=your_email@qq.com
EMAIL_PASSWORD=your_authorization_code
EMAIL_TO=receiver@example.com
```
💡 提示:
- 端口:587 (TLS) 或 465 (SSL)
- 密码:使用邮箱授权码,不是登录密码
- QQ邮箱授权码获取:设置 → 账户 → POP3/IMAP/SMTP → 生成授权码
""")
# 测试邮件按钮
if email_config['configured']:
if st.button("📧 发送测试邮件", type="primary", use_container_width=True):
with st.spinner("正在发送测试邮件..."):
success, message = notification_service.send_test_email()
if success:
st.success(f"✅ {message}")
st.balloons()
else:
st.error(f"❌ {message}")
else:
st.button("📧 发送测试邮件", type="primary", use_container_width=True, disabled=True)
st.caption("请先在.env文件中配置邮件参数")
with col2:
st.subheader("📱 通知历史")
# 显示所有通知(包括已发送和未发送的)
all_notifications = monitor_db.get_all_recent_notifications(limit=10)
if all_notifications:
# 显示通知列表
for notification in all_notifications:
notification_type = notification['type']
color_map = {
'entry': '🟢',
'take_profit': '🟡',
'stop_loss': '🔴',
'quant_trade': '🤖'
}
icon = color_map.get(notification_type, '🔵')
# 显示已发送状态
sent_status = "✅ 已发送" if notification.get('sent') else "⏳ 待发送"
# 显示通知信息
st.info(f"{icon} **{notification['symbol']}** - {notification['message']}\n\n_{notification['triggered_at']}_ | {sent_status}")
# 显示待发送通知数量
pending_count = len([n for n in all_notifications if not n.get('sent')])
if pending_count > 0:
st.warning(f"⚠️ 有 {pending_count} 条待发送通知")
# 清空通知按钮
col_a, col_b = st.columns(2)
with col_a:
if st.button("✅ 标记已读"):
monitor_db.mark_all_notifications_sent()
st.success("✅ 所有通知已标记为已读")
st.rerun()
with col_b:
if st.button("🗑️ 清空通知"):
monitor_db.clear_all_notifications()
st.success("✅ 通知已清空")
st.rerun()
else:
st.info("📭 暂无通知")
def display_miniqmt_status():
"""显示MiniQMT量化交易状态"""
st.markdown("### 🤖 MiniQMT量化交易")
qmt_status = get_miniqmt_status()
col1, col2 = st.columns([1, 1])
with col1:
st.subheader("📊 连接状态")
if qmt_status['enabled']:
st.success("✅ MiniQMT已启用")
else:
st.warning("⚠️ MiniQMT未启用")
if qmt_status['connected']:
st.success("✅ 已连接到MiniQMT")
else:
st.info("⏸️ 未连接到MiniQMT")
if qmt_status['account_id']:
st.info(f"**账户ID**: {qmt_status['account_id']}")
else:
st.caption("未配置账户ID")
st.markdown("---")
st.markdown("**⚙️ 配置说明**")
st.caption("""
在 `config.py` 中配置以下参数:
```python
MINIQMT_CONFIG = {
'enabled': True,
'account_id': 'your_account_id'
}
```
💡 提示:
- 需要安装并启动MiniQMT客户端
- 确保账户已登录
- 预留接口已实现,可对接真实交易
""")
with col2:
st.subheader("📈 量化统计")
# 统计启用量化的股票
stocks = monitor_db.get_monitored_stocks()
quant_stocks = [s for s in stocks if s.get('quant_enabled', False)]
st.metric("启用量化的股票", f"{len(quant_stocks)}/{len(stocks)}")
if quant_stocks:
st.markdown("**量化监测列表:**")
for stock in quant_stocks:
st.caption(f"🤖 {stock['symbol']} - {stock['name']}")
else:
st.info("暂无启用量化交易的股票")
st.markdown("---")
# 连接按钮
if qmt_status['enabled'] and not qmt_status['connected']:
if st.button("🔗 连接MiniQMT", type="primary", use_container_width=True):
success, msg = miniqmt.connect()
if success:
st.success(f"✅ {msg}")
else:
st.error(f"❌ {msg}")
st.rerun()
elif qmt_status['connected']:
if st.button("🔌 断开连接", use_container_width=True):
if miniqmt.disconnect():
st.info("⏸️ 已断开MiniQMT连接")
st.rerun()
def display_scheduler_section():
"""显示定时调度配置区域"""
st.markdown("---")
st.markdown("### ⏰ 定时自动启动/关闭")
# 获取调度器实例
scheduler = monitor_service.get_scheduler()
status = scheduler.get_status()
# 状态显示
col1, col2, col3, col4 = st.columns(4)
with col1:
if status['scheduler_enabled']:
st.success("🟢 定时已启用")
else:
st.info("⚪ 定时未启用")
with col2:
if status['scheduler_running']:
st.success("🔄 调度器运行中")
else:
st.info("⏸️ 调度器未运行")
with col3:
if status['is_trading_day']:
st.success(f"📅 交易日")
else:
st.info("📅 非交易日")
with col4:
if status['is_trading_time']:
st.success("⏰ 交易时间内")
else:
st.info(f"⏰ {status['next_trading_time']}")
# 配置设置
with st.expander("⚙️ 定时调度设置", expanded=False):
col1, col2 = st.columns([1, 1])
with col1:
st.subheader("📊 市场选择")
market = st.selectbox(
"选择市场",
["CN", "US", "HK"],
index=["CN", "US", "HK"].index(scheduler.config.get('market', 'CN')),
help="CN=中国A股, US=美股, HK=港股"
)
market_names = {
"CN": "中国A股",
"US": "美股",
"HK": "港股"
}
st.info(f"**当前市场**: {market_names.get(market, market)}")
# 显示交易时间
trading_hours = scheduler.config['trading_hours'].get(market, [])
st.markdown("**📅 交易时间:**")
for i, period in enumerate(trading_hours, 1):
st.caption(f"时段{i}: {period['start']} - {period['end']}")
# 交易日设置
st.markdown("**📅 交易日设置**")
trading_days = st.multiselect(
"选择交易日",
options=[1, 2, 3, 4, 5, 6, 7],
default=scheduler.config.get('trading_days', [1, 2, 3, 4, 5]),
format_func=lambda x: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][x-1],
help="选择哪些日期为交易日"
)
with col2:
st.subheader("⚙️ 调度参数")
enabled = st.checkbox(
"启用定时调度",
value=scheduler.config.get('enabled', False),
help="启用后将在交易时间自动启动监测服务"
)
auto_stop = st.checkbox(
"收盘后自动停止",
value=scheduler.config.get('auto_stop', True),
help="在交易时间结束后自动停止监测服务"
)
pre_market_minutes = st.slider(
"提前启动(分钟)",
min_value=0,
max_value=30,
value=scheduler.config.get('pre_market_minutes', 5),
help="在开盘前提前多少分钟启动"
)
post_market_minutes = st.slider(
"延后停止(分钟)",
min_value=0,
max_value=30,
value=scheduler.config.get('post_market_minutes', 5),
help="在收盘后延后多少分钟停止"
)
st.markdown("---")
# 说明信息
st.info("""
**💡 使用说明:**
- 启用定时调度后,系统将在交易时间自动启动监测
- 非交易时间或非交易日将自动停止监测(如启用自动停止)
- 调度器独立运行,不影响手动启动/停止
- 支持中国A股、美股、港股交易时间
""")
# 保存按钮
col1, col2, col3 = st.columns([1, 1, 1])
with col1:
if st.button("💾 保存设置", type="primary", use_container_width=True):
try:
# 更新配置
scheduler.update_config(
enabled=enabled,
market=market,
trading_days=trading_days,
auto_stop=auto_stop,
pre_market_minutes=pre_market_minutes,
post_market_minutes=post_market_minutes
)
st.success("✅ 设置已保存")
st.balloons()
time.sleep(1)
st.rerun()
except Exception as e:
st.error(f"❌ 保存失败: {e}")
with col2:
if status['scheduler_running']:
if st.button("⏹️ 停止调度器", use_container_width=True):
scheduler.stop_scheduler()
st.info("⏸️ 调度器已停止")
time.sleep(0.5)
st.rerun()
else:
if enabled:
if st.button("▶️ 启动调度器", type="secondary", use_container_width=True):
scheduler.start_scheduler()
st.success("✅ 调度器已启动")
time.sleep(0.5)
st.rerun()
else:
st.button("▶️ 启动调度器", use_container_width=True, disabled=True)
st.caption("请先启用定时调度")
with col3:
if st.button("🔄 刷新状态", use_container_width=True):
st.rerun()
def get_monitor_summary():
"""获取监测摘要信息"""
stocks = monitor_db.get_monitored_stocks()
summary = {
'total_stocks': len(stocks),
'stocks_needing_update': len(monitor_service.get_stocks_needing_update()),
'pending_notifications': len(monitor_db.get_pending_notifications()),
'active_monitoring': monitor_service.running
}
return summary