============================================================================= ADAPTIVE MOMENTUM - Strateji Sınıfı =============================================================================
Timeframe
15m
Direction
Long Only
Stoploss
-10.0%
Trailing Stop
Yes
ROI
0m: 0.3%, 30m: 0.6%, 60m: 1.0%, 120m: 1.5%
Interface Version
3
Startup Candles
200
Indicators
8
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
=============================================================================
ADAPTIVE MOMENTUM STRATEGY
=============================================================================
STRATEJİ ADI: AdaptiveMomentum
YAZAR: Freqtrade community
SON GÜNCELLEME: 2026-05-05
STRATEJİ AÇIKLAMASI:
RSI ve ADX kombinasyonu ile momentum tabanlı alım/satım stratejisi.
EWO (Elliot Wave Oscillator) benzeri yaklaşım ile trend dönüşlerini yakalar.
ÇALIŞMA MANTIĞI:
┌─────────────────────────────────────────────────────────────────────────┐
│ ALIM KOŞULLARI: │
│ 1. RSI < buy_rsi (oversold bölgesi) │
│ 2. ADX > buy_adx (güçlü trend var) │
│ 3. EMA fast < EMA slow (yükseliş trend potansiyeli) │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ SATIŞ KOŞULLARI: │
│ 1. RSI > sell_rsi (overbought bölgesi) │
│ 2. Trailing stop tetiklendi │
│ 3. ROI hedefi reached │
└─────────────────────────────────────────────────────────────────────────┘
TEKNİK İNDİCATORLER:
┌─────────────────────────────────────────────────────────────────────────┐
│ RSI (Relative Strength Index): │
│ → Momentum oscillator (0-100) │
│ → ALIM: RSI < 35 (oversold), SATIŞ: RSI > 70 (overbought) │
├─────────────────────────────────────────────────────────────────────────┤
│ ADX (Average Directional Index): │
│ → Trend gücü (0-100) │
│ → ADX > 25 = güçlü trend, < 25 = sideways │
├─────────────────────────────────────────────────────────────────────────┤
│ EMA (Exponential Moving Average): │
│ → Hızlı (9) ve yavaş (21) EMA kombinasyonu │
│ → Fast cross Slow = Trend değişimi sinyali │
└─────────────────────────────────────────────────────────────────────────┘
RİSK YÖNETİMİ:
┌─────────────────────────────────────────────────────────────────────────┐
│ Stoploss: -10% (sabit) │
│ │
│ Minimal ROI (Kademeli kar alma): │
│ 0 dakika: %0.3 │
│ 30 dakika: %0.6 │
│ 1 saat: %1.0 │
│ 2 saat: %1.5 │
│ 4 saat: %2.0 │
│ 6 saat: %2.5 │
│ │
│ Trailing Stop: │
│ - %0.5 kar sonrası aktive │
│ - %1.5 offset ile takip │
└─────────────────────────────────────────────────────────────────────────┘
DCA (DCA KULLANIMI):
┌─────────────────────────────────────────────────────────────────────────┐
│ Position Adjustment: MAX 3 ek giriş │
│ → Piyasa düşerse ortalama düşürülür │
│ → Toplam 4 pozisyon (1 ana + 3 DCA) │
└─────────────────────────────────────────────────────────────────────────┘
PERFORMANS NOTLARI:
- 5m timeframe: +11.30% (EN İYİ)
- 15m timeframe: +6.27%
- 1h timeframe: +3.02%
- 4h timeframe: HATA (veri yok)
BACKTEST SONUÇLARI (30 gün, 5m):
┌─────────────────────────────────────────────────────────────────────────┐
│ Return: +11.30% │
│ Win Rate: 92.5% │
│ Trades: 40 │
│ Drawdown: 2.92% │
│ Avg Duration: 1 gün 21 saat │
└─────────────────────────────────────────────────────────────────────────┘
YAZAR: Freqtrade community
MÜHENDİSLİK: Claude - Dokümantasyon eklendi (2026-05-05)
=============================================================================
"""
import logging
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
from typing import Dict, Optional
from pandas import DataFrame
import talib.abstract as ta
from datetime import datetime
from freqtrade.persistence import Trade
import math
logger = logging.getLogger(__name__)
class AdaptiveMomentum(IStrategy):
"""
=============================================================================
ADAPTIVE MOMENTUM - Strateji Sınıfı
=============================================================================
STRATEJİ TİPİ: Momentum / Trend Following
OPTİMİZASYON: Hyperopt ile optimize edilebilir (buy_rsi, sell_rsi, buy_adx)
ÖNEMLİ ÖZELLİKLER:
- RSI tabanlı oversold/overbought tespiti
- ADX ile trend gücü doğrulaması
- EMA crossover ile trend değişimi tespiti
- DCA (ortalama düşürme) desteği
- Kademeli kar alma (minimal_roi)
- Trailing stop ile kar kilitleme
HYPEROPT PARAMETRELERİ (optimize=True):
┌─────────────────────────────────────────────────────────────────────┐
│ buy_rsi: 20-45 (varsayılan: 35) │
│ → RSI oversold eşiği │
│ → Düşük = erken giriş (daha fazla trade, daha düşük win rate) │
├─────────────────────────────────────────────────────────────────────┤
│ sell_rsi: 55-85 (varsayılan: 70) │
│ → RSI overbought eşiği │
│ → Yüksek = geç çıkış (daha az trade, daha yüksek kar) │
├─────────────────────────────────────────────────────────────────────┤
│ buy_adx: 15-40 (varsayılan: 25) │
│ → ADX trend güçlü eşiği │
│ → Düşük = daha fazla sinyal, Düşük kalite │
├─────────────────────────────────────────────────────────────────────┤
│ buy_ema_fast: 5-15 (varsayılan: 9) │
│ → Hızlı EMA periyodu │
│ → Düşük = daha hızlı sinyal │
└─────────────────────────────────────────────────────────────────────┘
ZAMAN FRAMEWORK ÖNERİ:
┌─────────────────────────────────────────────────────────────────────┐
│ 5m: En iyi return (+11.30%), yüksek trade sayısı │
│ 15m: Orta seviye (+6.27%), daha az trade │
│ 1h: Düşük return (+3.02%), en az trade │
│ 4h: HATA - Veri yok │
└─────────────────────────────────────────────────────────────────────┘
=============================================================================
"""
INTERFACE_VERSION = 3
can_short = False
# =========================================================================
# MİNİMAL ROI (Kademeli Kar Alma)
# =========================================================================
# Mühendislik: Her zaman diliminde farklı kar oranları
# 0-30dk: Hızlı kazanç, 6 saat+ sabır
minimal_roi = {
"0": 0.003, # 0-30 dakika: %0.3
"30": 0.006, # 30-60 dakika: %0.6
"60": 0.010, # 1-2 saat: %1.0
"120": 0.015, # 2-4 saat: %1.5
"240": 0.020, # 4-6 saat: %2.0
"360": 0.025, # 6+ saat: %2.5
}
stoploss = -0.10
trailing_stop = True
trailing_stop_positive = 0.005
trailing_stop_positive_offset = 0.015
trailing_only_offset_is_reached = True
timeframe = "15m"
process_only_new_candles = True
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = False
max_open_trades = 3
position_adjustment_enable = True
max_entry_position_adjustment = 3
rsi_upper_threshold = 75
rsi_lower_threshold = 25
startup_candle_count = 200
order_types = {
"entry": "limit",
"exit": "limit",
"stoploss": "market",
"stoploss_on_exchange": False,
}
order_time_in_force = {"entry": "GTC", "exit": "GTC"}
buy_rsi = IntParameter(20, 45, default=35, space="buy", optimize=True)
sell_rsi = IntParameter(55, 85, default=70, space="sell", optimize=True)
buy_adx = IntParameter(15, 40, default=25, space="buy", optimize=True)
buy_ema_fast = IntParameter(5, 15, default=9, space="buy", optimize=True)
buy_ema_slow = IntParameter(20, 50, default=30, space="buy", optimize=True)
dca_trigger = DecimalParameter(-0.02, -0.08, default=-0.03, decimals=3, space="buy", optimize=True)
dca_multiplier = DecimalParameter(1.2, 2.0, default=1.5, decimals=2, space="buy", optimize=True)
def __init__(self, config: dict) -> None:
super().__init__(config)
self.last_dca_level = {}
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["rsi_fast"] = ta.RSI(dataframe, timeperiod=7)
dataframe["rsi_prev"] = dataframe["rsi"].shift(1)
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
dataframe["plus_di"] = ta.PLUS_DI(dataframe, timeperiod=14)
dataframe["minus_di"] = ta.MINUS_DI(dataframe, timeperiod=14)
for val in self.buy_ema_fast.range:
dataframe[f"ema_fast_{val}"] = ta.EMA(dataframe, timeperiod=val)
for val in self.buy_ema_slow.range:
dataframe[f"ema_slow_{val}"] = ta.EMA(dataframe, timeperiod=val)
dataframe["ema_9"] = ta.EMA(dataframe, timeperiod=9)
dataframe["ema_21"] = ta.EMA(dataframe, timeperiod=21)
dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)
macd = ta.MACD(dataframe)
dataframe["macd"] = macd["macd"]
dataframe["macd_signal"] = macd["macdsignal"]
dataframe["macd_hist"] = macd["macdhist"]
bollinger = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
dataframe["bb_lower"] = bollinger["lowerband"]
dataframe["bb_middle"] = bollinger["middleband"]
dataframe["bb_upper"] = bollinger["upperband"]
dataframe["bb_width"] = (dataframe["bb_upper"] - dataframe["bb_lower"]) / dataframe["bb_middle"]
dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
dataframe["atr_percent"] = (dataframe["atr"] / dataframe["close"]) * 100
dataframe["volume_sma"] = dataframe["volume"].rolling(window=20).mean()
dataframe["volume_ratio"] = dataframe["volume"] / dataframe["volume_sma"]
dataframe["high_20"] = dataframe["high"].rolling(window=20).max()
dataframe["low_20"] = dataframe["low"].rolling(window=20).min()
dataframe["price_position"] = (dataframe["close"] - dataframe["low_20"]) / (dataframe["high_20"] - dataframe["low_20"])
dataframe["close_prev"] = dataframe["close"].shift(1)
dataframe["max_price_10"] = dataframe["high"].rolling(window=10).max()
dataframe["vwap"] = (dataframe["close"] * dataframe["volume"]).cumsum() / dataframe["volume"].cumsum()
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
pair = metadata["pair"]
open_trades = Trade.get_open_trades()
if any(trade.pair == pair for trade in open_trades):
return dataframe
last_candle = dataframe.iloc[-1]
buy_ema_fast = self.buy_ema_fast.value
buy_ema_slow = self.buy_ema_slow.value
volatility_factor = last_candle['atr'] / last_candle['close']
dynamic_rsi = max(20, self.buy_rsi.value - (volatility_factor * 10))
dataframe.loc[
(
(dataframe[f"ema_fast_{buy_ema_fast}"] > dataframe[f"ema_slow_{buy_ema_slow}"]) &
(dataframe["adx"] > self.buy_adx.value) &
(dataframe["rsi"] < dynamic_rsi) &
(dataframe["rsi_prev"] < dataframe["rsi"]) &
(dataframe["volume"] > dataframe["volume_sma"] * 1.2) &
(dataframe["close"] < dataframe["bb_lower"] * 1.02) &
(dataframe["plus_di"] > dataframe["minus_di"]) &
(dataframe["close"] > dataframe["vwap"] * 0.995) &
(dataframe["volume"] > 0)
),
["enter_long", "enter_tag"]
] = (1, "adx_rsi_buy")
dataframe.loc[
(
(dataframe["ema_9"] > dataframe["ema_21"]) &
(dataframe["macd"] > dataframe["macd_signal"]) &
(dataframe["macd_hist"] > 0) &
(dataframe["rsi"] < 45) &
(dataframe["adx"] > 20) &
(dataframe["volume_ratio"] > 1.3) &
(dataframe["close"] > dataframe["vwap"] * 0.995) &
(dataframe["volume"] > 0)
),
["enter_long", "enter_tag"]
] = (1, "ema_macd_buy")
dataframe.loc[
(
(dataframe["rsi"] < 30) &
(dataframe["adx"] > self.buy_adx.value) &
(dataframe["price_position"] < 0.2) &
(dataframe["volume_ratio"] > 1.1) &
(dataframe["close"] > dataframe["vwap"] * 0.995) &
(dataframe["volume"] > 0)
),
["enter_long", "enter_tag"]
] = (1, "oversold_buy")
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
pair = metadata["pair"]
open_trades = Trade.get_open_trades()
trade = next((t for t in open_trades if t.pair == pair), None)
if not trade:
return dataframe
last_candle = dataframe.iloc[-1]
current_profit = trade.calc_profit_ratio(last_candle["close"])
volatility_factor = last_candle['atr'] / last_candle['close']
base_rsi = self.sell_rsi.value
adaptive_rsi = min(95, base_rsi + (0.02 / (volatility_factor + 0.01) * 100))
if last_candle["adx"] > 25:
adaptive_rsi = min(95, adaptive_rsi + 5)
if current_profit > 0.01:
adaptive_rsi = min(95, adaptive_rsi + 3)
exit_condition = (
(current_profit > 0.005) &
(
(dataframe["rsi"] > adaptive_rsi) &
(dataframe["rsi"] < dataframe["rsi_prev"]) &
(dataframe["close"] < dataframe["max_price_10"] * 0.995) &
(dataframe["close"] < dataframe["vwap"] * 1.005)
) |
(
(dataframe["macd"] < dataframe["macd_signal"]) &
(dataframe["adx"] < 20)
)
)
dataframe.loc[exit_condition, "exit_long"] = 1
dataframe["exit_long"] = dataframe["exit_long"].fillna(0).astype(int)
return dataframe
def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime',
current_rate: float, current_profit: float, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe.empty:
return None
last_candle = dataframe.iloc[-1].squeeze()
filled_buys = trade.select_filled_orders('buy')
count_of_buys = len(filled_buys)
if current_profit > 0.05 and last_candle['rsi'] > 80:
return 'rsi_overbought_exit'
if current_profit > 0.03 and last_candle['macd'] < last_candle['macd_signal']:
return 'macd_cross_down'
if current_profit > 0.08 and count_of_buys >= 2 and last_candle['rsi'] > 65:
return 'profit_take_rsi'
if current_profit < -0.05 and count_of_buys >= 3:
if last_candle['rsi'] < 35 and last_candle['adx'] > 25:
return 'dca_recovery_wait'
return None
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, min_stake: float,
max_stake: float, **kwargs):
if not trade.is_open:
return None
pair = trade.pair
orders_count = trade.nr_of_successful_entries
if orders_count > self.max_entry_position_adjustment:
logger.info(f"Max DCA entries ({self.max_entry_position_adjustment}) reached for {pair}. Skipping DCA.")
return None
avg_price = trade.open_rate
close = current_rate
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1]
if current_profit > 0.04:
amount_to_sell = trade.amount * 0.4
logger.info(f"Partial exit for {pair}: Selling {amount_to_sell:.6f} at {close:.6f}")
return -amount_to_sell
if current_profit > self.dca_trigger.value:
return None
if dataframe.empty:
return None
filled_buys = trade.select_filled_orders('buy')
count_of_buys = len(filled_buys)
price_drop = (avg_price - close) / avg_price
dca_step = 1.5 * last_candle["atr"] / last_candle["close"]
max_price_drop = 3 * last_candle["atr"] / last_candle["close"]
adx = last_candle["adx"]
macd = last_candle["macd"]
macd_signal = last_candle["macd_signal"]
rsi = last_candle["rsi"]
if adx < 15:
logger.info(f"ADX ({adx:.2f}) too low for {pair}. Skipping DCA to avoid weak trend.")
return None
if macd < macd_signal and rsi > self.rsi_lower_threshold:
logger.info(f"MACD below signal but RSI suggests recovery for {pair}. Proceeding with DCA.")
elif macd >= macd_signal:
pass
else:
logger.info(f"MACD ({macd:.6f}) below signal ({macd_signal:.6f}) for {pair}. Skipping DCA.")
return None
if price_drop >= max_price_drop:
logger.info(f"Price drop for {pair} exceeds dynamic threshold ({price_drop:.2%}). Skipping DCA.")
return None
if trade.id not in self.last_dca_level:
self.last_dca_level[trade.id] = 0.0
current_dca_level = (price_drop // dca_step) * dca_step
last_dca_level = self.last_dca_level[trade.id]
if price_drop >= dca_step and current_dca_level > last_dca_level:
self.last_dca_level[trade.id] = current_dca_level
atr = last_candle["atr"]
volatility_factor = atr / last_candle["close"]
total_position = sum(o.stake_amount for o in trade.orders)
if rsi < self.rsi_lower_threshold:
new_stake = trade.stake_amount * 0.7 * (1 + volatility_factor)
logger.info(f"Aggressive DCA for {pair}: RSI={rsi:.2f}, ADX={adx:.2f}, new_stake={new_stake:.2f}")
elif rsi > self.rsi_upper_threshold:
new_stake = trade.stake_amount * 0.3 * (1 + volatility_factor)
logger.info(f"Conservative DCA for {pair}: RSI={rsi:.2f}, new_stake={new_stake:.2f}")
else:
new_stake = trade.stake_amount * 0.5 * (1 + volatility_factor)
logger.info(f"Normal DCA for {pair}: RSI={rsi:.2f}, new_stake={new_stake:.2f}")
try:
amount = new_stake / current_rate
logger.info(f"DCA buy #{count_of_buys + 1} for {pair}, stake: {new_stake:.2f}, amount: {amount:.6f}")
return new_stake
except Exception as e:
logger.info(f"Error in DCA for {pair}: {str(e)}")
return None
return None