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!