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
This commit is contained in:
bnair123
2025-12-27 15:28:28 +04:00
parent 7d63e43b7b
commit eca17b42fe
15 changed files with 2579 additions and 1 deletions

View File

@@ -0,0 +1,87 @@
"""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