Freqtrade Strategies 101: A Quick Start for Strategy Development
For the purposes of this quick start, we are assuming you are familiar with the basics of trading, and have read the Bot Basics page.
Core Concepts
Trading Terminology
- Assets/Pairs: Represent the coin you're trading and the stake currency (e.g., BTC/USDT)
- Candles: OHLCV data (Open, High, Low, Close, Volume) for specific time periods
- Indicators: Technical analysis values calculated from candle data
- Signals: Entry/exit conditions based on indicator analysis
- Orders/Trades: Actual buy/sell transactions on the exchange
Long vs Short Trading
- Long: Buy low, sell high - profit when price increases
- Short: Borrow and sell high, buy back low - profit when price decreases
Focus on Spot Trading
This guide focuses on spot (long) trading for simplicity, though Freqtrade supports both spot and futures markets.
Strategy Structure
Main Components
A Freqtrade strategy consists of three main functions:
populate_indicators()
- Calculate technical indicatorspopulate_entry_trend()
- Define entry conditionspopulate_exit_trend()
- Define exit conditions
Dataframe Structure
Each trading pair has its own dataframe with:
- Index: Date/time (e.g.,
2024-06-31 12:00
) - Columns: OHLCV data plus custom indicators
- Signals: Entry/exit columns with 1/0 values
Basic Strategy Example
Here's a simple RSI-based strategy:
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import talib.abstract as ta
class MyFirstStrategy(IStrategy):
# Strategy settings
timeframe = '15m'
stoploss = -0.10 # 10% stop loss
minimal_roi = {"0": 0.01} # 1% minimum profit
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""Calculate technical indicators"""
# RSI (Relative Strength Index)
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
# Moving averages
dataframe['sma_20'] = ta.SMA(dataframe, timeperiod=20)
dataframe['sma_50'] = ta.SMA(dataframe, timeperiod=50)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""Define entry conditions"""
dataframe.loc[
(
(dataframe['rsi'] < 30) & # Oversold condition
(dataframe['sma_20'] > dataframe['sma_50']) # Upward trend
),
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""Define exit conditions"""
dataframe.loc[
(dataframe['rsi'] > 70), # Overbought condition
'exit_long'
] = 1
return dataframe
Strategy Configuration
Essential Parameters
class MyStrategy(IStrategy):
# Timeframe for analysis
timeframe = '5m' # 1m, 5m, 15m, 1h, 4h, 1d
# Risk management
stoploss = -0.05 # 5% stop loss
# Profit targets
minimal_roi = {
"0": 0.10, # 10% profit immediately
"40": 0.05, # 5% profit after 40 minutes
"100": 0.02, # 2% profit after 100 minutes
"180": 0.01 # 1% profit after 180 minutes
}
# Optional: Trailing stop
trailing_stop = True
trailing_stop_positive = 0.01
trailing_stop_positive_offset = 0.02
Common Indicators
Trend Indicators
# Moving Averages
dataframe['sma_20'] = ta.SMA(dataframe, timeperiod=20)
dataframe['ema_12'] = ta.EMA(dataframe, timeperiod=12)
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
Momentum Indicators
# RSI
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
# Stochastic
stoch = ta.STOCH(dataframe)
dataframe['slowk'] = stoch['slowk']
dataframe['slowd'] = stoch['slowd']
# Williams %R
dataframe['willr'] = ta.WILLR(dataframe, timeperiod=14)
Volatility Indicators
# Bollinger Bands
bollinger = ta.BBANDS(dataframe, timeperiod=20)
dataframe['bb_lower'] = bollinger['lowerband']
dataframe['bb_middle'] = bollinger['middleband']
dataframe['bb_upper'] = bollinger['upperband']
# Average True Range
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
Entry and Exit Logic
Complex Entry Conditions
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
# Trend conditions
(dataframe['ema_12'] > dataframe['ema_26']) &
(dataframe['close'] > dataframe['sma_50']) &
# Momentum conditions
(dataframe['rsi'] > 30) & (dataframe['rsi'] < 70) &
# Volume condition
(dataframe['volume'] > dataframe['volume'].rolling(20).mean()) &
# Price action
(dataframe['close'] > dataframe['open']) # Green candle
),
'enter_long'
] = 1
return dataframe
Multiple Exit Strategies
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Exit on overbought RSI
dataframe.loc[
(dataframe['rsi'] > 80),
'exit_long'
] = 1
# Exit on bearish MACD crossover
dataframe.loc[
(
(dataframe['macd'] < dataframe['macdsignal']) &
(dataframe['macd'].shift(1) >= dataframe['macdsignal'].shift(1))
),
'exit_long'
] = 1
return dataframe
Testing Your Strategy
Backtesting
Test your strategy with historical data:
# Basic backtest
freqtrade backtesting --strategy MyFirstStrategy
# Backtest with specific timerange
freqtrade backtesting \
--strategy MyFirstStrategy \
--timerange 20230101-20230601
# Backtest with specific pairs
freqtrade backtesting \
--strategy MyFirstStrategy \
--pairs BTC/USDT ETH/USDT
Dry Run (Paper Trading)
Test with live data without real money:
# Enable dry run in config.json
{
"dry_run": true,
"strategy": "MyFirstStrategy"
}
# Start dry run
freqtrade trade --config config.json
Strategy Optimization
Hyperparameter Optimization
from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal
class OptimizedStrategy(IStrategy):
# Hyperopt parameters
buy_rsi = IntParameter(20, 40, default=30, space="buy")
sell_rsi = IntParameter(60, 80, default=70, space="sell")
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(dataframe['rsi'] < self.buy_rsi.value),
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(dataframe['rsi'] > self.sell_rsi.value),
'exit_long'
] = 1
return dataframe
Run optimization:
freqtrade hyperopt \
--strategy OptimizedStrategy \
--hyperopt-loss SharpeHyperOptLoss \
--epochs 100
Best Practices
Strategy Development
- Start Simple: Begin with basic indicators and logic
- Avoid Overfitting: Don't optimize for past data only
- Test Thoroughly: Use both backtesting and dry runs
- Risk Management: Always include stop losses
- Documentation: Comment your code and logic
Common Mistakes
- Look-ahead bias: Using future data in calculations
- Overfitting: Too many conditions or parameters
- Ignoring fees: Not accounting for trading costs
- No risk management: Missing stop losses or position sizing
- Unrealistic expectations: Expecting 100% win rates
Performance Metrics
Monitor these key metrics:
- Total Return: Overall profit/loss
- Sharpe Ratio: Risk-adjusted returns
- Maximum Drawdown: Largest peak-to-trough decline
- Win Rate: Percentage of profitable trades
- Profit Factor: Ratio of gross profit to gross loss
Advanced Features
Custom Indicators
def custom_indicator(dataframe: DataFrame) -> DataFrame:
"""Custom indicator calculation"""
high_low = (dataframe['high'] - dataframe['low']).abs()
high_close = (dataframe['high'] - dataframe['close'].shift()).abs()
low_close = (dataframe['low'] - dataframe['close'].shift()).abs()
ranges = pd.concat([high_low, high_close, low_close], axis=1)
true_range = ranges.max(axis=1)
return true_range.rolling(14).mean()
Strategy Callbacks
def confirm_trade_entry(self, pair: str, order_type: str, amount: float,
rate: float, time_in_force: str, current_time: datetime,
entry_tag: Optional[str], side: str, **kwargs) -> bool:
"""Confirm trade entry"""
# Add custom logic to confirm or reject trades
return True
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str,
amount: float, rate: float, time_in_force: str,
exit_reason: str, current_time: datetime, **kwargs) -> bool:
"""Confirm trade exit"""
# Add custom logic to confirm or reject exits
return True
Next Steps
- Strategy Customization - Advanced strategy features
- Backtesting - Comprehensive testing guide
- Hyperopt - Parameter optimization
- Strategy Callbacks - Advanced control mechanisms
Resources
- Example Strategies - Community strategies
- Discord Community - Get help and share ideas
- Technical Analysis Library - TA-Lib documentation
Risk Disclaimer
Always test strategies thoroughly before using real money. Past performance doesn't guarantee future results. Only invest what you can afford to lose.