"""反向盤口移動(RLM)偵測模組。""" from __future__ import annotations from dataclasses import dataclass from datetime import datetime @dataclass(frozen=True) class ReverseLineMovementAlert: match_id: str market_type: str selection: str opening_odds: float current_odds: float ticket_pct: float handle_pct: float odds_change_pct: float smart_money_to: str is_triggered: bool triggered_at: datetime rationale: str def evaluate_reverse_line_movement( match_id: str, market_type: str, selection: str, *, opening_odds: float, current_odds: float, ticket_pct: float, handle_pct: float, ticket_threshold: float = 70.0, odds_change_threshold: float = 0.05, ) -> ReverseLineMovementAlert: """依條件判斷是否出現反向盤口。""" if opening_odds <= 0: odds_pct = 0.0 else: odds_pct = round((current_odds - opening_odds) / opening_odds, 6) is_triggered = ( ticket_pct > ticket_threshold and odds_pct > odds_change_threshold and handle_pct < ticket_pct ) smart_money_to = selection if handle_pct > ticket_pct else '對側' rationale = ( f'散戶 {ticket_pct:.1f}% 追捧卻資金 {handle_pct:.1f}%,\n' f'盤口由 {opening_odds:.2f} 上升到 {current_odds:.2f}' ) return ReverseLineMovementAlert( match_id=match_id, market_type=market_type, selection=selection, opening_odds=opening_odds, current_odds=current_odds, ticket_pct=ticket_pct, handle_pct=handle_pct, odds_change_pct=round(odds_pct * 100, 4), smart_money_to=smart_money_to, is_triggered=is_triggered, triggered_at=datetime.utcnow(), rationale=rationale, )