234 lines
7.1 KiB
Markdown
234 lines
7.1 KiB
Markdown
|
|
# Creating Custom Strategies
|
||
|
|
|
||
|
|
This guide explains how to create custom trading strategies for Crypto Trader.
|
||
|
|
|
||
|
|
## Strategy Framework
|
||
|
|
|
||
|
|
All strategies inherit from `BaseStrategy` and implement a standardized interface. This allows the trading engine to execute any strategy uniformly.
|
||
|
|
|
||
|
|
## Basic Strategy Structure
|
||
|
|
|
||
|
|
```python
|
||
|
|
from src.strategies.base import BaseStrategy, StrategyRegistry
|
||
|
|
from src.data.indicators import get_indicators
|
||
|
|
import pandas as pd
|
||
|
|
from typing import Dict, Any
|
||
|
|
|
||
|
|
class MyCustomStrategy(BaseStrategy):
|
||
|
|
"""My custom trading strategy."""
|
||
|
|
|
||
|
|
def __init__(self, strategy_id: int, name: str, symbol: str, timeframe: str, parameters: Dict[str, Any]):
|
||
|
|
super().__init__(strategy_id, name, symbol, timeframe, parameters)
|
||
|
|
# Initialize strategy-specific parameters
|
||
|
|
self.my_param = parameters.get("my_param", 10)
|
||
|
|
|
||
|
|
async def on_data(self, new_data: pd.DataFrame):
|
||
|
|
"""Called when new data is available."""
|
||
|
|
# Update internal data
|
||
|
|
self.current_data = pd.concat([self.current_data, new_data])
|
||
|
|
|
||
|
|
# Generate signal if enough data
|
||
|
|
if len(self.current_data) >= self.my_param:
|
||
|
|
signal = await self.generate_signal()
|
||
|
|
if signal["signal"] != "hold":
|
||
|
|
# Signal generated - will be handled by trading engine
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def generate_signal(self) -> Dict[str, Any]:
|
||
|
|
"""Generate trading signal."""
|
||
|
|
# Calculate indicators
|
||
|
|
indicators = get_indicators()
|
||
|
|
rsi = indicators.rsi(self.current_data['close'], period=14)
|
||
|
|
|
||
|
|
# Generate signal based on strategy logic
|
||
|
|
current_rsi = rsi.iloc[-1]
|
||
|
|
current_price = self.current_data['close'].iloc[-1]
|
||
|
|
|
||
|
|
if current_rsi < 30:
|
||
|
|
return {"signal": "buy", "price": current_price}
|
||
|
|
elif current_rsi > 70:
|
||
|
|
return {"signal": "sell", "price": current_price}
|
||
|
|
else:
|
||
|
|
return {"signal": "hold", "price": current_price}
|
||
|
|
|
||
|
|
async def calculate_position_size(self, capital: float, risk_percentage: float) -> float:
|
||
|
|
"""Calculate position size."""
|
||
|
|
return capital * risk_percentage
|
||
|
|
|
||
|
|
# Register strategy
|
||
|
|
StrategyRegistry.register_strategy("my_custom", MyCustomStrategy)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Required Methods
|
||
|
|
|
||
|
|
### `on_data(new_data: pd.DataFrame)`
|
||
|
|
|
||
|
|
Called when new OHLCV data is available for the strategy's timeframe.
|
||
|
|
|
||
|
|
**Responsibilities**:
|
||
|
|
- Update internal data storage
|
||
|
|
- Calculate indicators
|
||
|
|
- Generate signals when conditions are met
|
||
|
|
|
||
|
|
### `generate_signal() -> Dict[str, Any]`
|
||
|
|
|
||
|
|
Analyzes current data and generates a trading signal.
|
||
|
|
|
||
|
|
**Returns**:
|
||
|
|
```python
|
||
|
|
{
|
||
|
|
"signal": "buy" | "sell" | "hold",
|
||
|
|
"price": float, # Optional, current price
|
||
|
|
"confidence": float, # Optional, 0.0 to 1.0
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### `calculate_position_size(capital: float, risk_percentage: float) -> float`
|
||
|
|
|
||
|
|
Calculates the appropriate position size based on capital and risk.
|
||
|
|
|
||
|
|
**Parameters**:
|
||
|
|
- `capital`: Available trading capital
|
||
|
|
- `risk_percentage`: Percentage of capital to risk (0.0 to 1.0)
|
||
|
|
|
||
|
|
**Returns**: Position size in base currency
|
||
|
|
|
||
|
|
## Using Technical Indicators
|
||
|
|
|
||
|
|
Access indicators through the indicators library:
|
||
|
|
|
||
|
|
```python
|
||
|
|
from src.data.indicators import get_indicators
|
||
|
|
|
||
|
|
indicators = get_indicators()
|
||
|
|
|
||
|
|
# Calculate indicators
|
||
|
|
sma = indicators.sma(data['close'], period=20)
|
||
|
|
ema = indicators.ema(data['close'], period=20)
|
||
|
|
rsi = indicators.rsi(data['close'], period=14)
|
||
|
|
macd_result = indicators.macd(data['close'], fast=12, slow=26, signal=9)
|
||
|
|
bbands = indicators.bollinger_bands(data['close'], period=20, std_dev=2)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Multi-Timeframe Strategies
|
||
|
|
|
||
|
|
Strategies can use multiple timeframes:
|
||
|
|
|
||
|
|
```python
|
||
|
|
from src.strategies.timeframe_manager import get_timeframe_manager
|
||
|
|
|
||
|
|
timeframe_manager = get_timeframe_manager()
|
||
|
|
|
||
|
|
# Get data from different timeframes
|
||
|
|
primary_data = timeframe_manager.get_data(self.symbol, self.timeframe)
|
||
|
|
higher_tf_data = timeframe_manager.get_data(self.symbol, "1d") # Daily for trend
|
||
|
|
|
||
|
|
# Use higher timeframe for trend confirmation
|
||
|
|
if higher_tf_data is not None and len(higher_tf_data) > 0:
|
||
|
|
daily_trend = higher_tf_data['close'].iloc[-1] > higher_tf_data['close'].iloc[-20]
|
||
|
|
if daily_trend:
|
||
|
|
# Only trade in direction of higher timeframe trend
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
## Strategy Parameters
|
||
|
|
|
||
|
|
Define configurable parameters:
|
||
|
|
|
||
|
|
```python
|
||
|
|
def __init__(self, strategy_id: int, name: str, symbol: str, timeframe: str, parameters: Dict[str, Any]):
|
||
|
|
super().__init__(strategy_id, name, symbol, timeframe, parameters)
|
||
|
|
|
||
|
|
# Required parameters with defaults
|
||
|
|
self.rsi_period = parameters.get("rsi_period", 14)
|
||
|
|
self.overbought = parameters.get("overbought", 70)
|
||
|
|
self.oversold = parameters.get("oversold", 30)
|
||
|
|
|
||
|
|
# Validate parameters
|
||
|
|
if not (0 < self.oversold < self.overbought < 100):
|
||
|
|
raise ValueError("Invalid RSI thresholds")
|
||
|
|
```
|
||
|
|
|
||
|
|
## Strategy Registration
|
||
|
|
|
||
|
|
Register your strategy so it can be used:
|
||
|
|
|
||
|
|
```python
|
||
|
|
from src.strategies.base import StrategyRegistry
|
||
|
|
|
||
|
|
StrategyRegistry.register_strategy("my_strategy", MyCustomStrategy)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Strategy Lifecycle
|
||
|
|
|
||
|
|
1. **Initialization**: Strategy is created with parameters
|
||
|
|
2. **Start**: `start()` is called when strategy is activated
|
||
|
|
3. **Data Updates**: `on_data()` is called with new candles
|
||
|
|
4. **Signal Generation**: `generate_signal()` is called when conditions are met
|
||
|
|
5. **Order Execution**: Trading engine executes signals
|
||
|
|
6. **Stop**: `stop()` is called when strategy is deactivated
|
||
|
|
|
||
|
|
## Best Practices
|
||
|
|
|
||
|
|
1. **Parameter Validation**: Validate all parameters in `__init__`
|
||
|
|
2. **Error Handling**: Handle missing data and calculation errors
|
||
|
|
3. **Logging**: Use strategy logger for important events
|
||
|
|
4. **Testing**: Write unit tests for your strategy
|
||
|
|
5. **Documentation**: Document strategy logic and parameters
|
||
|
|
6. **Backtesting**: Always backtest before live trading
|
||
|
|
|
||
|
|
## Example: Complete Strategy
|
||
|
|
|
||
|
|
See `src/strategies/technical/rsi_strategy.py` for a complete example.
|
||
|
|
|
||
|
|
## Available Strategy Examples
|
||
|
|
|
||
|
|
The codebase includes several advanced strategy implementations that serve as examples:
|
||
|
|
|
||
|
|
### Multi-Indicator Confirmation
|
||
|
|
|
||
|
|
See `src/strategies/technical/confirmed_strategy.py` for an example of combining multiple indicators.
|
||
|
|
|
||
|
|
### Divergence Detection
|
||
|
|
|
||
|
|
See `src/strategies/technical/divergence_strategy.py` for an example of divergence detection using the indicators API.
|
||
|
|
|
||
|
|
### Ensemble Methods
|
||
|
|
|
||
|
|
See `src/strategies/ensemble/consensus_strategy.py` for an example of combining multiple strategies with weighted voting.
|
||
|
|
|
||
|
|
### Trend Filtering
|
||
|
|
|
||
|
|
All strategies can use the optional trend filter method from BaseStrategy:
|
||
|
|
|
||
|
|
```python
|
||
|
|
signal = strategy.apply_trend_filter(signal, ohlcv_data, adx_period=14, min_adx=25.0)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Testing Your Strategy
|
||
|
|
|
||
|
|
```python
|
||
|
|
import pytest
|
||
|
|
from src.strategies.my_strategy import MyCustomStrategy
|
||
|
|
|
||
|
|
def test_strategy_signal_generation():
|
||
|
|
"""Test strategy signal generation."""
|
||
|
|
strategy = MyCustomStrategy(
|
||
|
|
strategy_id=1,
|
||
|
|
name="Test Strategy",
|
||
|
|
symbol="BTC/USD",
|
||
|
|
timeframe="1h",
|
||
|
|
parameters={"rsi_period": 14}
|
||
|
|
)
|
||
|
|
|
||
|
|
# Create test data
|
||
|
|
test_data = pd.DataFrame({
|
||
|
|
'close': [100, 101, 102, 103, 104]
|
||
|
|
})
|
||
|
|
|
||
|
|
# Test signal generation
|
||
|
|
signal = await strategy.generate_signal()
|
||
|
|
assert signal["signal"] in ["buy", "sell", "hold"]
|
||
|
|
```
|
||
|
|
|