BTC-optimized swing trading strategy for BTC/USD on Kraken.
Timeframe
4h
Direction
Long Only
Stoploss
-2.5%
Trailing Stop
No
ROI
0m: 8.0%, 60m: 6.0%, 120m: 4.0%, 240m: 1.0%
Interface Version
3
Startup Candles
N/A
Indicators
6
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs ---
import numpy as np
import pandas as pd
from pandas import DataFrame
from datetime import datetime
from typing import Optional, Union
from freqtrade.strategy import (
IStrategy,
Trade,
Order,
PairLocks,
informative,
merge_informative_pair,
stoploss_from_open,
stoploss_from_absolute,
)
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class BTCSwingStrategy(IStrategy):
"""
BTC-optimized swing trading strategy for BTC/USD on Kraken.
Designed for Bitcoin's 7.55% volatility and steady trend patterns.
Uses 4h primary timeframe with 1h entry timing for precision.
Target: 4-6% profit moves, 65%+ win rate
Based on research from Strategy Analysis Report.
"""
# Strategy interface version
INTERFACE_VERSION = 3
# BTC-optimized timeframe (4h primary for trend identification)
timeframe = '4h'
# Use 1h for entry timing precision
informative_timeframe = '1h'
# Can this strategy go short?
can_short = False
# BTC-specific ROI targeting 4-6% moves with scale-out approach
minimal_roi = {
"0": 0.08, # 8% aggressive target
"60": 0.06, # 6% after 4 hours (optimal target)
"120": 0.04, # 4% after 8 hours (minimum for fees)
"240": 0.01 # 1% after 16 hours (safety exit)
}
# BTC-optimized stoploss (tighter due to lower volatility)
stoploss = -0.025 # 2.5% maximum loss (BTC's steadier patterns)
# Trailing stoploss
trailing_stop = False
# BTC-specific parameters
# Trend confirmation parameters
ema_short_period = 20
ema_long_period = 50
# RSI parameters for BTC's momentum patterns
rsi_period = 14
rsi_buy_threshold = 40 # Less oversold for BTC
rsi_sell_threshold = 65 # Earlier exit for BTC
# Volume confirmation
volume_sma_period = 20
# ADX trend strength
adx_min_strength = 25
def informative_pairs(self):
"""
Define additional informative pairs for 1h entry timing
"""
pairs = self.dp.current_whitelist()
informative_pairs = []
for pair in pairs:
informative_pairs.append((pair, self.informative_timeframe))
return informative_pairs
def populate_indicators(self, dataframe, metadata):
"""
Populate indicators optimized for BTC swing trading
"""
# EMA trend indicators (primary for BTC)
dataframe['ema_short'] = ta.EMA(dataframe, timeperiod=self.ema_short_period)
dataframe['ema_long'] = ta.EMA(dataframe, timeperiod=self.ema_long_period)
dataframe['ema_trend'] = dataframe['ema_short'] > dataframe['ema_long']
# RSI for momentum
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.rsi_period)
# Bollinger Bands for volatility
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
dataframe['bb_percent'] = (
(dataframe['close'] - dataframe['bb_lowerband']) /
(dataframe['bb_upperband'] - dataframe['bb_lowerband'])
)
# MACD for trend confirmation
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# Volume indicators
dataframe['volume_sma'] = ta.SMA(dataframe['volume'], timeperiod=self.volume_sma_period)
dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_sma']
# ADX for trend strength
dataframe['adx'] = ta.ADX(dataframe)
# Support/Resistance levels (simple approach)
dataframe['resistance'] = dataframe['high'].rolling(window=10).max()
dataframe['support'] = dataframe['low'].rolling(window=10).min()
# Price position relative to support/resistance
dataframe['price_position'] = (dataframe['close'] - dataframe['support']) / (dataframe['resistance'] - dataframe['support'])
return dataframe
@informative('1h')
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
1h timeframe indicators for precise entry timing
"""
# Fast RSI for entry timing
dataframe['rsi_1h'] = ta.RSI(dataframe, timeperiod=14)
# Fast EMA for immediate trend
dataframe['ema_fast_1h'] = ta.EMA(dataframe, timeperiod=12)
dataframe['ema_slow_1h'] = ta.EMA(dataframe, timeperiod=26)
# Volume surge detection
dataframe['volume_sma_1h'] = ta.SMA(dataframe['volume'], timeperiod=20)
dataframe['volume_surge_1h'] = dataframe['volume'] > dataframe['volume_sma_1h'] * 1.5
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
BTC-optimized entry conditions for 4-6% swing moves
Entry Logic:
1. 4h trend confirmation (EMA alignment)
2. RSI recovery from oversold (but not too deep)
3. MACD bullish alignment
4. Volume confirmation
5. 1h timing confirmation
6. Support/resistance positioning
"""
conditions = []
# Primary trend confirmation (4h timeframe)
conditions.append(dataframe['ema_trend']) # Short EMA > Long EMA
# RSI in favorable zone (not too oversold for BTC)
conditions.append(
(dataframe['rsi'] > self.rsi_buy_threshold) &
(dataframe['rsi'] < 60) # Not overbought
)
# MACD bullish alignment
conditions.append(
(dataframe['macd'] > dataframe['macdsignal']) &
(dataframe['macdhist'] > 0)
)
# Volume confirmation (institutional interest)
conditions.append(dataframe['volume_ratio'] > 1.2)
# ADX shows trend strength
conditions.append(dataframe['adx'] > self.adx_min_strength)
# Price position favorable (not at resistance)
conditions.append(dataframe['price_position'] < 0.8)
# 1h timeframe confirmation for entry timing
conditions.append(dataframe['rsi_1h'] > 35) # 1h RSI recovery
conditions.append(dataframe['ema_fast_1h'] > dataframe['ema_slow_1h']) # 1h trend aligned
# Bollinger Band positioning (near lower band for entry)
conditions.append(dataframe['bb_percent'] < 0.3)
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
BTC-optimized exit conditions for profit-taking
Exit Logic:
1. RSI overbought (momentum exhausted)
2. MACD bearish divergence
3. Volume declining
4. Price at upper Bollinger Band
5. 1h timeframe shows weakness
"""
conditions = []
# RSI overbought (BTC-specific threshold)
conditions.append(dataframe['rsi'] > self.rsi_sell_threshold)
# MACD bearish signal
conditions.append(
(dataframe['macd'] < dataframe['macdsignal']) |
(dataframe['macdhist'] < 0)
)
# Volume declining (profit-taking phase)
conditions.append(dataframe['volume_ratio'] < 0.8)
# Price at upper Bollinger Band
conditions.append(dataframe['bb_percent'] > 0.8)
# 1h timeframe showing weakness
conditions.append(dataframe['rsi_1h'] > 70)
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'exit_long'] = 1
return dataframe
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
"""
BTC-optimized stoploss logic
"""
return self.stoploss
def custom_sell(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
"""
BTC-optimized profit-taking logic
Scale-out approach: 4%, 6%, 8% targets
"""
# Scale out at 8% (aggressive target for BTC)
if current_profit >= 0.08:
return 'btc_profit_8pct'
# Scale out at 6% (optimal target)
if current_profit >= 0.06:
return 'btc_profit_6pct'
# Consider exit at 4% minimum if other conditions met
if current_profit >= 0.04:
return None # Let normal exit signals handle this
return None
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:
"""
No leverage for spot trading on Kraken
"""
return 1.0
def reduce(function, iterable, initializer=None):
"""Python reduce function for combining conditions"""
it = iter(iterable)
if initializer is None:
value = next(it)
else:
value = initializer
for element in it:
value = function(value, element)
return value