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

376 lines
12 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 -*-
"""
低价擒牛策略监控模块
监控持仓股票的卖出信号
"""
import sqlite3
import pandas as pd
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
import logging
import os
class LowPriceBullMonitor:
"""低价擒牛策略监控器"""
def __init__(self, db_path: str = "low_price_bull_monitor.db"):
"""
初始化监控器
Args:
db_path: 数据库文件路径
"""
self.logger = logging.getLogger(__name__)
self.db_path = db_path
self._init_database()
def _init_database(self):
"""初始化数据库"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 创建监控列表表
cursor.execute("""
CREATE TABLE IF NOT EXISTS monitored_stocks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
stock_code TEXT NOT NULL,
stock_name TEXT NOT NULL,
buy_price REAL NOT NULL,
buy_date TEXT NOT NULL,
holding_days INTEGER DEFAULT 0,
status TEXT DEFAULT 'holding',
add_time TEXT NOT NULL,
remove_time TEXT,
remove_reason TEXT,
UNIQUE(stock_code, status)
)
""")
# 创建卖出提醒表
cursor.execute("""
CREATE TABLE IF NOT EXISTS sell_alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
stock_code TEXT NOT NULL,
stock_name TEXT NOT NULL,
alert_type TEXT NOT NULL,
alert_reason TEXT NOT NULL,
current_price REAL,
ma5 REAL,
ma20 REAL,
holding_days INTEGER,
alert_time TEXT NOT NULL,
is_sent INTEGER DEFAULT 0
)
""")
conn.commit()
conn.close()
self.logger.info("低价擒牛监控数据库初始化完成")
def add_stock(self, stock_code: str, stock_name: str, buy_price: float,
buy_date: str = None) -> Tuple[bool, str]:
"""
添加股票到监控列表
Args:
stock_code: 股票代码(不含后缀)
stock_name: 股票名称
buy_price: 买入价格
buy_date: 买入日期(格式:YYYY-MM-DD
Returns:
(是否成功, 消息)
"""
try:
if buy_date is None:
buy_date = datetime.now().strftime("%Y-%m-%d")
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 检查是否已存在
cursor.execute("""
SELECT id FROM monitored_stocks
WHERE stock_code = ? AND status = 'holding'
""", (stock_code,))
if cursor.fetchone():
conn.close()
return False, f"股票 {stock_code} 已在监控列表中"
# 添加到监控列表
cursor.execute("""
INSERT INTO monitored_stocks
(stock_code, stock_name, buy_price, buy_date, add_time)
VALUES (?, ?, ?, ?, ?)
""", (stock_code, stock_name, buy_price, buy_date,
datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
conn.commit()
conn.close()
self.logger.info(f"添加股票到监控: {stock_code} {stock_name}")
return True, f"成功添加 {stock_code} {stock_name} 到监控列表"
except Exception as e:
self.logger.error(f"添加股票失败: {e}")
return False, f"添加失败: {str(e)}"
def remove_stock(self, stock_code: str, reason: str = "手动移除") -> Tuple[bool, str]:
"""
从监控列表移除股票
Args:
stock_code: 股票代码
reason: 移除原因
Returns:
(是否成功, 消息)
"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 先检查是否存在持仓中的股票
cursor.execute("""
SELECT id FROM monitored_stocks
WHERE stock_code = ? AND status = 'holding'
""", (stock_code,))
if not cursor.fetchone():
conn.close()
return False, f"股票 {stock_code} 不在监控列表中"
# 先删除该股票的所有'removed'记录(避免UNIQUE约束冲突)
cursor.execute("""
DELETE FROM monitored_stocks
WHERE stock_code = ? AND status = 'removed'
""", (stock_code,))
# 然后更新持仓中的记录为'removed'
cursor.execute("""
UPDATE monitored_stocks
SET status = 'removed',
remove_time = ?,
remove_reason = ?
WHERE stock_code = ? AND status = 'holding'
""", (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), reason, stock_code))
conn.commit()
conn.close()
self.logger.info(f"移除股票: {stock_code}, 原因: {reason}")
return True, f"成功移除 {stock_code}"
except Exception as e:
self.logger.error(f"移除股票失败: {e}")
return False, f"移除失败: {str(e)}"
def get_monitored_stocks(self) -> List[Dict]:
"""
获取所有监控中的股票
Returns:
股票列表
"""
try:
conn = sqlite3.connect(self.db_path)
df = pd.read_sql_query("""
SELECT * FROM monitored_stocks
WHERE status = 'holding'
ORDER BY add_time DESC
""", conn)
conn.close()
return df.to_dict('records') if not df.empty else []
except Exception as e:
self.logger.error(f"获取监控列表失败: {e}")
return []
def update_holding_days(self):
"""更新所有股票的持有天数"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 获取所有持仓股票
cursor.execute("""
SELECT stock_code, buy_date FROM monitored_stocks
WHERE status = 'holding'
""")
stocks = cursor.fetchall()
today = datetime.now().date()
for stock_code, buy_date in stocks:
buy_date_obj = datetime.strptime(buy_date, "%Y-%m-%d").date()
holding_days = (today - buy_date_obj).days
cursor.execute("""
UPDATE monitored_stocks
SET holding_days = ?
WHERE stock_code = ? AND status = 'holding'
""", (holding_days, stock_code))
conn.commit()
conn.close()
self.logger.info("持有天数更新完成")
except Exception as e:
self.logger.error(f"更新持有天数失败: {e}")
def add_sell_alert(self, stock_code: str, stock_name: str, alert_type: str,
alert_reason: str, current_price: float = None,
ma5: float = None, ma20: float = None,
holding_days: int = None) -> bool:
"""
添加卖出提醒
Args:
stock_code: 股票代码
stock_name: 股票名称
alert_type: 提醒类型(holding_days/ma_cross
alert_reason: 提醒原因
current_price: 当前价格
ma5: MA5值
ma20: MA20值
holding_days: 持有天数
Returns:
是否成功
"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 检查是否已存在相同的提醒
cursor.execute("""
SELECT id FROM sell_alerts
WHERE stock_code = ? AND alert_type = ? AND is_sent = 0
""", (stock_code, alert_type))
if cursor.fetchone():
conn.close()
return False
cursor.execute("""
INSERT INTO sell_alerts
(stock_code, stock_name, alert_type, alert_reason,
current_price, ma5, ma20, holding_days, alert_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (stock_code, stock_name, alert_type, alert_reason,
current_price, ma5, ma20, holding_days,
datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
conn.commit()
conn.close()
self.logger.info(f"添加卖出提醒: {stock_code} - {alert_reason}")
return True
except Exception as e:
self.logger.error(f"添加卖出提醒失败: {e}")
return False
def get_pending_alerts(self) -> List[Dict]:
"""
获取待发送的提醒
Returns:
提醒列表
"""
try:
conn = sqlite3.connect(self.db_path)
df = pd.read_sql_query("""
SELECT * FROM sell_alerts
WHERE is_sent = 0
ORDER BY alert_time DESC
""", conn)
conn.close()
return df.to_dict('records') if not df.empty else []
except Exception as e:
self.logger.error(f"获取提醒失败: {e}")
return []
def mark_alert_sent(self, alert_id: int):
"""标记提醒已发送"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
UPDATE sell_alerts
SET is_sent = 1
WHERE id = ?
""", (alert_id,))
conn.commit()
conn.close()
except Exception as e:
self.logger.error(f"标记提醒失败: {e}")
def get_history_alerts(self, limit: int = 50) -> List[Dict]:
"""
获取历史提醒记录
Args:
limit: 返回记录数
Returns:
提醒列表
"""
try:
conn = sqlite3.connect(self.db_path)
df = pd.read_sql_query(f"""
SELECT * FROM sell_alerts
ORDER BY alert_time DESC
LIMIT {limit}
""", conn)
conn.close()
return df.to_dict('records') if not df.empty else []
except Exception as e:
self.logger.error(f"获取历史提醒失败: {e}")
return []
def clear_old_alerts(self, days: int = 30):
"""
清理旧的提醒记录
Args:
days: 保留天数
"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cutoff_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
cursor.execute("""
DELETE FROM sell_alerts
WHERE alert_time < ? AND is_sent = 1
""", (cutoff_date,))
deleted = cursor.rowcount
conn.commit()
conn.close()
self.logger.info(f"清理了 {deleted} 条旧提醒记录")
except Exception as e:
self.logger.error(f"清理旧提醒失败: {e}")
# 全局监控器实例
low_price_bull_monitor = LowPriceBullMonitor()