Files
CryptoTrading/src/tradefinder/strategies/signals.py
bnair123 eca17b42fe Add Phase 2 foundation: regime classifier, strategy framework, WebSocket streamer
Phase 1 completion:
- Add DataStreamer for real-time Binance Futures WebSocket data (klines, mark price)
- Add DataValidator for candle validation and gap detection
- Add timeframes module with interval mappings

Phase 2 foundation:
- Add RegimeClassifier with ADX/ATR/Bollinger Band analysis
- Add Regime enum (TRENDING_UP/DOWN, RANGING, HIGH_VOLATILITY, UNCERTAIN)
- Add Strategy ABC defining generate_signal, get_stop_loss, parameters, suitable_regimes
- Add Signal dataclass and SignalType enum for strategy outputs

Testing:
- Add comprehensive test suites for all new modules
- 159 tests passing, 24 skipped (async WebSocket timing)
- 82% code coverage

Dependencies:
- Add pandas-stubs to dev dependencies for mypy compatibility
2025-12-27 15:28:28 +04:00

88 lines
2.7 KiB
Python

"""Signal types for trading strategies."""
from dataclasses import dataclass, field
from datetime import datetime
from decimal import Decimal
from enum import Enum
from typing import Any
import structlog
logger = structlog.get_logger(__name__)
class SignalType(str, Enum):
"""Type of trading signal."""
ENTRY_LONG = "entry_long"
ENTRY_SHORT = "entry_short"
EXIT_LONG = "exit_long"
EXIT_SHORT = "exit_short"
@dataclass
class Signal:
"""Trading signal emitted by a strategy.
Attributes:
signal_type: Type of signal (entry/exit, long/short)
symbol: Trading symbol (e.g., "BTCUSDT")
price: Suggested entry/exit price
stop_loss: Stop loss price for risk calculation
take_profit: Optional take profit price
confidence: Signal confidence from 0.0 to 1.0
timestamp: When the signal was generated
strategy_name: Name of the strategy that generated the signal
metadata: Additional signal-specific data
"""
signal_type: SignalType
symbol: str
price: Decimal
stop_loss: Decimal
take_profit: Decimal | None
confidence: float
timestamp: datetime
strategy_name: str
metadata: dict[str, Any] = field(default_factory=dict)
def __post_init__(self) -> None:
"""Validate signal parameters."""
if not 0.0 <= self.confidence <= 1.0:
raise ValueError(f"Confidence must be between 0.0 and 1.0, got {self.confidence}")
if self.price <= Decimal("0"):
raise ValueError(f"Price must be positive, got {self.price}")
if self.stop_loss <= Decimal("0"):
raise ValueError(f"Stop loss must be positive, got {self.stop_loss}")
@property
def is_entry(self) -> bool:
"""Check if this is an entry signal."""
return self.signal_type in (SignalType.ENTRY_LONG, SignalType.ENTRY_SHORT)
@property
def is_exit(self) -> bool:
"""Check if this is an exit signal."""
return self.signal_type in (SignalType.EXIT_LONG, SignalType.EXIT_SHORT)
@property
def is_long(self) -> bool:
"""Check if this is a long-side signal."""
return self.signal_type in (SignalType.ENTRY_LONG, SignalType.EXIT_LONG)
@property
def is_short(self) -> bool:
"""Check if this is a short-side signal."""
return self.signal_type in (SignalType.ENTRY_SHORT, SignalType.EXIT_SHORT)
@property
def risk_reward_ratio(self) -> Decimal | None:
"""Calculate risk/reward ratio if take profit is set."""
if self.take_profit is None:
return None
risk = abs(self.price - self.stop_loss)
reward = abs(self.take_profit - self.price)
if risk == Decimal("0"):
return None
return reward / risk