修复盯盘bug

This commit is contained in:
oficcejo
2025-10-09 11:47:54 +08:00
parent db935809bd
commit ad21672786
7 changed files with 840 additions and 23 deletions
+59
View File
@@ -152,6 +152,36 @@ class StockMonitorDatabase:
conn.commit()
conn.close()
def update_last_checked(self, stock_id: int):
"""仅更新最后检查时间(用于获取失败的情况)"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
UPDATE monitored_stocks
SET last_checked = CURRENT_TIMESTAMP
WHERE id = ?
''', (stock_id,))
conn.commit()
conn.close()
def has_recent_notification(self, stock_id: int, notification_type: str, minutes: int = 60) -> bool:
"""检查是否在最近X分钟内已有相同类型的通知"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
SELECT COUNT(*) FROM notifications
WHERE stock_id = ? AND type = ?
AND datetime(triggered_at) > datetime('now', '-' || ? || ' minutes')
''', (stock_id, notification_type, minutes))
count = cursor.fetchone()[0]
conn.close()
return count > 0
def add_notification(self, stock_id: int, notification_type: str, message: str):
"""添加提醒记录"""
conn = sqlite3.connect(self.db_path)
@@ -193,6 +223,35 @@ class StockMonitorDatabase:
conn.close()
return notifications
def get_all_recent_notifications(self, limit: int = 10) -> List[Dict]:
"""获取最近的所有通知(包括已发送和未发送的)"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
SELECT n.id, n.stock_id, s.symbol, s.name, n.type, n.message, n.triggered_at, n.sent
FROM notifications n
JOIN monitored_stocks s ON n.stock_id = s.id
ORDER BY n.triggered_at DESC
LIMIT ?
''', (limit,))
notifications = []
for row in cursor.fetchall():
notifications.append({
'id': row[0],
'stock_id': row[1],
'symbol': row[2],
'name': row[3],
'type': row[4],
'message': row[5],
'triggered_at': row[6],
'sent': bool(row[7])
})
conn.close()
return notifications
def mark_notification_sent(self, notification_id: int):
"""标记提醒已发送"""
conn = sqlite3.connect(self.db_path)
+15 -5
View File
@@ -577,21 +577,31 @@ def display_notification_management():
with col2:
st.subheader("📱 通知历史")
notifications = monitor_db.get_pending_notifications()
# 显示所有通知(包括已发送和未发送的)
all_notifications = monitor_db.get_all_recent_notifications(limit=10)
if notifications:
if all_notifications:
# 显示通知列表
for notification in notifications[-10:]: # 显示最近10条
for notification in all_notifications:
notification_type = notification['type']
color_map = {
'entry': '🟢',
'take_profit': '🟡',
'stop_loss': '🔴'
'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']}_")
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)
+59 -15
View File
@@ -8,6 +8,7 @@ import streamlit as st
from monitor_db import monitor_db
from stock_data import StockDataFetcher
from miniqmt_interface import miniqmt, get_miniqmt_status
from notification_service import notification_service
class StockMonitorService:
"""股票监测服务"""
@@ -36,19 +37,22 @@ class StockMonitorService:
def _monitor_loop(self):
"""监测循环"""
print("监测服务已启动")
while self.running:
try:
self._check_all_stocks()
time.sleep(60) # 每分钟检查一次
# 根据最小监测间隔决定循环间隔,最少5分钟检查一次
time.sleep(300) # 每5分钟检查一次
except Exception as e:
print(f"监测服务错误: {e}")
time.sleep(10)
time.sleep(60) # 错误后等待1分钟再重试
def _check_all_stocks(self):
"""检查所有监测股票"""
stocks = monitor_db.get_monitored_stocks()
current_time = datetime.now()
updated_count = 0
for stock in stocks:
# 检查是否需要更新价格
last_checked = stock.get('last_checked')
@@ -58,12 +62,25 @@ class StockMonitorService:
last_checked_dt = datetime.fromisoformat(last_checked)
next_check = last_checked_dt + timedelta(minutes=check_interval)
if current_time < next_check:
# 显示距离下次检查的时间
time_left = (next_check - current_time).total_seconds() / 60
print(f"股票 {stock['symbol']} 距离下次检查还有 {time_left:.1f} 分钟")
continue
try:
print(f"正在更新股票 {stock['symbol']} 的价格...")
self._update_stock_price(stock)
updated_count += 1
# 在每个股票请求之间增加延迟,避免API限流
if updated_count < len(stocks):
time.sleep(3) # 每个股票之间等待3秒
except Exception as e:
print(f"更新股票 {stock['symbol']} 价格失败: {e}")
print(f"更新股票 {stock['symbol']} 价格失败: {e}")
time.sleep(3) # 失败后也等待3秒再继续
if updated_count > 0:
print(f"✅ 本轮共更新了 {updated_count} 只股票")
def _update_stock_price(self, stock: Dict):
"""更新股票价格并检查条件"""
@@ -78,18 +95,28 @@ class StockMonitorService:
if current_price and current_price != 'N/A':
try:
current_price = float(current_price)
# 更新数据库
# 更新数据库(包括更新last_checked时间)
monitor_db.update_stock_price(stock['id'], current_price)
print(f"{symbol} 当前价格: ¥{current_price}")
# 检查触发条件
self._check_trigger_conditions(stock, current_price)
except (ValueError, TypeError):
print(f"股票 {symbol} 价格格式错误: {current_price}")
except (ValueError, TypeError) as e:
print(f"股票 {symbol} 价格格式错误: {current_price}")
# 即使失败也更新last_checked,避免持续重试
monitor_db.update_last_checked(stock['id'])
else:
print(f"无法获取股票 {symbol} 的当前价格")
print(f"⚠️ 无法获取股票 {symbol} 的当前价格")
# 更新last_checked,避免持续重试
monitor_db.update_last_checked(stock['id'])
except Exception as e:
print(f"获取股票 {symbol} 数据失败: {e}")
print(f"获取股票 {symbol} 数据失败: {e}")
# 即使失败也更新last_checked,避免持续重试
try:
monitor_db.update_last_checked(stock['id'])
except:
pass
def _check_trigger_conditions(self, stock: Dict, current_price: float):
"""检查触发条件"""
@@ -103,8 +130,13 @@ class StockMonitorService:
# 检查进场区间
if entry_range and entry_range.get('min') and entry_range.get('max'):
if current_price >= entry_range['min'] and current_price <= entry_range['max']:
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 进入进场区间 [{entry_range['min']}-{entry_range['max']}]"
monitor_db.add_notification(stock['id'], 'entry', message)
# 检查是否在最近60分钟内已发送过相同通知,避免重复
if not monitor_db.has_recent_notification(stock['id'], 'entry', minutes=60):
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 进入进场区间 [{entry_range['min']}-{entry_range['max']}]"
monitor_db.add_notification(stock['id'], 'entry', message)
# 立即发送通知(包括邮件)
notification_service.send_notifications()
# 如果启用量化交易,执行自动交易
if stock.get('quant_enabled', False):
@@ -112,8 +144,13 @@ class StockMonitorService:
# 检查止盈
if take_profit and current_price >= take_profit:
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 达到止盈位 {take_profit}"
monitor_db.add_notification(stock['id'], 'take_profit', message)
# 检查是否在最近60分钟内已发送过相同通知,避免重复
if not monitor_db.has_recent_notification(stock['id'], 'take_profit', minutes=60):
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 达到止盈位 {take_profit}"
monitor_db.add_notification(stock['id'], 'take_profit', message)
# 立即发送通知(包括邮件)
notification_service.send_notifications()
# 如果启用量化交易,执行自动交易
if stock.get('quant_enabled', False):
@@ -121,8 +158,13 @@ class StockMonitorService:
# 检查止损
if stop_loss and current_price <= stop_loss:
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 达到止损位 {stop_loss}"
monitor_db.add_notification(stock['id'], 'stop_loss', message)
# 检查是否在最近60分钟内已发送过相同通知,避免重复
if not monitor_db.has_recent_notification(stock['id'], 'stop_loss', minutes=60):
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 达到止损位 {stop_loss}"
monitor_db.add_notification(stock['id'], 'stop_loss', message)
# 立即发送通知(包括邮件)
notification_service.send_notifications()
# 如果启用量化交易,执行自动交易
if stock.get('quant_enabled', False):
@@ -159,12 +201,14 @@ class StockMonitorService:
if success:
print(f"✅ 量化交易成功: {stock['symbol']} - {msg}")
# 记录交易通知
# 记录交易通知(量化交易通知不检查重复,因为每次交易都应该通知)
monitor_db.add_notification(
stock['id'],
'quant_trade',
f"量化交易执行: {msg}"
)
# 立即发送通知(包括邮件)
notification_service.send_notifications()
else:
print(f"❌ 量化交易失败: {stock['symbol']} - {msg}")
+28 -3
View File
@@ -50,15 +50,28 @@ class NotificationService:
"""发送所有待发送的通知"""
notifications = monitor_db.get_pending_notifications()
if not notifications:
print("没有待发送的通知")
return
print(f"\n{'='*50}")
print(f"开始发送通知,共 {len(notifications)}")
print(f"{'='*50}")
for notification in notifications:
try:
print(f"\n处理通知: {notification['symbol']} - {notification['type']}")
if self.send_notification(notification):
monitor_db.mark_notification_sent(notification['id'])
print(f"✅ 通知已发送: {notification['message']}")
print(f"✅ 通知已成功发送并标记: {notification['message']}")
else:
print(f"❌ 通知发送失败: {notification['message']}")
except Exception as e:
print(f"❌ 发送通知时出错: {e}")
import traceback
traceback.print_exc()
print(f"{'='*50}\n")
def send_notification(self, notification: Dict) -> bool:
"""发送单个通知"""
@@ -76,7 +89,11 @@ class NotificationService:
# 检查邮件配置是否完整
if not all([self.config['smtp_server'], self.config['email_from'],
self.config['email_password'], self.config['email_to']]):
print("邮件配置不完整,使用界面通知")
print("⚠️ 邮件配置不完整,使用界面通知")
print(f" - SMTP服务器: {self.config['smtp_server'] or '未配置'}")
print(f" - 发件人: {self.config['email_from'] or '未配置'}")
print(f" - 收件人: {self.config['email_to'] or '未配置'}")
print(f" - 密码: {'已配置' if self.config['email_password'] else '未配置'}")
self._show_streamlit_notification(notification)
return True
@@ -100,17 +117,25 @@ class NotificationService:
msg.attach(MIMEText(body, 'html'))
print(f"📧 正在发送邮件...")
print(f" - 收件人: {self.config['email_to']}")
print(f" - 主题: 股票监测提醒 - {notification['symbol']}")
# 根据端口选择连接方式
if self.config['smtp_port'] == 465:
print(f" - 使用 SMTP_SSL 连接 {self.config['smtp_server']}:{self.config['smtp_port']}")
server = smtplib.SMTP_SSL(self.config['smtp_server'], self.config['smtp_port'], timeout=15)
else:
print(f" - 使用 SMTP+TLS 连接 {self.config['smtp_server']}:{self.config['smtp_port']}")
server = smtplib.SMTP(self.config['smtp_server'], self.config['smtp_port'], timeout=15)
server.starttls()
print(f" - 正在登录...")
server.login(self.config['email_from'], self.config['email_password'])
print(f" - 正在发送...")
server.send_message(msg)
server.quit()
print(f"邮件发送成功: {notification['symbol']}")
print(f"邮件发送成功: {notification['symbol']}")
return True
except Exception as e:
+215
View File
@@ -0,0 +1,215 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
通知系统诊断工具
用于排查邮件通知问题
"""
import sqlite3
from notification_service import notification_service
from monitor_db import monitor_db
from datetime import datetime
def check_database():
"""检查数据库中的通知"""
print("\n" + "="*60)
print("1. 检查数据库中的通知记录")
print("="*60)
conn = sqlite3.connect('stock_monitor.db')
cursor = conn.cursor()
# 检查所有通知
cursor.execute('''
SELECT n.id, s.symbol, s.name, n.type, n.message, n.triggered_at, n.sent
FROM notifications n
JOIN monitored_stocks s ON n.stock_id = s.id
ORDER BY n.triggered_at DESC
LIMIT 20
''')
notifications = cursor.fetchall()
if notifications:
print(f"\n最近20条通知记录:")
for row in notifications:
sent_status = "✅已发送" if row[6] else "⏳待发送"
print(f" [{sent_status}] {row[1]} - {row[3]} - {row[4]}")
print(f" 时间: {row[5]}")
else:
print(" ❌ 数据库中没有任何通知记录")
# 检查待发送通知
cursor.execute('''
SELECT COUNT(*) FROM notifications WHERE sent = FALSE
''')
pending_count = cursor.fetchone()[0]
print(f"\n待发送通知数量: {pending_count}")
conn.close()
def check_email_config():
"""检查邮件配置"""
print("\n" + "="*60)
print("2. 检查邮件配置")
print("="*60)
config = notification_service.get_email_config_status()
print(f"\n邮件启用: {'✅ 是' if config['enabled'] else '❌ 否'}")
print(f"SMTP服务器: {config['smtp_server']}")
print(f"SMTP端口: {config['smtp_port']}")
print(f"发件人: {config['email_from']}")
print(f"收件人: {config['email_to']}")
print(f"配置完整: {'✅ 是' if config['configured'] else '❌ 否'}")
return config['configured']
def test_email_connection():
"""测试邮件连接"""
print("\n" + "="*60)
print("3. 测试邮件连接")
print("="*60)
print("\n正在发送测试邮件...")
success, message = notification_service.send_test_email()
if success:
print(f"{message}")
return True
else:
print(f"{message}")
return False
def send_pending_notifications():
"""尝试发送待发送的通知"""
print("\n" + "="*60)
print("4. 尝试发送待发送的通知")
print("="*60)
pending = monitor_db.get_pending_notifications()
if not pending:
print("\n没有待发送的通知")
return
print(f"\n找到 {len(pending)} 条待发送通知:")
for notif in pending:
print(f" - {notif['symbol']}: {notif['message']}")
print("\n开始发送...")
notification_service.send_notifications()
# 再次检查
pending_after = monitor_db.get_pending_notifications()
print(f"\n发送后剩余待发送通知: {len(pending_after)}")
def check_stock_prices():
"""检查监测股票的当前价格和触发条件"""
print("\n" + "="*60)
print("5. 检查监测股票状态")
print("="*60)
stocks = monitor_db.get_monitored_stocks()
if not stocks:
print("\n没有监测股票")
return
print(f"\n共有 {len(stocks)} 只股票在监测:")
for stock in stocks:
print(f"\n📊 {stock['symbol']} - {stock['name']}")
print(f" 当前价格: {stock['current_price']}")
entry_range = stock['entry_range']
print(f" 进场区间: {entry_range['min']} - {entry_range['max']}")
if stock['take_profit']:
print(f" 止盈位: {stock['take_profit']}")
if stock['stop_loss']:
print(f" 止损位: {stock['stop_loss']}")
print(f" 通知启用: {'' if stock['notification_enabled'] else ''}")
print(f" 最后检查: {stock['last_checked'] or '从未'}")
# 检查是否满足触发条件
if stock['current_price']:
price = float(stock['current_price'])
# 检查进场区间
if price >= entry_range['min'] and price <= entry_range['max']:
print(f" 🟢 当前价格在进场区间内")
# 检查最近是否有通知
if monitor_db.has_recent_notification(stock['id'], 'entry', minutes=60):
print(f" ⚠️ 但最近60分钟内已发送过进场通知(防重复机制)")
else:
print(f" ❗ 应该触发通知但没有!")
else:
if price < entry_range['min']:
print(f" ⬇️ 当前价格低于进场区间 (差 {entry_range['min'] - price:.2f})")
else:
print(f" ⬆️ 当前价格高于进场区间 (高出 {price - entry_range['max']:.2f})")
# 检查止盈
if stock['take_profit'] and price >= stock['take_profit']:
print(f" 🟡 已达到止盈位")
if monitor_db.has_recent_notification(stock['id'], 'take_profit', minutes=60):
print(f" ⚠️ 最近60分钟内已发送过止盈通知")
# 检查止损
if stock['stop_loss'] and price <= stock['stop_loss']:
print(f" 🔴 已达到止损位")
if monitor_db.has_recent_notification(stock['id'], 'stop_loss', minutes=60):
print(f" ⚠️ 最近60分钟内已发送过止损通知")
def main():
"""主函数"""
print("\n" + "="*60)
print("股票监测通知系统诊断工具")
print("="*60)
print(f"诊断时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
# 1. 检查数据库
check_database()
# 2. 检查邮件配置
email_configured = check_email_config()
# 3. 测试邮件连接
if email_configured:
email_ok = test_email_connection()
else:
print("\n⚠️ 邮件未配置,跳过邮件测试")
email_ok = False
# 4. 检查股票价格和触发条件
check_stock_prices()
# 5. 尝试发送待发送通知
send_pending_notifications()
# 总结
print("\n" + "="*60)
print("诊断总结")
print("="*60)
if email_configured and email_ok:
print("✅ 邮件系统正常")
elif email_configured:
print("⚠️ 邮件已配置但发送失败,请检查配置参数")
else:
print("❌ 邮件未配置")
print("\n建议:")
print("1. 确保监测服务正在运行")
print("2. 检查股票价格是否在触发区间内")
print("3. 注意60分钟防重复机制")
print("4. 查看终端日志中的详细错误信息")
print("5. 如果邮件测试成功但监测通知收不到,检查.env配置")
print("\n" + "="*60)
if __name__ == "__main__":
main()
+188
View File
@@ -0,0 +1,188 @@
# 实时监测功能优化说明
## 修复日期
2025年10月9日
## 问题描述
### 问题1:邮件通知未发送
- **现象**:邮件测试可以发送接收成功,但实时监控运行后到达价格,网页有通知,但邮件收不到通知
- **原因**:监测服务检测到价格触发条件时,只是将通知添加到数据库,但从未调用邮件发送功能
### 问题2:数据获取过于频繁
- **现象**:终端日志显示大量"获取实时数据失败"错误,连接被远程服务器关闭
- **原因**
1. 监测循环间隔太短(60秒)
2. 多个股票同时请求数据,导致API限流
3. 获取失败后没有更新检查时间,导致持续重试
## 修复内容
### 1. 邮件通知修复 (monitor_service.py)
**修改点:**
- 在检测到价格触发条件后,立即调用 `notification_service.send_notifications()` 发送邮件通知
- 触发条件包括:
- 进入进场区间
- 达到止盈位
- 达到止损位
- 量化交易执行
**代码示例:**
```python
# 检查进场区间
if current_price >= entry_range['min'] and current_price <= entry_range['max']:
if not monitor_db.has_recent_notification(stock['id'], 'entry', minutes=60):
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 进入进场区间"
monitor_db.add_notification(stock['id'], 'entry', message)
# 立即发送通知(包括邮件)
notification_service.send_notifications()
```
### 2. 防止重复通知 (monitor_db.py)
**新增功能:**
- 添加 `has_recent_notification()` 方法
- 检查是否在最近60分钟内已发送过相同类型的通知
- 避免同一个价格触发条件重复发送邮件
**代码示例:**
```python
def has_recent_notification(self, stock_id: int, notification_type: str, minutes: int = 60) -> bool:
"""检查是否在最近X分钟内已有相同类型的通知"""
# 查询数据库检查重复
```
### 3. 优化监测频率 (monitor_service.py)
**修改点:**
1. **增加监测循环间隔**
- 从 60秒 → 300秒(5分钟)
- 减少循环次数,降低系统负载
2. **增加股票间延迟**
- 每个股票请求之间等待3秒
- 避免同时请求多个股票导致API限流
3. **智能跳过检查**
- 根据每个股票的 `check_interval` 参数判断是否需要更新
- 显示距离下次检查的剩余时间
4. **失败重试优化**
- 获取失败时也更新 `last_checked` 时间
- 避免持续重试失败的请求
- 错误后等待60秒再继续
**代码示例:**
```python
def _monitor_loop(self):
"""监测循环"""
print("监测服务已启动")
while self.running:
try:
self._check_all_stocks()
time.sleep(300) # 每5分钟检查一次
except Exception as e:
print(f"监测服务错误: {e}")
time.sleep(60) # 错误后等待1分钟再重试
```
### 4. 新增数据库方法 (monitor_db.py)
**新增:**
```python
def update_last_checked(self, stock_id: int):
"""仅更新最后检查时间(用于获取失败的情况)"""
# 即使价格获取失败,也更新检查时间
# 避免持续重试
```
## 使用说明
### 监测间隔设置
- 在添加或编辑股票监测时,可以设置 `check_interval` 参数
- 建议设置:
- 日内短线:5-15分钟
- 波段交易:30-60分钟
- 长线投资:120分钟或更长
### 邮件通知配置
确保 `.env` 文件中配置了以下参数:
```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
```
### 监测日志查看
监测服务会在终端输出以下信息:
- `✅ {symbol} 当前价格: ¥{price}` - 成功获取价格
- `股票 {symbol} 距离下次检查还有 X 分钟` - 跳过检查
- `❌ 更新股票 {symbol} 价格失败` - 获取失败
- `✅ 本轮共更新了 X 只股票` - 本轮统计
## 注意事项
1. **API限流**
- 避免设置过短的监测间隔(建议 ≥ 5分钟)
- 监测股票数量过多时,适当增加间隔
2. **邮件通知**
- 同一触发条件60分钟内只发送一次
- 测试邮件功能正常不代表监测邮件一定能发送
- 检查邮箱垃圾箱
3. **数据准确性**
- 非交易时间获取的价格可能不准确
- 建议在交易时间内使用监测功能
4. **系统资源**
- 监测服务在后台线程运行
- 长时间运行建议定期检查日志
- 可以随时启动/停止监测服务
## 测试建议
1. **邮件通知测试**
```
1. 先发送测试邮件,确认邮件配置正确
2. 添加一只股票,设置较宽的进场区间
3. 等待价格进入区间,查看是否收到邮件
```
2. **频率控制测试**
```
1. 添加2-3只股票,设置不同的监测间隔
2. 启动监测服务
3. 观察终端日志,确认按间隔更新
```
## 预期效果
✅ 价格到达触发条件时,同时收到:
- 网页界面通知
- 邮件通知
✅ 监测频率合理:
- 按照设置的间隔获取价格
- 不会频繁请求导致API限流
- 减少"获取实时数据失败"错误
✅ 系统更稳定:
- 获取失败不会持续重试
- 每个股票之间有延迟保护
- 更好的错误处理和日志输出
## 相关文件
- `monitor_service.py` - 监测服务核心逻辑
- `monitor_db.py` - 数据库操作
- `notification_service.py` - 邮件通知服务
- `monitor_manager.py` - 监测管理界面
- `.env` - 邮件配置文件
+276
View File
@@ -0,0 +1,276 @@
# 通知问题排查指南
## 问题现象
监测股票到价格了,但没有收到消息(包括网页和邮件)
## 快速诊断
### 步骤1:运行诊断工具
```bash
python test_notification_debug.py
```
这个工具会检查:
- ✅ 数据库中的通知记录
- ✅ 邮件配置状态
- ✅ 邮件发送测试
- ✅ 股票价格和触发条件
- ✅ 待发送通知
### 步骤2:检查常见问题
#### 问题1:价格已不在触发区间
**症状**:股票价格曾经到达过,但现在已经超出区间
**示例**
- 进场区间:44.87 - 46.48
- 当前价格:47.99(已超出)
- 结果:不会触发新通知
**解决**
- 查看"通知历史"确认是否之前已触发过
- 如需重新监测,调整进场区间
#### 问题260分钟防重复机制
**症状**:价格在区间内,但不发送通知
**原因**:同一触发条件60分钟内只发送一次,避免重复
**查看方法**
```bash
python test_notification_debug.py
```
会显示:"⚠️ 最近60分钟内已发送过XX通知"
**解决**
- 等待60分钟后会自动重新发送
- 或者清空数据库通知记录(会导致历史丢失)
#### 问题3:网页通知显示问题
**症状**:有通知但网页看不到
**原因**
1. 之前的显示逻辑只显示"未发送"的通知
2. 通知发送后被标记为"已发送"就不显示了
**已修复**
- 现在显示所有最近通知(包括已发送和未发送)
- 显示发送状态:"✅ 已发送" 或 "⏳ 待发送"
#### 问题4:邮件配置问题
**检查清单**
```env
# .env 文件
EMAIL_ENABLED=true # 必须是 true
SMTP_SERVER=smtp.qq.com # QQ邮箱或其他
SMTP_PORT=587 # 587(TLS) 或 465(SSL)
EMAIL_FROM=your_email@qq.com # 发件邮箱
EMAIL_PASSWORD=xxxxxx # 授权码(不是登录密码!)
EMAIL_TO=receiver@example.com # 收件邮箱
```
**测试方法**
1. 在网页界面:监测管理 → 通知管理 → 发送测试邮件
2. 或运行:`python test_notification_debug.py`
**常见错误**
- ❌ 使用邮箱登录密码而非授权码
- ❌ EMAIL_ENABLED 设置为 false
- ❌ 端口号不正确(QQ邮箱用587或465)
## 完整排查流程
### 1. 确认监测服务运行
```
网页界面 → 监测管理
查看状态:🟢 运行中
```
如果显示"🔴 已停止",点击"▶️ 启动监测"
### 2. 查看通知历史
```
网页界面 → 监测管理 → 通知历史
```
应该能看到:
- 所有触发的通知(新版本已修复)
- 每条通知的发送状态
- 触发时间
### 3. 检查股票状态
运行诊断工具查看详细信息:
```bash
python test_notification_debug.py
```
关注输出中的:
```
📊 股票代码 - 股票名称
当前价格: XX.XX
进场区间: XX.XX - XX.XX
🟢 当前价格在进场区间内
⚠️ 但最近60分钟内已发送过进场通知
```
### 4. 查看终端日志
监测服务运行时会输出详细日志:
**正常流程**
```
正在更新股票 300832 的价格...
✅ 300832 当前价格: ¥66.5
==================================================
开始发送通知,共 1 条
==================================================
处理通知: 300832 - entry
📧 正在发送邮件...
- 收件人: xxx@qq.com
- 主题: 股票监测提醒 - 300832
- 使用 SMTP+TLS 连接 smtp.qq.com:587
- 正在登录...
- 正在发送...
✅ 邮件发送成功: 300832
✅ 通知已成功发送并标记
```
**配置问题**
```
⚠️ 邮件配置不完整,使用界面通知
- SMTP服务器: 未配置
- 发件人: 未配置
- 收件人: 未配置
- 密码: 未配置
```
**发送失败**
```
邮件发送失败: [Errno 11001] getaddrinfo failed
使用界面通知作为备用方案
```
### 5. 手动测试通知
如果想立即测试通知(不等待价格触发):
```python
# 创建测试脚本 test_manual_notification.py
from monitor_db import monitor_db
from notification_service import notification_service
# 添加一条测试通知
monitor_db.add_notification(
stock_id=1, # 你的股票ID
notification_type='entry',
message='这是一条测试通知'
)
# 立即发送
notification_service.send_notifications()
```
## 修复后的改进
### 1. 网页通知显示增强 ✅
- 显示所有最近通知(不只是待发送的)
- 显示发送状态标识
- 显示待发送通知数量警告
### 2. 日志输出增强 ✅
- 详细的邮件发送过程日志
- 配置检查提示
- 错误堆栈跟踪
### 3. 防重复机制 ✅
- 60分钟内同类型通知只发送一次
- 避免价格波动导致的重复通知
### 4. 监测频率优化 ✅
- 5分钟循环检查
- 按设定间隔更新每只股票
- 股票间3秒延迟,避免API限流
## 预期效果
当股票价格满足条件时:
1. **数据库记录**
```
notifications 表中新增记录
sent = FALSE
```
2. **发送通知** ✅
```
调用 notification_service.send_notifications()
尝试发送邮件
标记 sent = TRUE
```
3. **邮件到达** ✅
```
收件箱收到邮件
主题:股票监测提醒 - XXX
```
4. **网页显示** ✅
```
监测管理 → 通知历史
显示:[✅ 已发送] 股票 XXX ...
```
## 常见场景答疑
### Q1: 价格到了但没收到通知
**检查**
1. 是否60分钟内已发送过?→ 查看通知历史
2. 邮件配置是否正确?→ 发送测试邮件
3. 监测服务是否运行?→ 查看状态
4. 通知是否被禁用?→ 检查股票的"通知启用"开关
### Q2: 测试邮件能收到,但监测邮件收不到
**可能原因**
1. 触发条件未满足(价格不在区间)
2. 60分钟防重复
3. 监测服务未启动
4. 代码更新前的旧版本问题
**解决**
1. 运行 `test_notification_debug.py` 全面检查
2. 查看终端日志确认是否真的触发
3. 确保使用的是修复后的代码
### Q3: 网页看不到通知
**已修复**:旧版本只显示待发送通知,新版本显示所有通知
**刷新界面**:点击"🔄 刷新状态"
### Q4: 想立即重新发送通知
**方法**
1. 清空通知记录(会丢失历史)
2. 或等待60分钟
3. 或调整价格区间让其重新触发
## 技术支持
如果问题仍未解决:
1. **提供诊断报告**
```bash
python test_notification_debug.py > diagnosis.txt
```
2. **提供终端日志**
复制运行 Streamlit 的终端输出
3. **检查文件**
- `.env` 配置(隐藏密码)
- `stock_monitor.db` 是否存在
- 监测股票列表截图
4. **描述问题**
- 什么时候开始的?
- 做了什么操作?
- 预期行为 vs 实际行为