126 lines
3.9 KiB
Python
126 lines
3.9 KiB
Python
from functools import reduce
|
|
|
|
from freqtrade.strategy import IStrategy, IntParameter
|
|
from pandas import DataFrame
|
|
import talib.abstract as ta
|
|
|
|
|
|
class BTC1h(IStrategy):
|
|
# 1-hour timeframe
|
|
timeframe = "1h"
|
|
|
|
# Higher timeframe for trend filter
|
|
informative_timeframe = "4h"
|
|
|
|
# ROI table (0 = latest candle)
|
|
minimal_roi = {
|
|
"0": 0.10,
|
|
"120": 0.05,
|
|
"360": 0.03,
|
|
"720": 0,
|
|
}
|
|
|
|
stoploss = -0.05
|
|
|
|
trailing_stop = False
|
|
trailing_stop_positive = 0.01
|
|
trailing_stop_positive_offset = 0.03
|
|
trailing_only_offset_is_reached = True
|
|
|
|
use_exit_signal = True
|
|
exit_profit_only = False
|
|
|
|
startup_candle_count = 200
|
|
|
|
order_types = {
|
|
"entry": "limit",
|
|
"exit": "limit",
|
|
"stoploss": "market",
|
|
"stoploss_on_exchange": False,
|
|
}
|
|
|
|
# --- Hyperoptable parameters ---
|
|
ema_short = IntParameter(20, 50, default=34, space="buy")
|
|
ema_long = IntParameter(100, 200, default=144, space="buy")
|
|
rsi_entry = IntParameter(25, 45, default=35, space="buy")
|
|
rsi_exit = IntParameter(60, 80, default=70, space="sell")
|
|
|
|
def informative_pairs(self):
|
|
pairs = self.dp.current_whitelist()
|
|
return [(pair, self.informative_timeframe) for pair in pairs]
|
|
|
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
# --- Higher timeframe trend filter ---
|
|
if self.dp:
|
|
informative = self.dp.get_pair_dataframe(
|
|
pair=metadata["pair"], timeframe=self.informative_timeframe
|
|
)
|
|
informative["ema_200"] = ta.EMA(informative, timeperiod=200)
|
|
informative["htf_bull"] = (
|
|
informative["close"] > informative["ema_200"]
|
|
).astype(int)
|
|
|
|
# Merge HTF data into 1h dataframe
|
|
dataframe = dataframe.merge(
|
|
informative[["date", "htf_bull"]], on="date", how="left"
|
|
)
|
|
dataframe["htf_bull"] = dataframe["htf_bull"].ffill().fillna(0)
|
|
else:
|
|
dataframe["htf_bull"] = 1
|
|
|
|
# --- EMAs ---
|
|
dataframe["ema_short"] = ta.EMA(dataframe, timeperiod=self.ema_short.value)
|
|
dataframe["ema_long"] = ta.EMA(dataframe, timeperiod=self.ema_long.value)
|
|
|
|
# --- RSI ---
|
|
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
|
|
|
# --- MACD ---
|
|
macd = ta.MACD(dataframe)
|
|
dataframe["macd"] = macd["macd"]
|
|
dataframe["macd_signal"] = macd["macdsignal"]
|
|
dataframe["macd_hist"] = macd["macdhist"]
|
|
|
|
# --- Volume ---
|
|
dataframe["volume_ma"] = ta.SMA(dataframe, timeperiod=20)
|
|
|
|
# --- ATR ---
|
|
dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
|
|
|
|
return dataframe
|
|
|
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
conditions = [
|
|
# 4h trend is bullish
|
|
dataframe["htf_bull"] == 1,
|
|
# Price above long-term EMA
|
|
dataframe["close"] > dataframe["ema_long"],
|
|
# Pullback near short-term EMA
|
|
dataframe["close"] < dataframe["ema_short"] * 1.02,
|
|
# RSI dip
|
|
dataframe["rsi"] < self.rsi_entry.value,
|
|
# MACD turning up
|
|
dataframe["macd_hist"] > dataframe["macd_hist"].shift(1),
|
|
# Volume confirmation
|
|
dataframe["volume"] > dataframe["volume_ma"],
|
|
]
|
|
|
|
dataframe.loc[reduce(lambda a, b: a & b, conditions), "enter_long"] = 1
|
|
|
|
return dataframe
|
|
|
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
conditions = [
|
|
# RSI overbought
|
|
dataframe["rsi"] > self.rsi_exit.value,
|
|
# MACD bearish cross
|
|
(
|
|
(dataframe["macd"] < dataframe["macd_signal"])
|
|
& (dataframe["macd"].shift(1) > dataframe["macd_signal"].shift(1))
|
|
),
|
|
]
|
|
|
|
dataframe.loc[reduce(lambda a, b: a | b, conditions), "exit_long"] = 1
|
|
|
|
return dataframe
|