Advanced Futures Trading Strategy
Timeframe
5m
Direction
Long Only
Stoploss
-4.0%
Trailing Stop
Yes
ROI
0m: 15.0%, 30m: 8.0%, 60m: 5.0%, 120m: 3.0%
Interface Version
3
Startup Candles
N/A
Indicators
11
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs ---
import numpy as np
import pandas as pd
from pandas import DataFrame
from datetime import datetime
from typing import Optional, Union
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
IntParameter, IStrategy, merge_informative_pair)
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import pandas_ta as pta
from technical import qtpylib
class AdvancedFuturesStrategy(IStrategy):
"""
Advanced Futures Trading Strategy
Strategi ini dirancang untuk futures trading dengan fokus pada:
- High Average/Cumulative/Total Profit %
- High Win Rate %
- Low Drawdown %
- High Sharpe Ratio
Menggunakan kombinasi indikator teknikal dan manajemen risiko yang ketat
"""
# Strategy interface version
INTERFACE_VERSION = 3
# Timeframe untuk strategi utama
timeframe = '5m'
# Timeframe informative untuk konfirmasi trend
informative_timeframe = '15m'
# ROI table - Conservative approach untuk futures
minimal_roi = {
"0": 0.15, # 15% profit untuk exit segera
"30": 0.08, # 8% setelah 30 menit
"60": 0.05, # 5% setelah 1 jam
"120": 0.03, # 3% setelah 2 jam
"240": 0.02, # 2% setelah 4 jam
"480": 0.01 # 1% setelah 8 jam
}
# Stoploss - Ketat untuk mengurangi drawdown
stoploss = -0.04 # 4% stop loss
# Trailing stop
trailing_stop = True
trailing_stop_positive = 0.02 # Mulai trailing di +2%
trailing_stop_positive_offset = 0.025 # Offset 0.5% dari entry
trailing_only_offset_is_reached = True
# Futures leverage (hati-hati dengan leverage tinggi)
leverage_num = 3
# Position sizing
position_adjustment_enable = True
max_entry_position_adjustment = 2
# Protections
protections = [
{
"method": "StoplossGuard",
"lookback_period_candles": 50,
"trade_limit": 2,
"stop_duration_candles": 20,
"only_per_pair": False
},
{
"method": "MaxDrawdown",
"lookback_period_candles": 200,
"trade_limit": 10,
"stop_duration_candles": 100,
"max_allowed_drawdown": 0.15
},
{
"method": "LowProfitPairs",
"lookback_period_candles": 400,
"trade_limit": 8,
"stop_duration_candles": 60,
"required_profit": -0.02
}
]
# Hyperopt parameters
# Entry parameters
rsi_buy_threshold = IntParameter(25, 40, default=32, space="buy")
rsi_sell_threshold = IntParameter(60, 75, default=68, space="sell")
bb_buy_threshold = DecimalParameter(0.98, 1.02, default=1.0, space="buy")
bb_sell_threshold = DecimalParameter(0.98, 1.02, default=1.0, space="sell")
adx_threshold = IntParameter(20, 35, default=25, space="buy")
# Volume parameters
volume_factor_buy = DecimalParameter(1.2, 2.5, default=1.8, space="buy")
volume_factor_sell = DecimalParameter(1.2, 2.5, default=1.8, space="sell")
# EMA parameters
ema_fast = IntParameter(8, 21, default=12, space="buy")
ema_slow = IntParameter(21, 50, default=34, space="buy")
# MACD parameters
macd_fast = IntParameter(8, 15, default=12, space="buy")
macd_slow = IntParameter(21, 30, default=26, space="buy")
macd_signal = IntParameter(7, 12, default=9, space="buy")
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
side: str, **kwargs) -> float:
return self.leverage_num
def informative_pairs(self):
pairs = self.dp.current_whitelist()
informative_pairs = [(pair, self.informative_timeframe) for pair in pairs]
return informative_pairs
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Dapatkan data timeframe yang lebih tinggi
informative = self.dp.get_pair_dataframe(pair=metadata['pair'],
timeframe=self.informative_timeframe)
informative = self.populate_indicators_informative(informative, metadata)
dataframe = merge_informative_pair(dataframe, informative,
self.timeframe, self.informative_timeframe, ffill=True)
# RSI
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=7)
# Bollinger Bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lower'] = bollinger['lower']
dataframe['bb_middle'] = bollinger['mid']
dataframe['bb_upper'] = bollinger['upper']
dataframe['bb_percent'] = (dataframe['close'] - dataframe['bb_lower']) / (dataframe['bb_upper'] - dataframe['bb_lower'])
dataframe['bb_width'] = (dataframe['bb_upper'] - dataframe['bb_lower']) / dataframe['bb_middle']
# EMAs
dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=self.ema_fast.value)
dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=self.ema_slow.value)
dataframe['ema_200'] = ta.EMA(dataframe, timeperiod=200)
# MACD
macd = ta.MACD(dataframe, fastperiod=self.macd_fast.value,
slowperiod=self.macd_slow.value, signalperiod=self.macd_signal.value)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# ADX untuk mengukur kekuatan trend
dataframe['adx'] = ta.ADX(dataframe, timeperiod=14)
dataframe['di_plus'] = ta.PLUS_DI(dataframe, timeperiod=14)
dataframe['di_minus'] = ta.MINUS_DI(dataframe, timeperiod=14)
# Volume indicators
dataframe['volume_sma'] = ta.SMA(dataframe['volume'], timeperiod=20)
dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_sma']
# Stochastic
stoch = ta.STOCH(dataframe, fastk_period=14, slowk_period=3, slowd_period=3)
dataframe['stoch_k'] = stoch['slowk']
dataframe['stoch_d'] = stoch['slowd']
# Williams %R
dataframe['williams_r'] = ta.WILLR(dataframe, timeperiod=14)
# Support and Resistance levels
dataframe['pivot'] = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3
dataframe['r1'] = 2 * dataframe['pivot'] - dataframe['low']
dataframe['s1'] = 2 * dataframe['pivot'] - dataframe['high']
# ATR untuk volatilitas
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
dataframe['atr_percent'] = (dataframe['atr'] / dataframe['close']) * 100
# Heikin Ashi untuk smooth trend
heikinashi = qtpylib.heikinashi(dataframe)
dataframe['ha_open'] = heikinashi['open']
dataframe['ha_close'] = heikinashi['close']
dataframe['ha_high'] = heikinashi['high']
dataframe['ha_low'] = heikinashi['low']
return dataframe
def populate_indicators_informative(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Indikator untuk timeframe yang lebih tinggi (konfirmasi trend)
dataframe['ema_50'] = ta.EMA(dataframe, timeperiod=50)
dataframe['ema_100'] = ta.EMA(dataframe, timeperiod=100)
dataframe['rsi_higher'] = ta.RSI(dataframe, timeperiod=14)
# Trend direction
dataframe['trend_up'] = (dataframe['ema_50'] > dataframe['ema_100']) & \
(dataframe['close'] > dataframe['ema_50'])
dataframe['trend_down'] = (dataframe['ema_50'] < dataframe['ema_100']) & \
(dataframe['close'] < dataframe['ema_50'])
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions_long = [
# Trend confirmation dari timeframe lebih tinggi
(dataframe[f'trend_up_{self.informative_timeframe}'] == True),
# RSI oversold tapi tidak terlalu dalam
(dataframe['rsi'] > self.rsi_buy_threshold.value),
(dataframe['rsi'] < 50),
# Price near lower Bollinger Band
(dataframe['bb_percent'] < self.bb_buy_threshold.value),
(dataframe['bb_percent'] > 0.1), # Tidak terlalu dekat dengan lower band
# EMA crossover atau price above fast EMA
((dataframe['ema_fast'] > dataframe['ema_slow']) |
(dataframe['close'] > dataframe['ema_fast'])),
# MACD bullish
(dataframe['macd'] > dataframe['macdsignal']),
(dataframe['macdhist'] > 0),
# ADX menunjukkan trend yang kuat
(dataframe['adx'] > self.adx_threshold.value),
(dataframe['di_plus'] > dataframe['di_minus']),
# Volume confirmation
(dataframe['volume_ratio'] > self.volume_factor_buy.value),
# Price above 200 EMA (long term trend)
(dataframe['close'] > dataframe['ema_200']),
# Stochastic tidak overbought
(dataframe['stoch_k'] < 80),
# Williams %R oversold
(dataframe['williams_r'] < -20),
(dataframe['williams_r'] > -80),
# Volatilitas tidak terlalu tinggi
(dataframe['atr_percent'] < 5),
# Heikin Ashi bullish
(dataframe['ha_close'] > dataframe['ha_open'])
]
conditions_short = [
# Trend confirmation dari timeframe lebih tinggi
(dataframe[f'trend_down_{self.informative_timeframe}'] == True),
# RSI overbought tapi tidak terlalu dalam
(dataframe['rsi'] < self.rsi_sell_threshold.value),
(dataframe['rsi'] > 50),
# Price near upper Bollinger Band
(dataframe['bb_percent'] > self.bb_sell_threshold.value),
(dataframe['bb_percent'] < 0.9), # Tidak terlalu dekat dengan upper band
# EMA crossover atau price below fast EMA
((dataframe['ema_fast'] < dataframe['ema_slow']) |
(dataframe['close'] < dataframe['ema_fast'])),
# MACD bearish
(dataframe['macd'] < dataframe['macdsignal']),
(dataframe['macdhist'] < 0),
# ADX menunjukkan trend yang kuat
(dataframe['adx'] > self.adx_threshold.value),
(dataframe['di_minus'] > dataframe['di_plus']),
# Volume confirmation
(dataframe['volume_ratio'] > self.volume_factor_sell.value),
# Price below 200 EMA (long term trend)
(dataframe['close'] < dataframe['ema_200']),
# Stochastic tidak oversold
(dataframe['stoch_k'] > 20),
# Williams %R overbought
(dataframe['williams_r'] > -20),
(dataframe['williams_r'] < -80),
# Volatilitas tidak terlalu tinggi
(dataframe['atr_percent'] < 5),
# Heikin Ashi bearish
(dataframe['ha_close'] < dataframe['ha_open'])
]
# Long entries
dataframe.loc[
(
reduce(lambda x, y: x & y, conditions_long)
),
['enter_long', 'enter_tag']
] = (1, 'long_entry')
# Short entries
dataframe.loc[
(
reduce(lambda x, y: x & y, conditions_short)
),
['enter_short', 'enter_tag']
] = (1, 'short_entry')
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions_exit_long = [
# RSI overbought
(dataframe['rsi'] > 70) |
# Price near upper Bollinger Band
(dataframe['bb_percent'] > 0.85) |
# MACD bearish crossover
((dataframe['macd'] < dataframe['macdsignal']) &
(dataframe['macdhist'] < 0)) |
# Stochastic overbought
(dataframe['stoch_k'] > 80) |
# Williams %R overbought
(dataframe['williams_r'] > -20) |
# Trend berubah di timeframe lebih tinggi
(dataframe[f'trend_down_{self.informative_timeframe}'] == True)
]
conditions_exit_short = [
# RSI oversold
(dataframe['rsi'] < 30) |
# Price near lower Bollinger Band
(dataframe['bb_percent'] < 0.15) |
# MACD bullish crossover
((dataframe['macd'] > dataframe['macdsignal']) &
(dataframe['macdhist'] > 0)) |
# Stochastic oversold
(dataframe['stoch_k'] < 20) |
# Williams %R oversold
(dataframe['williams_r'] < -80) |
# Trend berubah di timeframe lebih tinggi
(dataframe[f'trend_up_{self.informative_timeframe}'] == True)
]
# Exit long positions
dataframe.loc[
(
reduce(lambda x, y: x | y, conditions_exit_long)
),
['exit_long', 'exit_tag']
] = (1, 'exit_long_signal')
# Exit short positions
dataframe.loc[
(
reduce(lambda x, y: x | y, conditions_exit_short)
),
['exit_short', 'exit_tag']
] = (1, 'exit_short_signal')
return dataframe
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
"""
Dynamic stoploss berdasarkan ATR dan profit
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
# ATR-based dynamic stop
atr_multiplier = 2.0
if trade.is_short:
atr_stop = (current_rate + (last_candle['atr'] * atr_multiplier)) / trade.open_rate - 1
else:
atr_stop = (current_rate - (last_candle['atr'] * atr_multiplier)) / trade.open_rate - 1
# Trailing stop yang lebih agresif saat profit tinggi
if current_profit > 0.05: # 5% profit
trailing_stop = -0.02 # 2% trailing
elif current_profit > 0.03: # 3% profit
trailing_stop = -0.025 # 2.5% trailing
else:
trailing_stop = self.stoploss
return max(atr_stop, trailing_stop)
def adjust_trade_position(self, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs) -> Optional[float]:
"""
Position adjustment untuk averaging down/up
"""
if current_profit > -0.02: # Hanya adjust jika loss tidak lebih dari 2%
return None
# Hanya adjust sekali
if trade.nr_of_successful_entries > 1:
return None
# Check if trend masih sesuai
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
if trade.is_short:
if not last_candle[f'trend_down_{self.informative_timeframe}']:
return None
else:
if not last_candle[f'trend_up_{self.informative_timeframe}']:
return None
# Adjust dengan ukuran yang lebih kecil
return trade.stake_amount * 0.5
def confirm_trade_entry(self, pair: str, order_type: str, amount: float,
rate: float, time_in_force: str, current_time: datetime,
entry_tag: Optional[str], side: str, **kwargs) -> bool:
"""
Konfirmasi final sebelum entry
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) < 1:
return False
last_candle = dataframe.iloc[-1].squeeze()
# Check volatilitas tidak terlalu tinggi
if last_candle['atr_percent'] > 8:
return False
# Check volume
if last_candle['volume_ratio'] < 1.2:
return False
return True