Timeframe
N/A
Direction
Long Only
Stoploss
N/A
Trailing Stop
No
ROI
N/A
Interface Version
N/A
Startup Candles
N/A
Indicators
5
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
ClucHAnix — Heikin-Ashi Noise-Filtered Bollinger Band Scalper
Source: https://github.com/freqtrade/freqtrade-strategies (community)
Timeframe: 5m (with 1h informative)
Description: Uses Heikin-Ashi candle smoothing over Bollinger Bands with a 1h ROCR filter
and Fisher RSI exit signal. Custom progressive trailing stoploss.
"""
from freqtrade.strategy import IStrategy, DecimalParameter, merge_informative_pair
from pandas import DataFrame
import numpy as np
import pandas_ta as pta
class ClucHAnix(IStrategy):
INTERFACE_VERSION: int = 3
timeframe: str = "5m"
informative_timeframe: str = "1h"
# ROI disabled — custom exits dominate
minimal_roi: dict = {"0": 10}
# Base stoploss disabled — custom stoploss handles exits
stoploss: float = -0.99
use_exit_signal: bool = True
exit_profit_only: bool = False
# -----------------------------------------------------------------------
# Buy hyperopt parameters
# -----------------------------------------------------------------------
buy_closedelta_ratio = DecimalParameter(0.5, 2.0, default=1.0, space="buy",
help="closedelta / (0.5 * bbdelta) must exceed this")
buy_tail_ratio = DecimalParameter(0.5, 1.5, default=1.0, space="buy",
help="tail / (0.5 * bbdelta) must be below this")
buy_rocr_1h = DecimalParameter(0.95, 1.1, default=1.0, space="buy",
help="1h ROCR must exceed this")
# -----------------------------------------------------------------------
# Sell hyperopt parameters
# -----------------------------------------------------------------------
sell_fisher_rsi = DecimalParameter(0.1, 0.8, default=0.3, space="sell",
help="Exit when Fisher RSI exceeds this")
def informative_pairs(self) -> list:
pairs = self.dp.current_whitelist()
return [(pair, self.informative_timeframe) for pair in pairs]
def _compute_heikin_ashi(self, dataframe: DataFrame) -> DataFrame:
ha_close = (dataframe["open"] + dataframe["high"] + dataframe["low"] + dataframe["close"]) / 4
ha_open = ha_close.copy()
ha_open.iloc[0] = (dataframe["open"].iloc[0] + dataframe["close"].iloc[0]) / 2
for i in range(1, len(dataframe)):
ha_open.iloc[i] = (ha_open.iloc[i - 1] + ha_close.iloc[i - 1]) / 2
dataframe["ha_open"] = ha_open
dataframe["ha_close"] = ha_close
dataframe["ha_high"] = dataframe[["high", "ha_open", "ha_close"]].max(axis=1)
dataframe["ha_low"] = dataframe[["low", "ha_open", "ha_close"]].min(axis=1)
return dataframe
def _compute_fisher_rsi(self, dataframe: DataFrame, length: int = 5, smoothing: int = 5) -> DataFrame:
rsi = pta.rsi(dataframe["close"], length=length)
# Normalize RSI to -1..1 range
rsi_norm = 0.1 * (rsi - 50)
# Clamp to avoid infinity in log
rsi_norm = rsi_norm.clip(-0.999, 0.999)
# Fisher transform
fisher = (np.log((1 + rsi_norm) / (1 - rsi_norm))).rolling(window=smoothing).mean()
dataframe["fisher_rsi"] = fisher
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# --- 1h informative ---
inf_tf = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe=self.informative_timeframe)
inf_tf["rocr"] = pta.roc(inf_tf["close"], length=1) / 100 + 1 # ROCR as ratio
dataframe = merge_informative_pair(dataframe, inf_tf, self.timeframe, self.informative_timeframe, ffill=True)
# --- Bollinger Bands (20, 2) ---
bbands = pta.bbands(dataframe["close"], length=20, std=2.0)
dataframe["bb_lowerband"] = bbands["BBL_20_2.0"]
dataframe["bb_middleband"] = bbands["BBM_20_2.0"]
dataframe["bb_upperband"] = bbands["BBU_20_2.0"]
# --- Heikin-Ashi ---
dataframe = self._compute_heikin_ashi(dataframe)
# --- Fisher RSI ---
dataframe = self._compute_fisher_rsi(dataframe)
# --- Entry helper columns ---
dataframe["bbdelta"] = (dataframe["bb_middleband"] - dataframe["bb_lowerband"]).abs()
dataframe["closedelta"] = (dataframe["close"] - dataframe["close"].shift(1)).abs()
dataframe["tail"] = (dataframe["low"] - dataframe["close"]).abs()
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe["bbdelta"] > 0)
& (dataframe["closedelta"] / (0.5 * dataframe["bbdelta"]) > self.buy_closedelta_ratio.value)
& (dataframe["tail"] / (0.5 * dataframe["bbdelta"]) < self.buy_tail_ratio.value)
& (dataframe["ha_close"] < dataframe["ha_open"])
& (dataframe["ha_close"] < dataframe["ha_close"].shift(1))
& (dataframe[f"rocr_{self.informative_timeframe}"] > self.buy_rocr_1h.value)
& (dataframe["volume"] > 0)
),
"enter_long",
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe["fisher_rsi"] > self.sell_fisher_rsi.value)
& (dataframe["volume"] > 0)
),
"exit_long",
] = 1
return dataframe
def custom_stoploss(
self,
pair: str,
trade: "Trade",
current_time: "datetime",
current_rate: float,
current_profit: float,
**kwargs,
) -> float:
"""
Progressive trailing stoploss: rises from -0.05 to -0.01 as profit
increases from 0% to 2%.
"""
if current_profit < 0.0:
return -0.05
if current_profit >= 0.02:
return -0.01
# Linear interpolation between -0.05 and -0.01
return -0.05 + (current_profit / 0.02) * 0.04