2025-05-28 22:37:53 +08:00

17 KiB

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

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:

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:

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:

# 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:

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

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:

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:

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:

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:

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:

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

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

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:

# 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:

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:

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:

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

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

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!