修复盯盘bug
This commit is contained in:
@@ -152,6 +152,36 @@ class StockMonitorDatabase:
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
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):
|
def add_notification(self, stock_id: int, notification_type: str, message: str):
|
||||||
"""添加提醒记录"""
|
"""添加提醒记录"""
|
||||||
conn = sqlite3.connect(self.db_path)
|
conn = sqlite3.connect(self.db_path)
|
||||||
@@ -193,6 +223,35 @@ class StockMonitorDatabase:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return notifications
|
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):
|
def mark_notification_sent(self, notification_id: int):
|
||||||
"""标记提醒已发送"""
|
"""标记提醒已发送"""
|
||||||
conn = sqlite3.connect(self.db_path)
|
conn = sqlite3.connect(self.db_path)
|
||||||
|
|||||||
+15
-5
@@ -577,21 +577,31 @@ def display_notification_management():
|
|||||||
with col2:
|
with col2:
|
||||||
st.subheader("📱 通知历史")
|
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']
|
notification_type = notification['type']
|
||||||
color_map = {
|
color_map = {
|
||||||
'entry': '🟢',
|
'entry': '🟢',
|
||||||
'take_profit': '🟡',
|
'take_profit': '🟡',
|
||||||
'stop_loss': '🔴'
|
'stop_loss': '🔴',
|
||||||
|
'quant_trade': '🤖'
|
||||||
}
|
}
|
||||||
icon = color_map.get(notification_type, '🔵')
|
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)
|
col_a, col_b = st.columns(2)
|
||||||
|
|||||||
+53
-9
@@ -8,6 +8,7 @@ import streamlit as st
|
|||||||
from monitor_db import monitor_db
|
from monitor_db import monitor_db
|
||||||
from stock_data import StockDataFetcher
|
from stock_data import StockDataFetcher
|
||||||
from miniqmt_interface import miniqmt, get_miniqmt_status
|
from miniqmt_interface import miniqmt, get_miniqmt_status
|
||||||
|
from notification_service import notification_service
|
||||||
|
|
||||||
class StockMonitorService:
|
class StockMonitorService:
|
||||||
"""股票监测服务"""
|
"""股票监测服务"""
|
||||||
@@ -36,19 +37,22 @@ class StockMonitorService:
|
|||||||
|
|
||||||
def _monitor_loop(self):
|
def _monitor_loop(self):
|
||||||
"""监测循环"""
|
"""监测循环"""
|
||||||
|
print("监测服务已启动")
|
||||||
while self.running:
|
while self.running:
|
||||||
try:
|
try:
|
||||||
self._check_all_stocks()
|
self._check_all_stocks()
|
||||||
time.sleep(60) # 每分钟检查一次
|
# 根据最小监测间隔决定循环间隔,最少5分钟检查一次
|
||||||
|
time.sleep(300) # 每5分钟检查一次
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"监测服务错误: {e}")
|
print(f"监测服务错误: {e}")
|
||||||
time.sleep(10)
|
time.sleep(60) # 错误后等待1分钟再重试
|
||||||
|
|
||||||
def _check_all_stocks(self):
|
def _check_all_stocks(self):
|
||||||
"""检查所有监测股票"""
|
"""检查所有监测股票"""
|
||||||
stocks = monitor_db.get_monitored_stocks()
|
stocks = monitor_db.get_monitored_stocks()
|
||||||
current_time = datetime.now()
|
current_time = datetime.now()
|
||||||
|
|
||||||
|
updated_count = 0
|
||||||
for stock in stocks:
|
for stock in stocks:
|
||||||
# 检查是否需要更新价格
|
# 检查是否需要更新价格
|
||||||
last_checked = stock.get('last_checked')
|
last_checked = stock.get('last_checked')
|
||||||
@@ -58,12 +62,25 @@ class StockMonitorService:
|
|||||||
last_checked_dt = datetime.fromisoformat(last_checked)
|
last_checked_dt = datetime.fromisoformat(last_checked)
|
||||||
next_check = last_checked_dt + timedelta(minutes=check_interval)
|
next_check = last_checked_dt + timedelta(minutes=check_interval)
|
||||||
if current_time < next_check:
|
if current_time < next_check:
|
||||||
|
# 显示距离下次检查的时间
|
||||||
|
time_left = (next_check - current_time).total_seconds() / 60
|
||||||
|
print(f"股票 {stock['symbol']} 距离下次检查还有 {time_left:.1f} 分钟")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
print(f"正在更新股票 {stock['symbol']} 的价格...")
|
||||||
self._update_stock_price(stock)
|
self._update_stock_price(stock)
|
||||||
|
updated_count += 1
|
||||||
|
|
||||||
|
# 在每个股票请求之间增加延迟,避免API限流
|
||||||
|
if updated_count < len(stocks):
|
||||||
|
time.sleep(3) # 每个股票之间等待3秒
|
||||||
except Exception as e:
|
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):
|
def _update_stock_price(self, stock: Dict):
|
||||||
"""更新股票价格并检查条件"""
|
"""更新股票价格并检查条件"""
|
||||||
@@ -78,18 +95,28 @@ class StockMonitorService:
|
|||||||
if current_price and current_price != 'N/A':
|
if current_price and current_price != 'N/A':
|
||||||
try:
|
try:
|
||||||
current_price = float(current_price)
|
current_price = float(current_price)
|
||||||
# 更新数据库
|
# 更新数据库(包括更新last_checked时间)
|
||||||
monitor_db.update_stock_price(stock['id'], current_price)
|
monitor_db.update_stock_price(stock['id'], current_price)
|
||||||
|
print(f"✅ {symbol} 当前价格: ¥{current_price}")
|
||||||
|
|
||||||
# 检查触发条件
|
# 检查触发条件
|
||||||
self._check_trigger_conditions(stock, current_price)
|
self._check_trigger_conditions(stock, current_price)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError) as e:
|
||||||
print(f"股票 {symbol} 价格格式错误: {current_price}")
|
print(f"❌ 股票 {symbol} 价格格式错误: {current_price}")
|
||||||
|
# 即使失败也更新last_checked,避免持续重试
|
||||||
|
monitor_db.update_last_checked(stock['id'])
|
||||||
else:
|
else:
|
||||||
print(f"无法获取股票 {symbol} 的当前价格")
|
print(f"⚠️ 无法获取股票 {symbol} 的当前价格")
|
||||||
|
# 更新last_checked,避免持续重试
|
||||||
|
monitor_db.update_last_checked(stock['id'])
|
||||||
|
|
||||||
except Exception as e:
|
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):
|
def _check_trigger_conditions(self, stock: Dict, current_price: float):
|
||||||
"""检查触发条件"""
|
"""检查触发条件"""
|
||||||
@@ -103,27 +130,42 @@ class StockMonitorService:
|
|||||||
# 检查进场区间
|
# 检查进场区间
|
||||||
if entry_range and entry_range.get('min') and entry_range.get('max'):
|
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']:
|
if current_price >= entry_range['min'] and current_price <= entry_range['max']:
|
||||||
|
# 检查是否在最近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']}]"
|
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 进入进场区间 [{entry_range['min']}-{entry_range['max']}]"
|
||||||
monitor_db.add_notification(stock['id'], 'entry', message)
|
monitor_db.add_notification(stock['id'], 'entry', message)
|
||||||
|
|
||||||
|
# 立即发送通知(包括邮件)
|
||||||
|
notification_service.send_notifications()
|
||||||
|
|
||||||
# 如果启用量化交易,执行自动交易
|
# 如果启用量化交易,执行自动交易
|
||||||
if stock.get('quant_enabled', False):
|
if stock.get('quant_enabled', False):
|
||||||
self._execute_quant_trade(stock, 'entry', current_price)
|
self._execute_quant_trade(stock, 'entry', current_price)
|
||||||
|
|
||||||
# 检查止盈
|
# 检查止盈
|
||||||
if take_profit and current_price >= take_profit:
|
if take_profit and current_price >= take_profit:
|
||||||
|
# 检查是否在最近60分钟内已发送过相同通知,避免重复
|
||||||
|
if not monitor_db.has_recent_notification(stock['id'], 'take_profit', minutes=60):
|
||||||
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 达到止盈位 {take_profit}"
|
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 达到止盈位 {take_profit}"
|
||||||
monitor_db.add_notification(stock['id'], 'take_profit', message)
|
monitor_db.add_notification(stock['id'], 'take_profit', message)
|
||||||
|
|
||||||
|
# 立即发送通知(包括邮件)
|
||||||
|
notification_service.send_notifications()
|
||||||
|
|
||||||
# 如果启用量化交易,执行自动交易
|
# 如果启用量化交易,执行自动交易
|
||||||
if stock.get('quant_enabled', False):
|
if stock.get('quant_enabled', False):
|
||||||
self._execute_quant_trade(stock, 'take_profit', current_price)
|
self._execute_quant_trade(stock, 'take_profit', current_price)
|
||||||
|
|
||||||
# 检查止损
|
# 检查止损
|
||||||
if stop_loss and current_price <= stop_loss:
|
if stop_loss and current_price <= stop_loss:
|
||||||
|
# 检查是否在最近60分钟内已发送过相同通知,避免重复
|
||||||
|
if not monitor_db.has_recent_notification(stock['id'], 'stop_loss', minutes=60):
|
||||||
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 达到止损位 {stop_loss}"
|
message = f"股票 {stock['symbol']} ({stock['name']}) 价格 {current_price} 达到止损位 {stop_loss}"
|
||||||
monitor_db.add_notification(stock['id'], 'stop_loss', message)
|
monitor_db.add_notification(stock['id'], 'stop_loss', message)
|
||||||
|
|
||||||
|
# 立即发送通知(包括邮件)
|
||||||
|
notification_service.send_notifications()
|
||||||
|
|
||||||
# 如果启用量化交易,执行自动交易
|
# 如果启用量化交易,执行自动交易
|
||||||
if stock.get('quant_enabled', False):
|
if stock.get('quant_enabled', False):
|
||||||
self._execute_quant_trade(stock, 'stop_loss', current_price)
|
self._execute_quant_trade(stock, 'stop_loss', current_price)
|
||||||
@@ -159,12 +201,14 @@ class StockMonitorService:
|
|||||||
|
|
||||||
if success:
|
if success:
|
||||||
print(f"✅ 量化交易成功: {stock['symbol']} - {msg}")
|
print(f"✅ 量化交易成功: {stock['symbol']} - {msg}")
|
||||||
# 记录交易通知
|
# 记录交易通知(量化交易通知不检查重复,因为每次交易都应该通知)
|
||||||
monitor_db.add_notification(
|
monitor_db.add_notification(
|
||||||
stock['id'],
|
stock['id'],
|
||||||
'quant_trade',
|
'quant_trade',
|
||||||
f"量化交易执行: {msg}"
|
f"量化交易执行: {msg}"
|
||||||
)
|
)
|
||||||
|
# 立即发送通知(包括邮件)
|
||||||
|
notification_service.send_notifications()
|
||||||
else:
|
else:
|
||||||
print(f"❌ 量化交易失败: {stock['symbol']} - {msg}")
|
print(f"❌ 量化交易失败: {stock['symbol']} - {msg}")
|
||||||
|
|
||||||
|
|||||||
+28
-3
@@ -50,15 +50,28 @@ class NotificationService:
|
|||||||
"""发送所有待发送的通知"""
|
"""发送所有待发送的通知"""
|
||||||
notifications = monitor_db.get_pending_notifications()
|
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:
|
for notification in notifications:
|
||||||
try:
|
try:
|
||||||
|
print(f"\n处理通知: {notification['symbol']} - {notification['type']}")
|
||||||
if self.send_notification(notification):
|
if self.send_notification(notification):
|
||||||
monitor_db.mark_notification_sent(notification['id'])
|
monitor_db.mark_notification_sent(notification['id'])
|
||||||
print(f"✅ 通知已发送: {notification['message']}")
|
print(f"✅ 通知已成功发送并标记: {notification['message']}")
|
||||||
else:
|
else:
|
||||||
print(f"❌ 通知发送失败: {notification['message']}")
|
print(f"❌ 通知发送失败: {notification['message']}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ 发送通知时出错: {e}")
|
print(f"❌ 发送通知时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
print(f"{'='*50}\n")
|
||||||
|
|
||||||
def send_notification(self, notification: Dict) -> bool:
|
def send_notification(self, notification: Dict) -> bool:
|
||||||
"""发送单个通知"""
|
"""发送单个通知"""
|
||||||
@@ -76,7 +89,11 @@ class NotificationService:
|
|||||||
# 检查邮件配置是否完整
|
# 检查邮件配置是否完整
|
||||||
if not all([self.config['smtp_server'], self.config['email_from'],
|
if not all([self.config['smtp_server'], self.config['email_from'],
|
||||||
self.config['email_password'], self.config['email_to']]):
|
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)
|
self._show_streamlit_notification(notification)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -100,17 +117,25 @@ class NotificationService:
|
|||||||
|
|
||||||
msg.attach(MIMEText(body, 'html'))
|
msg.attach(MIMEText(body, 'html'))
|
||||||
|
|
||||||
|
print(f"📧 正在发送邮件...")
|
||||||
|
print(f" - 收件人: {self.config['email_to']}")
|
||||||
|
print(f" - 主题: 股票监测提醒 - {notification['symbol']}")
|
||||||
|
|
||||||
# 根据端口选择连接方式
|
# 根据端口选择连接方式
|
||||||
if self.config['smtp_port'] == 465:
|
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)
|
server = smtplib.SMTP_SSL(self.config['smtp_server'], self.config['smtp_port'], timeout=15)
|
||||||
else:
|
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 = smtplib.SMTP(self.config['smtp_server'], self.config['smtp_port'], timeout=15)
|
||||||
server.starttls()
|
server.starttls()
|
||||||
|
|
||||||
|
print(f" - 正在登录...")
|
||||||
server.login(self.config['email_from'], self.config['email_password'])
|
server.login(self.config['email_from'], self.config['email_password'])
|
||||||
|
print(f" - 正在发送...")
|
||||||
server.send_message(msg)
|
server.send_message(msg)
|
||||||
server.quit()
|
server.quit()
|
||||||
print(f"邮件发送成功: {notification['symbol']}")
|
print(f"✅ 邮件发送成功: {notification['symbol']}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -0,0 +1,276 @@
|
|||||||
|
# 通知问题排查指南
|
||||||
|
|
||||||
|
## 问题现象
|
||||||
|
监测股票到价格了,但没有收到消息(包括网页和邮件)
|
||||||
|
|
||||||
|
## 快速诊断
|
||||||
|
|
||||||
|
### 步骤1:运行诊断工具
|
||||||
|
```bash
|
||||||
|
python test_notification_debug.py
|
||||||
|
```
|
||||||
|
|
||||||
|
这个工具会检查:
|
||||||
|
- ✅ 数据库中的通知记录
|
||||||
|
- ✅ 邮件配置状态
|
||||||
|
- ✅ 邮件发送测试
|
||||||
|
- ✅ 股票价格和触发条件
|
||||||
|
- ✅ 待发送通知
|
||||||
|
|
||||||
|
### 步骤2:检查常见问题
|
||||||
|
|
||||||
|
#### 问题1:价格已不在触发区间
|
||||||
|
**症状**:股票价格曾经到达过,但现在已经超出区间
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
- 进场区间:44.87 - 46.48
|
||||||
|
- 当前价格:47.99(已超出)
|
||||||
|
- 结果:不会触发新通知
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- 查看"通知历史"确认是否之前已触发过
|
||||||
|
- 如需重新监测,调整进场区间
|
||||||
|
|
||||||
|
#### 问题2:60分钟防重复机制
|
||||||
|
**症状**:价格在区间内,但不发送通知
|
||||||
|
|
||||||
|
**原因**:同一触发条件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 实际行为
|
||||||
|
|
||||||
Reference in New Issue
Block a user