# Strategy Development Guide This guide explains how to create custom trading strategies using the IncrementalTrader framework. ## Overview IncrementalTrader strategies are built around the `IncStrategyBase` class, which provides a robust framework for incremental computation, timeframe aggregation, and signal generation. ## Basic Strategy Structure ```python from IncrementalTrader.strategies.base import IncStrategyBase, IncStrategySignal from IncrementalTrader.strategies.indicators import MovingAverageState class MyCustomStrategy(IncStrategyBase): def __init__(self, name: str, params: dict = None): super().__init__(name, params) # Initialize indicators self.sma_fast = MovingAverageState(period=self.params.get('fast_period', 10)) self.sma_slow = MovingAverageState(period=self.params.get('slow_period', 20)) # Strategy state self.current_signal = IncStrategySignal.HOLD() def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: """Process aggregated data and generate signals.""" open_price, high, low, close, volume = ohlcv # Update indicators self.sma_fast.update(close) self.sma_slow.update(close) # Generate signals if self.sma_fast.is_ready() and self.sma_slow.is_ready(): fast_sma = self.sma_fast.get_value() slow_sma = self.sma_slow.get_value() if fast_sma > slow_sma and self.current_signal.signal_type != 'BUY': self.current_signal = IncStrategySignal.BUY( confidence=0.8, metadata={'fast_sma': fast_sma, 'slow_sma': slow_sma} ) elif fast_sma < slow_sma and self.current_signal.signal_type != 'SELL': self.current_signal = IncStrategySignal.SELL( confidence=0.8, metadata={'fast_sma': fast_sma, 'slow_sma': slow_sma} ) return self.current_signal ``` ## Key Components ### 1. Base Class Inheritance All strategies must inherit from `IncStrategyBase`: ```python class MyStrategy(IncStrategyBase): def __init__(self, name: str, params: dict = None): super().__init__(name, params) # Your initialization code here ``` ### 2. Required Methods #### `_process_aggregated_data()` This is the core method where your strategy logic goes: ```python def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: """ Process aggregated OHLCV data and return a signal. Args: timestamp: Unix timestamp ohlcv: Tuple of (open, high, low, close, volume) Returns: IncStrategySignal: BUY, SELL, or HOLD signal """ # Your strategy logic here return signal ``` ### 3. Signal Generation Use the factory methods to create signals: ```python # Buy signal signal = IncStrategySignal.BUY( confidence=0.8, # Optional: 0.0 to 1.0 metadata={'reason': 'Golden cross detected'} # Optional: additional data ) # Sell signal signal = IncStrategySignal.SELL( confidence=0.9, metadata={'reason': 'Death cross detected'} ) # Hold signal signal = IncStrategySignal.HOLD() ``` ## Using Indicators ### Built-in Indicators IncrementalTrader provides many built-in indicators: ```python from IncrementalTrader.strategies.indicators import ( MovingAverageState, ExponentialMovingAverageState, ATRState, SupertrendState, RSIState, BollingerBandsState ) class MyStrategy(IncStrategyBase): def __init__(self, name: str, params: dict = None): super().__init__(name, params) # Moving averages self.sma = MovingAverageState(period=20) self.ema = ExponentialMovingAverageState(period=20, alpha=0.1) # Volatility self.atr = ATRState(period=14) # Trend self.supertrend = SupertrendState(period=10, multiplier=3.0) # Oscillators self.rsi = RSIState(period=14) self.bb = BollingerBandsState(period=20, std_dev=2.0) ``` ### Indicator Usage Pattern ```python def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: open_price, high, low, close, volume = ohlcv # Update indicators self.sma.update(close) self.rsi.update(close) self.atr.update_ohlc(high, low, close) # Check if indicators are ready if not (self.sma.is_ready() and self.rsi.is_ready()): return IncStrategySignal.HOLD() # Get indicator values sma_value = self.sma.get_value() rsi_value = self.rsi.get_value() atr_value = self.atr.get_value() # Your strategy logic here # ... ``` ## Advanced Features ### 1. Timeframe Aggregation The base class automatically handles timeframe aggregation: ```python class MyStrategy(IncStrategyBase): def __init__(self, name: str, params: dict = None): # Set timeframe in params default_params = {"timeframe": "15min"} if params: default_params.update(params) super().__init__(name, default_params) ``` Supported timeframes: - `"1min"`, `"5min"`, `"15min"`, `"30min"` - `"1h"`, `"4h"`, `"1d"` ### 2. State Management Track strategy state for complex logic: ```python class TrendFollowingStrategy(IncStrategyBase): def __init__(self, name: str, params: dict = None): super().__init__(name, params) # Strategy state self.trend_state = "UNKNOWN" # BULLISH, BEARISH, SIDEWAYS self.position_state = "NONE" # LONG, SHORT, NONE self.last_signal_time = 0 def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: # Update trend state self._update_trend_state(ohlcv) # Generate signals based on trend and position if self.trend_state == "BULLISH" and self.position_state != "LONG": self.position_state = "LONG" return IncStrategySignal.BUY(confidence=0.8) elif self.trend_state == "BEARISH" and self.position_state != "SHORT": self.position_state = "SHORT" return IncStrategySignal.SELL(confidence=0.8) return IncStrategySignal.HOLD() ``` ### 3. Multi-Indicator Strategies Combine multiple indicators for robust signals: ```python class MultiIndicatorStrategy(IncStrategyBase): def __init__(self, name: str, params: dict = None): super().__init__(name, params) # Trend indicators self.supertrend = SupertrendState(period=10, multiplier=3.0) self.sma_50 = MovingAverageState(period=50) self.sma_200 = MovingAverageState(period=200) # Momentum indicators self.rsi = RSIState(period=14) # Volatility indicators self.bb = BollingerBandsState(period=20, std_dev=2.0) def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: open_price, high, low, close, volume = ohlcv # Update all indicators self.supertrend.update_ohlc(high, low, close) self.sma_50.update(close) self.sma_200.update(close) self.rsi.update(close) self.bb.update(close) # Wait for all indicators to be ready if not all([ self.supertrend.is_ready(), self.sma_50.is_ready(), self.sma_200.is_ready(), self.rsi.is_ready(), self.bb.is_ready() ]): return IncStrategySignal.HOLD() # Get indicator values supertrend_signal = self.supertrend.get_signal() sma_50 = self.sma_50.get_value() sma_200 = self.sma_200.get_value() rsi = self.rsi.get_value() bb_upper, bb_middle, bb_lower = self.bb.get_bands() # Multi-condition buy signal buy_conditions = [ supertrend_signal == 'BUY', sma_50 > sma_200, # Long-term uptrend rsi < 70, # Not overbought close < bb_upper # Not at upper band ] # Multi-condition sell signal sell_conditions = [ supertrend_signal == 'SELL', sma_50 < sma_200, # Long-term downtrend rsi > 30, # Not oversold close > bb_lower # Not at lower band ] if all(buy_conditions): confidence = sum([1 for c in buy_conditions if c]) / len(buy_conditions) return IncStrategySignal.BUY( confidence=confidence, metadata={ 'supertrend': supertrend_signal, 'sma_trend': 'UP' if sma_50 > sma_200 else 'DOWN', 'rsi': rsi, 'bb_position': 'MIDDLE' } ) elif all(sell_conditions): confidence = sum([1 for c in sell_conditions if c]) / len(sell_conditions) return IncStrategySignal.SELL( confidence=confidence, metadata={ 'supertrend': supertrend_signal, 'sma_trend': 'DOWN' if sma_50 < sma_200 else 'UP', 'rsi': rsi, 'bb_position': 'MIDDLE' } ) return IncStrategySignal.HOLD() ``` ## Parameter Management ### Default Parameters Define default parameters in your strategy: ```python class MyStrategy(IncStrategyBase): def __init__(self, name: str, params: dict = None): # Define defaults default_params = { "timeframe": "15min", "fast_period": 10, "slow_period": 20, "rsi_period": 14, "rsi_overbought": 70, "rsi_oversold": 30 } # Merge with provided params if params: default_params.update(params) super().__init__(name, default_params) # Use parameters self.fast_sma = MovingAverageState(period=self.params['fast_period']) self.slow_sma = MovingAverageState(period=self.params['slow_period']) self.rsi = RSIState(period=self.params['rsi_period']) ``` ### Parameter Validation Add validation for critical parameters: ```python def __init__(self, name: str, params: dict = None): super().__init__(name, params) # Validate parameters if self.params['fast_period'] >= self.params['slow_period']: raise ValueError("fast_period must be less than slow_period") if not (1 <= self.params['rsi_period'] <= 100): raise ValueError("rsi_period must be between 1 and 100") ``` ## Testing Your Strategy ### Unit Testing ```python import unittest from IncrementalTrader.strategies.base import IncStrategySignal class TestMyStrategy(unittest.TestCase): def setUp(self): self.strategy = MyCustomStrategy("test", { "fast_period": 5, "slow_period": 10 }) def test_initialization(self): self.assertEqual(self.strategy.name, "test") self.assertEqual(self.strategy.params['fast_period'], 5) def test_signal_generation(self): # Feed test data test_data = [ (1000, (100, 105, 95, 102, 1000)), (1001, (102, 108, 100, 106, 1200)), # ... more test data ] for timestamp, ohlcv in test_data: signal = self.strategy.process_data_point(timestamp, ohlcv) self.assertIsInstance(signal, IncStrategySignal) ``` ### Backtesting ```python from IncrementalTrader import IncBacktester, BacktestConfig # Test your strategy config = BacktestConfig( initial_usd=10000, start_date="2024-01-01", end_date="2024-03-31" ) backtester = IncBacktester() results = backtester.run_single_strategy( strategy_class=MyCustomStrategy, strategy_params={"fast_period": 10, "slow_period": 20}, config=config, data_file="test_data.csv" ) print(f"Total Return: {results['performance_metrics']['total_return_pct']:.2f}%") ``` ## Best Practices ### 1. Incremental Design Always design for incremental computation: ```python # Good: Incremental calculation class IncrementalSMA: def __init__(self, period): self.period = period self.values = deque(maxlen=period) self.sum = 0 def update(self, value): if len(self.values) == self.period: self.sum -= self.values[0] self.values.append(value) self.sum += value def get_value(self): return self.sum / len(self.values) if self.values else 0 # Bad: Batch calculation def calculate_sma(prices, period): return [sum(prices[i:i+period])/period for i in range(len(prices)-period+1)] ``` ### 2. State Management Keep minimal state and ensure it's always consistent: ```python def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: # Update all indicators first self._update_indicators(ohlcv) # Then update strategy state self._update_strategy_state() # Finally generate signal return self._generate_signal() ``` ### 3. Error Handling Handle edge cases gracefully: ```python def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: try: open_price, high, low, close, volume = ohlcv # Validate data if not all(isinstance(x, (int, float)) for x in ohlcv): self.logger.warning(f"Invalid OHLCV data: {ohlcv}") return IncStrategySignal.HOLD() if high < low or close < 0: self.logger.warning(f"Inconsistent price data: {ohlcv}") return IncStrategySignal.HOLD() # Your strategy logic here # ... except Exception as e: self.logger.error(f"Error processing data: {e}") return IncStrategySignal.HOLD() ``` ### 4. Logging Use the built-in logger for debugging: ```python def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: open_price, high, low, close, volume = ohlcv # Log important events if self.sma_fast.get_value() > self.sma_slow.get_value(): self.logger.debug(f"Fast SMA ({self.sma_fast.get_value():.2f}) > Slow SMA ({self.sma_slow.get_value():.2f})") # Log signal generation if signal.signal_type != 'HOLD': self.logger.info(f"Generated {signal.signal_type} signal with confidence {signal.confidence}") return signal ``` ## Example Strategies ### Simple Moving Average Crossover ```python class SMAStrategy(IncStrategyBase): def __init__(self, name: str, params: dict = None): default_params = { "timeframe": "15min", "fast_period": 10, "slow_period": 20 } if params: default_params.update(params) super().__init__(name, default_params) self.sma_fast = MovingAverageState(period=self.params['fast_period']) self.sma_slow = MovingAverageState(period=self.params['slow_period']) self.last_signal = 'HOLD' def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: _, _, _, close, _ = ohlcv self.sma_fast.update(close) self.sma_slow.update(close) if not (self.sma_fast.is_ready() and self.sma_slow.is_ready()): return IncStrategySignal.HOLD() fast = self.sma_fast.get_value() slow = self.sma_slow.get_value() if fast > slow and self.last_signal != 'BUY': self.last_signal = 'BUY' return IncStrategySignal.BUY(confidence=0.7) elif fast < slow and self.last_signal != 'SELL': self.last_signal = 'SELL' return IncStrategySignal.SELL(confidence=0.7) return IncStrategySignal.HOLD() ``` ### RSI Mean Reversion ```python class RSIMeanReversionStrategy(IncStrategyBase): def __init__(self, name: str, params: dict = None): default_params = { "timeframe": "15min", "rsi_period": 14, "oversold": 30, "overbought": 70 } if params: default_params.update(params) super().__init__(name, default_params) self.rsi = RSIState(period=self.params['rsi_period']) def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: _, _, _, close, _ = ohlcv self.rsi.update(close) if not self.rsi.is_ready(): return IncStrategySignal.HOLD() rsi_value = self.rsi.get_value() if rsi_value < self.params['oversold']: return IncStrategySignal.BUY( confidence=min(1.0, (self.params['oversold'] - rsi_value) / 20), metadata={'rsi': rsi_value, 'condition': 'oversold'} ) elif rsi_value > self.params['overbought']: return IncStrategySignal.SELL( confidence=min(1.0, (rsi_value - self.params['overbought']) / 20), metadata={'rsi': rsi_value, 'condition': 'overbought'} ) return IncStrategySignal.HOLD() ``` This guide provides a comprehensive foundation for developing custom strategies with IncrementalTrader. Remember to always test your strategies thoroughly before using them in live trading!