Timeframe
N/A
Direction
Long Only
Stoploss
N/A
Trailing Stop
No
ROI
N/A
Interface Version
N/A
Startup Candles
N/A
Indicators
6
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
CombinedBinHAndClucV8Hyper — Hyperopt-Ready Combined Dip-Buy Scalper
Source: https://github.com/freqtrade/freqtrade-strategies (BinHV45 + ClucMay72018)
Timeframe: 5m (with 1h informative)
Description: Combines BinHV45 and ClucMay72018 dip-buy signals with full hyperopt
parameter support. Four independently togglable entry conditions with
SSL Channel and MFI indicators.
"""
from functools import reduce
from freqtrade.strategy import (
IStrategy,
BooleanParameter,
DecimalParameter,
IntParameter,
merge_informative_pair,
)
from pandas import DataFrame
import numpy as np
import pandas_ta as pta
class CombinedBinHAndClucV8Hyper(IStrategy):
INTERFACE_VERSION: int = 3
timeframe: str = "5m"
informative_timeframe: str = "1h"
minimal_roi: dict = {
"0": 0.15,
"30": 0.05,
"60": 0.02,
"120": 0.01,
}
stoploss: float = -0.274
use_exit_signal: bool = True
exit_profit_only: bool = False
# -----------------------------------------------------------------------
# Buy hyperopt parameters — Condition 1 (BB40 squeeze)
# -----------------------------------------------------------------------
buy_cond1_enabled = BooleanParameter(default=True, space="buy")
buy_cond1_bbdelta = DecimalParameter(0.01, 0.05, default=0.025, space="buy")
buy_cond1_closedelta_factor = DecimalParameter(0.5, 1.5, default=1.0, space="buy")
buy_cond1_tail_factor = DecimalParameter(0.1, 1.0, default=0.5, space="buy")
# -----------------------------------------------------------------------
# Buy hyperopt parameters — Condition 2 (BB20 lower band dip)
# -----------------------------------------------------------------------
buy_cond2_enabled = BooleanParameter(default=True, space="buy")
buy_cond2_close_factor = DecimalParameter(0.97, 1.0, default=0.99, space="buy")
buy_cond2_volume_factor = DecimalParameter(0.5, 2.0, default=1.0, space="buy")
# -----------------------------------------------------------------------
# Buy hyperopt parameters — Condition 3 (RSI 1h divergence)
# -----------------------------------------------------------------------
buy_cond3_enabled = BooleanParameter(default=True, space="buy")
buy_cond3_rsi_1h = IntParameter(30, 50, default=40, space="buy")
buy_cond3_rsi_diff = DecimalParameter(0.5, 3.0, default=1.5, space="buy")
# -----------------------------------------------------------------------
# Buy hyperopt parameters — Condition 4 (EMA momentum)
# -----------------------------------------------------------------------
buy_cond4_enabled = BooleanParameter(default=True, space="buy")
buy_cond4_ema_ratio = DecimalParameter(0.99, 1.01, default=1.0, space="buy")
# -----------------------------------------------------------------------
# Sell hyperopt parameters
# -----------------------------------------------------------------------
sell_rsi_threshold = IntParameter(60, 80, default=70, space="sell")
def informative_pairs(self) -> list:
pairs = self.dp.current_whitelist()
return [(pair, self.informative_timeframe) for pair in pairs]
def _ssl_channel(self, dataframe: DataFrame, length: int = 10) -> DataFrame:
"""SSL Channel indicator — ATR + SMA hybrid."""
atr = pta.atr(dataframe["high"], dataframe["low"], dataframe["close"], length=length)
sma_high = pta.sma(dataframe["high"], length=length)
sma_low = pta.sma(dataframe["low"], length=length)
hlv = np.where(
dataframe["close"] > sma_high,
1,
np.where(dataframe["close"] < sma_low, -1, np.nan),
)
hlv = DataFrame({"hlv": hlv}).ffill().values.flatten()
dataframe["ssl_down"] = np.where(hlv < 0, sma_high, sma_low)
dataframe["ssl_up"] = np.where(hlv < 0, sma_low, sma_high)
dataframe["ssl_atr"] = atr
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["rsi_1h"] = pta.rsi(inf_tf["close"], length=14)
inf_tf["sma200_1h"] = pta.sma(inf_tf["close"], length=200)
dataframe = merge_informative_pair(dataframe, inf_tf, self.timeframe, self.informative_timeframe, ffill=True)
# --- Bollinger Bands (20, 2) ---
bb20 = pta.bbands(dataframe["close"], length=20, std=2.0)
dataframe["bb20_low"] = bb20["BBL_20_2.0"]
dataframe["bb20_mid"] = bb20["BBM_20_2.0"]
dataframe["bb20_upp"] = bb20["BBU_20_2.0"]
# --- Bollinger Bands (40, 2) for condition 1 ---
bb40 = pta.bbands(dataframe["close"], length=40, std=2.0)
dataframe["bb40_low"] = bb40["BBL_40_2.0"]
dataframe["bb40_mid"] = bb40["BBM_40_2.0"]
# --- BB40 deltas for condition 1 ---
dataframe["bb40_delta"] = (dataframe["bb40_mid"] - dataframe["bb40_low"]).abs()
dataframe["closedelta"] = (dataframe["close"] - dataframe["close"].shift(1)).abs()
dataframe["tail"] = (dataframe["low"] - dataframe["close"]).abs()
# --- RSI ---
dataframe["rsi"] = pta.rsi(dataframe["close"], length=14)
# --- EMAs ---
dataframe["ema50"] = pta.ema(dataframe["close"], length=50)
dataframe["ema200"] = pta.ema(dataframe["close"], length=200)
dataframe["sma200"] = pta.sma(dataframe["close"], length=200)
# --- MFI (Money Flow Index, 14) ---
dataframe["mfi"] = pta.mfi(
dataframe["high"], dataframe["low"], dataframe["close"], dataframe["volume"], length=14
)
# --- SSL Channel ---
dataframe = self._ssl_channel(dataframe, length=10)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions: list = []
# Condition 1 — BB40 squeeze
if self.buy_cond1_enabled.value:
cond1 = (
(dataframe["bb40_delta"] > self.buy_cond1_bbdelta.value)
& (dataframe["closedelta"] > dataframe["bb40_delta"] * self.buy_cond1_closedelta_factor.value)
& (dataframe["tail"] < dataframe["bb40_delta"] * self.buy_cond1_tail_factor.value)
& (dataframe["close"] < dataframe["bb40_low"])
& (dataframe["volume"] > 0)
)
conditions.append(cond1)
# Condition 2 — BB20 lower band dip
if self.buy_cond2_enabled.value:
cond2 = (
(dataframe["close"] < dataframe["bb20_low"])
& (dataframe["close"] < dataframe["close"].shift(1) * self.buy_cond2_close_factor.value)
& (dataframe["volume"] > dataframe["volume"].shift(1) * self.buy_cond2_volume_factor.value)
)
conditions.append(cond2)
# Condition 3 — RSI 1h divergence
if self.buy_cond3_enabled.value:
cond3 = (
(dataframe[f"rsi_1h_{self.informative_timeframe}"] < self.buy_cond3_rsi_1h.value)
& (dataframe["rsi"] < dataframe["rsi"].shift(1))
& (dataframe["rsi"] > dataframe["rsi"].shift(1) + self.buy_cond3_rsi_diff.value)
& (dataframe["volume"] > 0)
)
conditions.append(cond3)
# Condition 4 — EMA momentum
if self.buy_cond4_enabled.value:
cond4 = (
(dataframe["close"] < dataframe["ema50"])
& (dataframe["ema50"] < dataframe["ema200"] * self.buy_cond4_ema_ratio.value)
& (dataframe["volume"] > 0)
)
conditions.append(cond4)
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:
dataframe.loc[
(
(dataframe["rsi"] > self.sell_rsi_threshold.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:
"""Check SMA200 direction — tighten stop if 1h trend is bearish."""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) < 1:
return self.stoploss
last_candle = dataframe.iloc[-1]
# If price is above SMA200 on 1h, use wider stop
if last_candle.get(f"sma200_1h_{self.informative_timeframe}", 0) > 0:
if last_candle["close"] > last_candle[f"sma200_1h_{self.informative_timeframe}"]:
return -0.274
else:
return -0.15
return self.stoploss