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
88 lines
2.7 KiB
Python
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
|