from dataclasses import dataclass from datetime import datetime, timedelta from threading import Lock import logging @dataclass class _Entry: last_close: float expires_at: datetime class DipWatch: def __init__(self, expire_seconds: float = 300, rebound_threshold: float = 0.61): self.expire_seconds, self.rebound_threshold = expire_seconds, rebound_threshold self.data: dict[str, _Entry] = {}; self.lock = Lock() def triggered(self, tag: str, code: str, price: float, now: datetime | None = None) -> bool: if price <= 0: return False now = now or datetime.now() with self.lock: watch = self.data.get(code) if watch is None or now >= watch.expires_at: self.data[code] = _Entry(price, now + timedelta(seconds=self.expire_seconds)); return False if price < watch.last_close: self.data[code] = _Entry(price, now + timedelta(seconds=self.expire_seconds)); return False rebound = (price - watch.last_close) / watch.last_close * 100 if rebound <= 0 or rebound < self.rebound_threshold: return False del self.data[code] logging.info("[%s-触发] %s 反弹=%.2f%%", tag, code, rebound) return True def forget(self, code): with self.lock: self.data.pop(code, None)