Replaced pullback strategy with EMA 12/26 crossover on 1h, filtered by 4h EMA50 uptrend and 1h EMA200. Exits via bearish cross or trailing stop. Dec 2025-May 2026 backtest: +1.89 USDT (+0.19%), 27 trades, 37% win rate, 0.29% max drawdown, while BTC dropped 6.7%. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
96 lines
3.0 KiB
Python
96 lines
3.0 KiB
Python
from functools import reduce
|
|
|
|
from freqtrade.strategy import IStrategy, merge_informative_pair
|
|
from pandas import DataFrame
|
|
import talib.abstract as ta
|
|
|
|
|
|
class BTC1h(IStrategy):
|
|
"""
|
|
EMA crossover trend-following strategy for BTC/USDT on the 1h timeframe.
|
|
|
|
Entry: 4h EMA50 uptrend + 1h price above 200 EMA + 12/26 EMA bullish cross.
|
|
Exit: 12/26 EMA bearish cross, trailing stop, or stoploss.
|
|
|
|
Performs best in trending markets. During the Dec 2025-May 2026 period (BTC
|
|
dropped 6.7%), this strategy returned +1.89 USDT (+0.19%) with 27 trades,
|
|
37% win rate, and max 0.29% drawdown.
|
|
"""
|
|
timeframe = "1h"
|
|
informative_timeframe = "4h"
|
|
|
|
minimal_roi = {"0": 0.99}
|
|
|
|
stoploss = -0.025
|
|
|
|
trailing_stop = True
|
|
trailing_stop_positive = 0.01
|
|
trailing_stop_positive_offset = 0.025
|
|
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,
|
|
}
|
|
|
|
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:
|
|
if self.dp:
|
|
inf = self.dp.get_pair_dataframe(
|
|
pair=metadata["pair"], timeframe=self.informative_timeframe
|
|
)
|
|
inf["ema_50"] = ta.EMA(inf, timeperiod=50)
|
|
inf["htf_bull"] = (inf["close"] > inf["ema_50"]).astype(int)
|
|
|
|
dataframe = merge_informative_pair(
|
|
dataframe, inf,
|
|
self.timeframe, self.informative_timeframe,
|
|
ffill=True,
|
|
)
|
|
|
|
dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=12)
|
|
dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=26)
|
|
|
|
dataframe["cross_above"] = (
|
|
(dataframe["ema_fast"] > dataframe["ema_slow"])
|
|
& (dataframe["ema_fast"].shift(1) <= dataframe["ema_slow"].shift(1))
|
|
)
|
|
dataframe["cross_below"] = (
|
|
(dataframe["ema_fast"] < dataframe["ema_slow"])
|
|
& (dataframe["ema_fast"].shift(1) >= dataframe["ema_slow"].shift(1))
|
|
)
|
|
|
|
dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)
|
|
|
|
return dataframe
|
|
|
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
conditions = [
|
|
dataframe["htf_bull_4h"] == 1,
|
|
dataframe["close"] > dataframe["ema_200"],
|
|
dataframe["cross_above"] == True,
|
|
]
|
|
|
|
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 = [
|
|
dataframe["cross_below"] == True,
|
|
]
|
|
|
|
dataframe.loc[reduce(lambda a, b: a | b, conditions), "exit_long"] = 1
|
|
|
|
return dataframe
|