A52 c=0.50 e=-0.18 Top ranked strategy — balanced profit with controlled drawdown
Timeframe
5m
Direction
Long & Short
Stoploss
-0.8%
Trailing Stop
Yes
ROI
0m: 1.2%, 30m: 0.8%, 60m: 0.5%, 120m: 0.2%
Interface Version
3
Startup Candles
200
Indicators
7
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
A52 Strategy — Top performing strategy from the ranking table
ETH/USDT 5m Futures | WR% 53.4 | DD% 9.8 | Profit +24,737
Core logic:
- Multi-timeframe momentum with mean-reversion filter
- c=0.50 (conservative position sizing coefficient)
- e=-0.18 (slight short bias — the "多空方向針")
- Uses ATR-based dynamic SL/TP
The 'e' parameter (entry bias) controls long/short preference:
e > 0 = long bias, e < 0 = short bias, e = 0 = neutral
The 'c' parameter controls position sizing aggression:
c = 1.0 = max size, c = 0.5 = half size (safer)
"""
import numpy as np
from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter
from pandas import DataFrame
import talib.abstract as ta
class A52Strategy(IStrategy):
"""
A52 c=0.50 e=-0.18
Top ranked strategy — balanced profit with controlled drawdown
"""
INTERFACE_VERSION = 3
# Strategy parameters
timeframe = "5m"
can_short = True # Futures: both long and short
# Position sizing coefficient
position_c = DecimalParameter(0.1, 1.0, default=0.50, space="buy", optimize=True)
# Entry bias: negative = short bias, positive = long bias
entry_bias = DecimalParameter(-0.5, 0.5, default=-0.18, space="buy", optimize=True)
# Indicator periods
fast_ema = IntParameter(8, 21, default=12, space="buy", optimize=True)
slow_ema = IntParameter(21, 55, default=26, space="buy", optimize=True)
rsi_period = IntParameter(10, 20, default=14, space="buy", optimize=True)
atr_period = IntParameter(10, 20, default=14, space="sell", optimize=True)
# ROI table — graduated take profit
minimal_roi = {
"0": 0.012,
"30": 0.008,
"60": 0.005,
"120": 0.002,
}
# Stoploss
stoploss = -0.008 # 0.8% hard stop
trailing_stop = True
trailing_stop_positive = 0.004
trailing_stop_positive_offset = 0.008
trailing_only_offset_is_reached = True
# Startup candles needed for indicators
startup_candle_count = 200
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""Calculate all technical indicators"""
# EMAs for trend
dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=int(self.fast_ema.value))
dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=int(self.slow_ema.value))
# RSI for momentum
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=int(self.rsi_period.value))
# ATR for volatility-based stops
dataframe["atr"] = ta.ATR(dataframe, timeperiod=int(self.atr_period.value))
# MACD for momentum confirmation
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
dataframe["macd"] = macd["macd"]
dataframe["macd_signal"] = macd["macdsignal"]
dataframe["macd_hist"] = macd["macdhist"]
# Bollinger Bands for mean reversion
bb = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
dataframe["bb_upper"] = bb["upperband"]
dataframe["bb_middle"] = bb["middleband"]
dataframe["bb_lower"] = bb["lowerband"]
# Volume SMA for volume filter
dataframe["volume_sma"] = ta.SMA(dataframe["volume"], timeperiod=20)
# ADX for trend strength
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
# EMA slope for trend direction
dataframe["ema_slope"] = (dataframe["ema_fast"] - dataframe["ema_fast"].shift(10)) / dataframe["ema_fast"].shift(10) * 100
# Direction score: combines trend + momentum + bias
trend_score = np.where(
dataframe["ema_fast"] > dataframe["ema_slow"], 1.0, -1.0
)
momentum_score = (dataframe["rsi"] - 50) / 50 # normalize to [-1, 1]
macd_score = np.where(dataframe["macd_hist"] > 0, 0.5, -0.5)
dataframe["direction_score"] = (
trend_score * 0.4
+ momentum_score * 0.3
+ macd_score * 0.3
+ float(self.entry_bias.value) # The 多空方向針
)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""Generate entry signals"""
# Long entry conditions
dataframe.loc[
(
(dataframe["direction_score"] > 0.6)
& (dataframe["close"] > dataframe["ema_fast"])
& (dataframe["rsi"] > 30)
& (dataframe["rsi"] < 70)
& (dataframe["volume"] > dataframe["volume_sma"] * 1.3)
& (dataframe["macd_hist"] > 0)
& (dataframe["adx"] > 20)
),
["enter_long", "enter_tag"],
] = (1, "a52_long_momentum")
# Short entry conditions (enabled by negative entry_bias)
dataframe.loc[
(
(dataframe["direction_score"] < -0.6)
& (dataframe["close"] < dataframe["ema_fast"])
& (dataframe["rsi"] > 30)
& (dataframe["rsi"] < 70)
& (dataframe["volume"] > dataframe["volume_sma"] * 1.3)
& (dataframe["macd_hist"] < 0)
& (dataframe["adx"] > 20)
),
["enter_short", "enter_tag"],
] = (1, "a52_short_momentum")
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""Generate exit signals — require confirmation, not single triggers"""
# Exit long: strong reversal confirmed by multiple indicators
dataframe.loc[
(
(dataframe["direction_score"] < -0.4)
& (dataframe["macd_hist"] < 0)
)
| (
(dataframe["rsi"] > 78)
& (dataframe["close"] > dataframe["bb_upper"])
),
["exit_long", "exit_tag"],
] = (1, "a52_exit_reversal")
# Exit short: strong reversal confirmed
dataframe.loc[
(
(dataframe["direction_score"] > 0.4)
& (dataframe["macd_hist"] > 0)
)
| (
(dataframe["rsi"] < 22)
& (dataframe["close"] < dataframe["bb_lower"])
),
["exit_short", "exit_tag"],
] = (1, "a52_exit_reversal")
return dataframe
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time, entry_tag, side: str,
**kwargs) -> bool:
"""Block entry in choppy/fading conditions"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) == 0:
return True
last = dataframe.iloc[-1]
adx = last.get("adx", 0)
slope = last.get("ema_slope", 0)
# Block long in fading downtrend
if side == "long" and adx > 25 and slope < -0.15:
return False
# Block short in strong uptrend
if side == "short" and adx > 25 and slope > 0.15:
return False
return True
def custom_stake_amount(self, pair: str, current_time, current_rate: float,
proposed_stake: float, min_stake, max_stake,
leverage: float, entry_tag, side: str, **kwargs) -> float:
"""Apply position sizing coefficient c"""
return proposed_stake * float(self.position_c.value)