Simple Reliable Strategy - Phase 3 - Uses simple technical indicators: RSI + BB + Volume + Trend - No HMM complexity, proven indicators only - Target: WinRate > 50%, Sharpe > 1.5, MDD < 0.5%, NumTrades: 10-20/week
Timeframe
5m
Direction
Long Only
Stoploss
-2.0%
Trailing Stop
No
ROI
0m: 1.5%, 20m: 1.0%, 40m: 0.5%, 80m: 0.2%
Interface Version
3
Startup Candles
200
Indicators
6
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
Simple Reliable Strategy - Phase 3 Implementation
- Uses simple technical indicators: RSI + BB + Volume + Trend
- No HMM complexity, proven indicators only
- Target: WinRate > 50%, Sharpe > 1.5, MDD < 0.5%, NumTrades: 10-20/week
"""
from pathlib import Path
import sys
# Add project root and src to path
root_path = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(root_path))
sys.path.insert(0, str(root_path / 'src'))
from datetime import datetime
from typing import Optional
import numpy as np
import pandas as pd
from pandas import DataFrame
from freqtrade.strategy import IStrategy
import talib.abstract as ta
class SimpleReliableStrategy(IStrategy):
"""
Simple Reliable Strategy - Phase 3
- Uses simple technical indicators: RSI + BB + Volume + Trend
- No HMM complexity, proven indicators only
- Target: WinRate > 50%, Sharpe > 1.5, MDD < 0.5%, NumTrades: 10-20/week
"""
INTERFACE_VERSION = 3
timeframe = '5m'
process_only_new_candles = True
startup_candle_count = 200
can_short = False
# Conservative ROI
minimal_roi = {"0": 0.015, "20": 0.01, "40": 0.005, "80": 0.002}
# Dynamic stoploss
stoploss = -0.02
# Position sizing
position_adjustment_enable = True
max_entry_position_adjustment = 2
def __init__(self, config: dict) -> None:
super().__init__(config)
def populate_indicators(self, df: DataFrame, metadata: dict) -> DataFrame:
# Basic technical indicators
df['ema20'] = ta.EMA(df, timeperiod=20)
df['ema50'] = ta.EMA(df, timeperiod=50)
df['ema100'] = ta.EMA(df, timeperiod=100)
df['rsi'] = ta.RSI(df, timeperiod=14)
df['adx'] = ta.ADX(df, timeperiod=14)
df['atr'] = ta.ATR(df, timeperiod=14)
# Bollinger Bands
bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
df['bb_upper'] = bb['upperband']
df['bb_middle'] = bb['middleband']
df['bb_lower'] = bb['lowerband']
df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['bb_middle']
df['bb_position'] = (df['close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'])
# MACD
macd = ta.MACD(df)
df['macd'] = macd['macd']
df['macd_signal'] = macd['macdsignal']
df['macd_hist'] = macd['macdhist']
# Volume indicators
df['volume_sma'] = df['volume'].rolling(20).mean()
df['volume_ratio'] = df['volume'] / df['volume_sma']
df['volume_trend'] = df['volume'].rolling(5).mean() / df['volume'].rolling(20).mean()
# Price momentum
df['price_change_5'] = df['close'].pct_change(5)
df['price_change_10'] = df['close'].pct_change(10)
df['price_change_20'] = df['close'].pct_change(20)
# Trend strength
df['trend_strength'] = (df['ema20'] - df['ema50']) / df['ema50']
df['trend_direction'] = np.where(df['ema20'] > df['ema50'], 1, -1)
# Volatility
df['volatility'] = df['price_change_5'].rolling(20).std()
df['volatility_percentile'] = df['volatility'].rolling(100).rank(pct=True)
# Support/Resistance levels
df['support'] = df['low'].rolling(20).min()
df['resistance'] = df['high'].rolling(20).max()
df['support_distance'] = (df['close'] - df['support']) / df['close']
df['resistance_distance'] = (df['resistance'] - df['close']) / df['close']
# Market structure
df['higher_high'] = (df['high'] > df['high'].shift(1)) & (df['high'].shift(1) > df['high'].shift(2))
df['lower_low'] = (df['low'] < df['low'].shift(1)) & (df['low'].shift(1) < df['low'].shift(2))
return df
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
df['enter_long'] = False
# Simple but effective entry conditions
entry_mask = (
# Trend confirmation
(df['ema20'] > df['ema50']) & # Short EMA above long EMA
(df['ema50'] > df['ema100']) & # Medium EMA above long EMA
(df['trend_strength'] > 0.01) & # Strong trend
# RSI conditions
(df['rsi'] > 45) & # Not oversold
(df['rsi'] < 75) & # Not overbought
# Bollinger Bands
(df['bb_position'] > 0.2) & # Above lower BB
(df['bb_position'] < 0.8) & # Below upper BB
(df['bb_width'] > 0.02) & # Sufficient volatility
(df['bb_width'] < 0.08) & # Not extreme volatility
# MACD confirmation
(df['macd'] > df['macd_signal']) & # MACD bullish
(df['macd_hist'] > df['macd_hist'].shift(1)) & # MACD improving
# Volume confirmation
(df['volume_ratio'] > 1.1) & # Above average volume
(df['volume_trend'] > 0.9) & # Volume trend positive
# Price momentum
(df['price_change_5'] > 0) & # Positive short-term momentum
(df['price_change_10'] > -0.01) & # Not severely declining
# Support/Resistance
(df['support_distance'] > 0.005) & # Above support
(df['resistance_distance'] > 0.01) & # Room to resistance
# Market structure
(df['higher_high'] == True) & # Higher high pattern
# Volatility
(df['volatility_percentile'] > 0.3) & # Not too low volatility
(df['volatility_percentile'] < 0.8) # Not too high volatility
)
df['enter_long'] = entry_mask
# Enter tags for analysis
df['enter_tag'] = 'Simple_Entry'
return df
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
df['exit_long'] = False
# Simple but effective exit conditions
exit_mask = (
# Trend weakening
(df['ema20'] < df['ema50']) | # Short EMA below medium EMA
(df['trend_strength'] < 0.005) | # Weak trend
# RSI conditions
(df['rsi'] > 80) | # Severely overbought
(df['rsi'] < 35) | # Severely oversold
# Bollinger Bands
(df['bb_position'] > 0.9) | # Near upper BB
(df['bb_position'] < 0.1) | # Near lower BB
(df['bb_width'] > 0.10) | # Extreme volatility
# MACD bearish
(df['macd'] < df['macd_signal']) | # MACD bearish
(df['macd_hist'] < df['macd_hist'].shift(1)) | # MACD deteriorating
# Volume issues
(df['volume_ratio'] < 0.7) | # Very low volume
(df['volume_trend'] < 0.8) | # Volume trend negative
# Price momentum
(df['price_change_5'] < -0.005) | # Negative momentum
(df['price_change_10'] < -0.01) | # Declining trend
# Support/Resistance
(df['support_distance'] < 0.002) | # Near support
(df['resistance_distance'] < 0.005) | # Near resistance
# Market structure
(df['lower_low'] == True) | # Lower low pattern
# Volatility
(df['volatility_percentile'] > 0.9) # Extreme volatility
)
df['exit_long'] = exit_mask
return df
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
"""
Simple dynamic stoploss
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe.empty:
return self.stoploss
last_candle = dataframe.iloc[-1]
atr_percent = last_candle.get('atr', 0) / current_rate
volatility_percentile = last_candle.get('volatility_percentile', 0.5)
# Base stoploss
base_stoploss = self.stoploss
# Adjust based on ATR
if atr_percent > 0.01: # High volatility
base_stoploss *= 1.2
elif atr_percent < 0.005: # Low volatility
base_stoploss *= 0.8
# Adjust based on volatility percentile
if volatility_percentile > 0.7: # High volatility
base_stoploss *= 1.1
elif volatility_percentile < 0.3: # Low volatility
base_stoploss *= 0.9
# Adjust based on profit
if current_profit > 0.01: # In profit
base_stoploss = max(base_stoploss, -0.015) # Trail stop
elif current_profit < -0.005: # In loss
base_stoploss *= 1.05 # Slightly wider
return max(base_stoploss, -0.08) # Cap at -8%
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: float, max_stake: float,
leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
"""
Simple dynamic position sizing
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe.empty:
return proposed_stake
last_candle = dataframe.iloc[-1]
volatility_percentile = last_candle.get('volatility_percentile', 0.5)
trend_strength = last_candle.get('trend_strength', 0.0)
# Base position size
base_multiplier = 1.0
# Adjust based on volatility
if volatility_percentile > 0.7: # High volatility
base_multiplier *= 0.8
elif volatility_percentile < 0.3: # Low volatility
base_multiplier *= 1.1
# Adjust based on trend strength
if trend_strength > 0.02: # Strong trend
base_multiplier *= 1.1
elif trend_strength < 0.005: # Weak trend
base_multiplier *= 0.9
adjusted_stake = proposed_stake * base_multiplier
# Ensure within bounds
return max(min_stake, min(adjusted_stake, max_stake))
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
side: str, **kwargs) -> float:
"""
Conservative leverage
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe.empty:
return 1.0
last_candle = dataframe.iloc[-1]
volatility_percentile = last_candle.get('volatility_percentile', 0.5)
# Conservative leverage based on volatility
if volatility_percentile > 0.7: # High volatility
return 1.0 # No leverage
elif volatility_percentile < 0.3: # Low volatility
return min(1.5, max_leverage)
else: # Medium volatility
return min(1.2, max_leverage)