chart/app/analysis/alerts.py

55 lines
2 KiB
Python

from dataclasses import dataclass
from app.analysis.confluence import Cluster
@dataclass(slots=True)
class Alert:
cluster: Cluster
message: str
class AlertEngine:
def __init__(self, min_score: float, cooldown_seconds: int = 900):
self.min_score = min_score
self.cooldown_seconds = cooldown_seconds
self._fired_at: dict[str, int] = {}
def evaluate(
self,
clusters: list[Cluster],
current_price: float,
atr15: float,
now: int,
symbol: str,
) -> list[Alert]:
tolerance = 0.5 * atr15
if tolerance <= 0:
return []
alerts: list[Alert] = []
active_ids = {cluster.id for cluster in clusters}
for cluster_id, fired_at in list(self._fired_at.items()):
cluster = next((item for item in clusters if item.id == cluster_id), None)
separated = cluster is None or abs(cluster.center - current_price) > 2 * tolerance
if separated and now - fired_at >= self.cooldown_seconds:
del self._fired_at[cluster_id]
elif cluster_id not in active_ids and now - fired_at >= self.cooldown_seconds:
del self._fired_at[cluster_id]
for cluster in clusters:
if (
cluster.score < self.min_score
or abs(cluster.center - current_price) > tolerance
or cluster.id in self._fired_at
):
continue
self._fired_at[cluster.id] = now
direction = "BEARISH" if cluster.side.value == "resistance" else "BULLISH"
timeframes = ", ".join(dict.fromkeys(member.tf.value for member in cluster.members))
message = (
f"{direction} ZONE {symbol} {current_price:.2f}\n"
f"{cluster.side.value.title()} confluence {cluster.score:g} "
f"@ {cluster.low:.2f}-{cluster.high:.2f}\n{timeframes}"
)
alerts.append(Alert(cluster, message))
return alerts