TradingAgents/tradingagents/strategies/event_driven.py

80 lines
2.8 KiB
Python

"""Event-Driven strategy signal (§5.1 — Event-Driven / Earnings & Dividend Proximity).
Flags proximity to upcoming earnings or ex-dividend dates as event catalysts.
Reference:
Kakushadze & Serur, "151 Trading Strategies", §5.1
"""
from __future__ import annotations
from datetime import datetime
from typing import Any
from .base import BaseStrategy, StrategySignal
from ._data import get_info
class EventDrivenStrategy(BaseStrategy):
name = "Event-Driven (§5.1)"
roles = ["fundamentals", "news", "researcher"]
def compute(self, ticker: str, date: str, context: dict[str, Any] | None = None) -> StrategySignal | None:
info = get_info(ticker, context)
if not info:
return None
try:
ref = datetime.strptime(date, "%Y-%m-%d")
except ValueError:
return None
events: list[str] = []
days_to_event: int | None = None
# Check earnings date proximity
for key in ("earningsDate", "nextEarningsDate"):
raw = info.get(key)
if raw is None:
continue
# yfinance may return a timestamp or list
if isinstance(raw, (list, tuple)) and raw:
raw = raw[0]
try:
dt = datetime.fromtimestamp(int(raw)) if isinstance(raw, (int, float)) else datetime.strptime(str(raw)[:10], "%Y-%m-%d")
delta = (dt - ref).days
if 0 <= delta <= 30:
events.append(f"earnings in {delta}d")
days_to_event = min(days_to_event, delta) if days_to_event is not None else delta
except Exception:
continue
# Check ex-dividend date
ex_div = info.get("exDividendDate")
if ex_div:
try:
dt = datetime.fromtimestamp(int(ex_div)) if isinstance(ex_div, (int, float)) else datetime.strptime(str(ex_div)[:10], "%Y-%m-%d")
delta = (dt - ref).days
if 0 <= delta <= 30:
events.append(f"ex-div in {delta}d")
days_to_event = min(days_to_event, delta) if days_to_event is not None else delta
except Exception:
pass
if not events:
return None
# Closer event → stronger signal (event risk / catalyst)
# Neutral direction — events are catalysts, not directional
proximity = max(0.0, 1.0 - (days_to_event or 30) / 30.0)
strength = round(proximity * 0.5, 4) # cap at 0.5 — events are informational
return StrategySignal(
name=self.name,
ticker=ticker,
date=date,
signal_strength=strength,
direction="neutral",
detail=f"Upcoming: {', '.join(events)}",
)