""" Incremental Random Strategy for Testing This strategy generates random entry and exit signals for testing the incremental strategy system. It's useful for verifying that the incremental strategy framework is working correctly. """ import random import logging import time from typing import Dict, Optional import pandas as pd from .base import IncStrategyBase, IncStrategySignal logger = logging.getLogger(__name__) class IncRandomStrategy(IncStrategyBase): """ Incremental random signal generator strategy for testing. This strategy generates random entry and exit signals with configurable probability and confidence levels. It's designed to test the incremental strategy framework and signal processing system. The incremental version maintains minimal state and processes each new data point independently, making it ideal for testing real-time performance. Parameters: entry_probability: Probability of generating an entry signal (0.0-1.0) exit_probability: Probability of generating an exit signal (0.0-1.0) min_confidence: Minimum confidence level for signals max_confidence: Maximum confidence level for signals timeframe: Timeframe to operate on (default: "1min") signal_frequency: How often to generate signals (every N bars) random_seed: Optional seed for reproducible random signals Example: strategy = IncRandomStrategy( weight=1.0, params={ "entry_probability": 0.1, "exit_probability": 0.15, "min_confidence": 0.7, "max_confidence": 0.9, "signal_frequency": 5, "random_seed": 42 # For reproducible testing } ) """ def __init__(self, weight: float = 1.0, params: Optional[Dict] = None): """Initialize the incremental random strategy.""" super().__init__("inc_random", weight, params) # Strategy parameters with defaults self.entry_probability = self.params.get("entry_probability", 0.05) # 5% chance per bar self.exit_probability = self.params.get("exit_probability", 0.1) # 10% chance per bar self.min_confidence = self.params.get("min_confidence", 0.6) self.max_confidence = self.params.get("max_confidence", 0.9) self.timeframe = self.params.get("timeframe", "1min") self.signal_frequency = self.params.get("signal_frequency", 1) # Every bar # Create separate random instance for this strategy self._random = random.Random() random_seed = self.params.get("random_seed") if random_seed is not None: self._random.seed(random_seed) logger.info(f"IncRandomStrategy: Set random seed to {random_seed}") # Internal state (minimal for random strategy) self._bar_count = 0 self._last_signal_bar = -1 self._current_price = None self._last_timestamp = None logger.info(f"IncRandomStrategy initialized with entry_prob={self.entry_probability}, " f"exit_prob={self.exit_probability}, timeframe={self.timeframe}, " f"aggregation_enabled={self._timeframe_aggregator is not None}") def get_minimum_buffer_size(self) -> Dict[str, int]: """ Return minimum data points needed for each timeframe. Random strategy doesn't need any historical data for calculations, so we only need 1 data point to start generating signals. With the new base class timeframe aggregation, we only specify our primary timeframe. Returns: Dict[str, int]: Minimal buffer requirements """ return {self.timeframe: 1} # Only need current data point def supports_incremental_calculation(self) -> bool: """ Whether strategy supports incremental calculation. Random strategy is ideal for incremental mode since it doesn't depend on historical calculations. Returns: bool: Always True for random strategy """ return True def calculate_on_data(self, new_data_point: Dict[str, float], timestamp: pd.Timestamp) -> None: """ Process a single new data point incrementally. For random strategy, we just update our internal state with the current price. The base class now handles timeframe aggregation automatically, so we only receive data when a complete timeframe bar is formed. Args: new_data_point: OHLCV data point {open, high, low, close, volume} timestamp: Timestamp of the data point """ start_time = time.perf_counter() try: # Update internal state - base class handles timeframe aggregation self._current_price = new_data_point['close'] self._last_timestamp = timestamp self._data_points_received += 1 # Increment bar count for each processed timeframe bar self._bar_count += 1 # Debug logging every 10 bars if self._bar_count % 10 == 0: logger.debug(f"IncRandomStrategy: Processing bar {self._bar_count}, " f"price=${self._current_price:.2f}, timestamp={timestamp}") # Update warm-up status if not self._is_warmed_up and self._data_points_received >= 1: self._is_warmed_up = True self._calculation_mode = "incremental" logger.info(f"IncRandomStrategy: Warmed up after {self._data_points_received} data points") # Record performance metrics update_time = time.perf_counter() - start_time self._performance_metrics['update_times'].append(update_time) except Exception as e: logger.error(f"IncRandomStrategy: Error in calculate_on_data: {e}") self._performance_metrics['state_validation_failures'] += 1 raise def get_entry_signal(self) -> IncStrategySignal: """ Generate random entry signals based on current state. Returns: IncStrategySignal: Entry signal with confidence level """ if not self._is_warmed_up: return IncStrategySignal("HOLD", 0.0) start_time = time.perf_counter() try: # Check if we should generate a signal based on frequency if (self._bar_count - self._last_signal_bar) < self.signal_frequency: return IncStrategySignal("HOLD", 0.0) # Generate random entry signal using strategy's random instance random_value = self._random.random() if random_value < self.entry_probability: confidence = self._random.uniform(self.min_confidence, self.max_confidence) self._last_signal_bar = self._bar_count logger.info(f"IncRandomStrategy: Generated ENTRY signal at bar {self._bar_count}, " f"price=${self._current_price:.2f}, confidence={confidence:.2f}, " f"random_value={random_value:.3f}") signal = IncStrategySignal( "ENTRY", confidence=confidence, price=self._current_price, metadata={ "strategy": "inc_random", "bar_count": self._bar_count, "timeframe": self.timeframe, "random_value": random_value, "timestamp": self._last_timestamp } ) # Record performance metrics signal_time = time.perf_counter() - start_time self._performance_metrics['signal_generation_times'].append(signal_time) return signal return IncStrategySignal("HOLD", 0.0) except Exception as e: logger.error(f"IncRandomStrategy: Error in get_entry_signal: {e}") return IncStrategySignal("HOLD", 0.0) def get_exit_signal(self) -> IncStrategySignal: """ Generate random exit signals based on current state. Returns: IncStrategySignal: Exit signal with confidence level """ if not self._is_warmed_up: return IncStrategySignal("HOLD", 0.0) start_time = time.perf_counter() try: # Generate random exit signal using strategy's random instance random_value = self._random.random() if random_value < self.exit_probability: confidence = self._random.uniform(self.min_confidence, self.max_confidence) # Randomly choose exit type exit_types = ["SELL_SIGNAL", "TAKE_PROFIT", "STOP_LOSS"] exit_type = self._random.choice(exit_types) logger.info(f"IncRandomStrategy: Generated EXIT signal at bar {self._bar_count}, " f"price=${self._current_price:.2f}, confidence={confidence:.2f}, " f"type={exit_type}, random_value={random_value:.3f}") signal = IncStrategySignal( "EXIT", confidence=confidence, price=self._current_price, metadata={ "type": exit_type, "strategy": "inc_random", "bar_count": self._bar_count, "timeframe": self.timeframe, "random_value": random_value, "timestamp": self._last_timestamp } ) # Record performance metrics signal_time = time.perf_counter() - start_time self._performance_metrics['signal_generation_times'].append(signal_time) return signal return IncStrategySignal("HOLD", 0.0) except Exception as e: logger.error(f"IncRandomStrategy: Error in get_exit_signal: {e}") return IncStrategySignal("HOLD", 0.0) def get_confidence(self) -> float: """ Return random confidence level for current market state. Returns: float: Random confidence level between min and max confidence """ if not self._is_warmed_up: return 0.0 return self._random.uniform(self.min_confidence, self.max_confidence) def reset_calculation_state(self) -> None: """Reset internal calculation state for reinitialization.""" super().reset_calculation_state() # Reset random strategy specific state self._bar_count = 0 self._last_signal_bar = -1 self._current_price = None self._last_timestamp = None # Reset random state if seed was provided random_seed = self.params.get("random_seed") if random_seed is not None: self._random.seed(random_seed) logger.info("IncRandomStrategy: Calculation state reset") def _reinitialize_from_buffers(self) -> None: """ Reinitialize indicators from available buffer data. For random strategy, we just need to restore the current price from the latest data point in the buffer. """ try: # Get the latest data point from 1min buffer buffer_1min = self._timeframe_buffers.get("1min") if buffer_1min and len(buffer_1min) > 0: latest_data = buffer_1min[-1] self._current_price = latest_data['close'] self._last_timestamp = latest_data.get('timestamp') self._bar_count = len(buffer_1min) logger.info(f"IncRandomStrategy: Reinitialized from buffer with {self._bar_count} bars") else: logger.warning("IncRandomStrategy: No buffer data available for reinitialization") except Exception as e: logger.error(f"IncRandomStrategy: Error reinitializing from buffers: {e}") raise def get_current_state_summary(self) -> Dict[str, any]: """Get summary of current calculation state for debugging.""" base_summary = super().get_current_state_summary() base_summary.update({ 'entry_probability': self.entry_probability, 'exit_probability': self.exit_probability, 'bar_count': self._bar_count, 'last_signal_bar': self._last_signal_bar, 'current_price': self._current_price, 'last_timestamp': self._last_timestamp, 'signal_frequency': self.signal_frequency, 'timeframe': self.timeframe }) return base_summary def __repr__(self) -> str: """String representation of the strategy.""" return (f"IncRandomStrategy(entry_prob={self.entry_probability}, " f"exit_prob={self.exit_probability}, timeframe={self.timeframe}, " f"mode={self._calculation_mode}, warmed_up={self._is_warmed_up}, " f"bars={self._bar_count})")