BTCSwingStrategy
♡
Basics
mode: spot
timeframe: 4h
interface version: 3
1h
Settings
stoploss: -0.025
has minimal roi
custom stoploss
Indicators
ADX
Bollinger_Bands
EMA
MACD
RSI
SMA
talib
Concepts
divergence
mean_reversion
swing
15 related strategies (⧉ identical code, ≈ similar name)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | # 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 # 8% aggressive target # 6% after 4 hours (optimal target) # 4% after 8 hours (minimum for fees) # 1% after 16 hours (safety exit) minimal_roi = {'0': 0.08, '60': 0.06, '120': 0.04, '240': 0.01} # 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) # Not overbought conditions.append((dataframe['rsi'] > self.rsi_buy_threshold) & (dataframe['rsi'] < 60)) # 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_exit(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 |
Strategy League — fixed backtest that feeds the ranking
The fixed-params backtest (33 pairs · 20210101-20260101) — the only run that feeds the Strategy League ranking.
Backtests — over a market period
Backtest this strategy over a chosen crypto-cycle period. These don't affect the League ranking, and need that period's candle data downloaded.
Log in or sign up to run backtests.
| Period | Range | Total % | Win % | Max DD | Trades | |
|---|---|---|---|---|---|---|
| 2020 · DeFi Summer & Pre-Halving Rally | 20200101-20210101 | not run | ||||
| 2021 · Institutional Bull Market | 20210101-20220101 | not run | ||||
| 2022 · Post-Bull Crash & Macro Tightening | 20220101-20230101 | not run | ||||
| 2023–2024 · Recovery & ETF Anticipation | 20230101-20250101 | not run | ||||
| 2025–2026 · Current Cycle | 20250101-20260101 | not run | ||||
Walk forward
Out-of-sample backtest on recent data · 33 pairs · 20260101-20260701.
Backtest trust check
Static source analysis — instant, does not run the strategy. Flags future-data leaks, backtest-realism problems, and indicators worth a second look.
Lookahead analysis
freqtrade lookahead-analysis: detects strategies peeking at future candles.