""" 山寨币状态跟踪器 — 去重 + 状态升级管理 状态生命周期:蓄力 → 加速 → 爁发 → 已告警 → 过期 只有状态升级才告警,同级别12h内不重复推送 """ import sqlite3 import json from datetime import datetime, timedelta DB_PATH = "/home/ubuntu/quant_monitor/altcoin/altcoin_monitor.db" STATE_ORDER = { "蓄力": 1, "加速": 2, "爆发": 3, "已告警": 4, "过期": 5, } ALERT_LEVELS = { "蓄力": "low", "加速": "medium", "爆发": "high", } # 12h内同级别不重复告警 ALERT_COOLDOWN_HOURS = 12 # 24h后状态自动过期 EXPIRE_HOURS = 24 def init_db(): """初始化数据库""" conn = sqlite3.connect(DB_PATH) conn.execute(""" CREATE TABLE IF NOT EXISTS coin_state ( symbol TEXT PRIMARY KEY, state TEXT NOT NULL, score REAL DEFAULT 0, anomaly_type TEXT DEFAULT '', sector TEXT DEFAULT '', leader_status TEXT DEFAULT '', detected_at TEXT NOT NULL, last_alert_time TEXT DEFAULT '', last_alert_level TEXT DEFAULT '', detail_json TEXT DEFAULT '{}' ) """) conn.commit() conn.close() def get_state(symbol): """获取币种当前状态""" conn = sqlite3.connect(DB_PATH) row = conn.execute("SELECT * FROM coin_state WHERE symbol=?", (symbol,)).fetchone() conn.close() if row: return { "symbol": row[0], "state": row[1], "score": row[2], "anomaly_type": row[3], "sector": row[4], "leader_status": row[5], "detected_at": row[6], "last_alert_time": row[7], "last_alert_level": row[8], "detail": json.loads(row[9]) if row[9] else {}, } return None def update_state(symbol, new_state, score=0, anomaly_type="", sector="", leader_status="", detail={}): """ 更新币种状态,判断是否需要告警 返回: {"should_alert": bool, "alert_level": str, "reason": str} """ current = get_state(symbol) now = datetime.now().isoformat() should_alert = False alert_level = "" reason = "" if current: current_level = STATE_ORDER.get(current["state"], 0) new_level = STATE_ORDER.get(new_state, 0) # 状态升级 → 检查冷却时间 if new_level > current_level: last_alert_time = current.get("last_alert_time", "") or "" last_alert_level_str = current.get("last_alert_level", "") or "" if not last_alert_time: # 没有上次告警记录 → 状态升级肯定要告警 should_alert = True alert_level = ALERT_LEVELS.get(new_state, "low") reason = f"状态升级(首次告警): {current['state']} → {new_state}" else: last_dt = datetime.fromisoformat(last_alert_time) cooldown_end = last_dt + timedelta(hours=ALERT_COOLDOWN_HOURS) # 新级别比上次告警级别高 → 不管冷却期都要告警 last_alert_num = STATE_ORDER.get(last_alert_level_str, 0) if new_level > last_alert_num: should_alert = True alert_level = ALERT_LEVELS.get(new_state, "low") reason = f"状态升级: {current['state']} → {new_state}" elif now > cooldown_end.isoformat(): # 冷却期过了,可以再告警 should_alert = True alert_level = ALERT_LEVELS.get(new_state, "low") reason = f"冷却期结束,重新告警: {new_state}" else: should_alert = False reason = f"冷却期内(上次告警: {last_alert_level_str} @ {last_alert_time})" # 状态降级 → 标记为"信号消退" elif new_level < current_level: should_alert = False reason = f"信号消退: {current['state']} → {new_state}" # 同级别,分数显著提升(≥3分) → 也告警 elif new_level == current_level and score - current["score"] >= 3: last_alert_time = current.get("last_alert_time", "") or "" if not last_alert_time: should_alert = True alert_level = ALERT_LEVELS.get(new_state, "low") reason = f"同级别分数显著提升(首次): {current['score']} → {score}" else: last_dt = datetime.fromisoformat(last_alert_time) cooldown_end = last_dt + timedelta(hours=ALERT_COOLDOWN_HOURS) if now > cooldown_end.isoformat(): should_alert = True alert_level = ALERT_LEVELS.get(new_state, "low") reason = f"同级别但分数显著提升: {current['score']} → {score}" # 更新记录 conn = sqlite3.connect(DB_PATH) alert_time = now if should_alert else current.get("last_alert_time", "") alert_lvl = alert_level if should_alert else current.get("last_alert_level", "") conn.execute(""" UPDATE coin_state SET state=?, score=?, anomaly_type=?, sector=?, leader_status=?, detail_json=?, last_alert_time=?, last_alert_level=? WHERE symbol=? """, (new_state, score, anomaly_type, sector, leader_status, json.dumps(detail, ensure_ascii=False), alert_time, alert_lvl, symbol)) conn.commit() conn.close() else: # 新币种,首次检测 # 蓄力级别首次不告警(太多噪音),加速和爆发才告警 if STATE_ORDER.get(new_state, 0) >= STATE_ORDER.get("加速", 0): should_alert = True alert_level = ALERT_LEVELS.get(new_state, "low") reason = f"新检测: {new_state}" conn = sqlite3.connect(DB_PATH) alert_time = now if should_alert else "" alert_lvl = alert_level if should_alert else "" conn.execute(""" INSERT INTO coin_state (symbol, state, score, anomaly_type, sector, leader_status, detected_at, last_alert_time, last_alert_level, detail_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, (symbol, new_state, score, anomaly_type, sector, leader_status, now, alert_time, alert_lvl, json.dumps(detail, ensure_ascii=False))) conn.commit() conn.close() return {"should_alert": should_alert, "alert_level": alert_level, "reason": reason} def get_all_active(): """获取所有活跃状态(未过期的)""" conn = sqlite3.connect(DB_PATH) cutoff = (datetime.now() - timedelta(hours=EXPIRE_HOURS)).isoformat() rows = conn.execute(""" SELECT symbol, state, score, anomaly_type, sector, leader_status, detected_at FROM coin_state WHERE detected_at > ? AND state != '过期' ORDER BY score DESC """, (cutoff,)).fetchall() conn.close() return [{ "symbol": r[0], "state": r[1], "score": r[2], "anomaly_type": r[3], "sector": r[4], "leader_status": r[5], "detected_at": r[6] } for r in rows] def get_candidates_for_confirm(): """获取加速状态的候选(需要第三层确认的)""" conn = sqlite3.connect(DB_PATH) cutoff = (datetime.now() - timedelta(hours=EXPIRE_HOURS)).isoformat() rows = conn.execute(""" SELECT symbol, state, score, anomaly_type, sector, leader_status, detail_json FROM coin_state WHERE state IN ('加速', '蓄力') AND detected_at > ? AND score >= 6 ORDER BY score DESC """, (cutoff,)).fetchall() conn.close() return [{ "symbol": r[0], "state": r[1], "score": r[2], "anomaly_type": r[3], "sector": r[4], "leader_status": r[5], "detail": json.loads(r[6]) if r[6] else {}, } for r in rows] def expire_old_states(): """过期超过24h的状态""" conn = sqlite3.connect(DB_PATH) cutoff = (datetime.now() - timedelta(hours=EXPIRE_HOURS)).isoformat() conn.execute("UPDATE coin_state SET state='过期' WHERE detected_at < ? AND state != '过期'", (cutoff,)) conn.commit() conn.close() if __name__ == "__main__": init_db() print("DB初始化完成") # 测试状态升级 r1 = update_state("FET/USDT", "蓄力", score=3, anomaly_type="布林收窄+量突变", sector="AI_DePIN") print(f"FET 蓄力: alert={r1['should_alert']}, reason={r1['reason']}") r2 = update_state("FET/USDT", "加速", score=8, anomaly_type="MACD金叉+RSI拐点", sector="AI_DePIN") print(f"FET 加速: alert={r2['should_alert']}, reason={r2['reason']}") r3 = update_state("FET/USDT", "爆发", score=12, anomaly_type="1H放量突破", sector="AI_DePIN") print(f"FET 爁发: alert={r3['should_alert']}, reason={r3['reason']}") # 测试同级别不重复 r4 = update_state("FET/USDT", "爆发", score=13, anomaly_type="1H放量突破+均线多头", sector="AI_DePIN") print(f"FET 爁发(重复): alert={r4['should_alert']}, reason={r4['reason']}") # 测试分数显著提升 r5 = update_state("FET/USDT", "爆发", score=16, anomaly_type="三级共振", sector="AI_DePIN") print(f"FET 爁发(分数升3+): alert={r5['should_alert']}, reason={r5['reason']}") # 测试新币种首次检测 r6 = update_state("PEPE/USDT", "蓄力", score=3, sector="MEME") print(f"PEPE 蓄力(首次): alert={r6['should_alert']}, reason={r6['reason']}") r7 = update_state("PEPE/USDT", "加速", score=8, sector="MEME") print(f"PEPE 加速(首次): alert={r7['should_alert']}, reason={r7['reason']}") # 查看活跃状态 active = get_all_active() print(f"\n活跃状态: {len(active)}个") for a in active: print(f" {a['symbol']}: {a['state']} (score={a['score']})") # 查看需要确认的候选 candidates = get_candidates_for_confirm() print(f"\n需要确认的候选: {len(candidates)}个")