Advanced Multi-Timeframe Futures Swing Trading Strategy with Order Flow Analysis
Timeframe
15m
Direction
Long & Short
Stoploss
-6.0%
Trailing Stop
Yes
ROI
0m: 20.0%, 720m: 15.0%, 1440m: 10.0%, 2880m: 6.0%
Interface Version
3
Startup Candles
N/A
Indicators
7
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
Advanced Multi-Timeframe Futures Swing Strategy for Freqtrade
=============================================================
This strategy incorporates:
- Higher Timeframe (HTF) trend analysis
- Distribution & Accumulation zones
- Support & Resistance detection
- Long wick analysis (rejection patterns)
- Order flow confirmation
- Smart Money Concepts (SMC)
"""
from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter, merge_informative_pair
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from functools import reduce
import numpy as np
from datetime import datetime
class AdvancedFuturesSwingStrategy(IStrategy):
"""
Advanced Multi-Timeframe Futures Swing Trading Strategy
with Order Flow Analysis
"""
INTERFACE_VERSION = 3
# Base timeframe for entry/exit
timeframe = '15m'
# Can short
can_short = True
# ROI table
minimal_roi = {
"0": 0.20,
"720": 0.15,
"1440": 0.10,
"2880": 0.06
}
# Stoploss
stoploss = -0.06
# Trailing stop
trailing_stop = True
trailing_stop_positive = 0.015
trailing_stop_positive_offset = 0.03
trailing_only_offset_is_reached = True
# Leverage
leverage_num = 3
# ===== HYPEROPT PARAMETERS =====
# These will be optimized quarterly and auto-injected
# Trend Parameters
htf_trend_ema = IntParameter(50, 200, default=100, space='buy', optimize=True)
accumulation_period = IntParameter(20, 50, default=30, space='buy', optimize=True)
# Wick Analysis
wick_threshold = DecimalParameter(0.3, 0.7, default=0.5, space='buy', decimals=2, optimize=True)
upper_wick_min = DecimalParameter(0.4, 0.8, default=0.6, space='buy', decimals=2, optimize=True)
lower_wick_min = DecimalParameter(0.4, 0.8, default=0.6, space='buy', decimals=2, optimize=True)
# Volume Parameters
volume_surge = DecimalParameter(1.5, 3.0, default=2.0, space='buy', decimals=1, optimize=True)
volume_confirmation = DecimalParameter(1.2, 2.5, default=1.5, space='buy', decimals=1, optimize=True)
# Support/Resistance
sr_lookback = IntParameter(50, 100, default=75, space='buy', optimize=True)
sr_threshold = DecimalParameter(0.002, 0.01, default=0.005, space='buy', decimals=3, optimize=True)
# RSI Parameters
rsi_buy_min = IntParameter(30, 45, default=40, space='buy', optimize=True)
rsi_buy_max = IntParameter(50, 65, default=65, space='buy', optimize=True)
rsi_sell_min = IntParameter(60, 75, default=65, space='sell', optimize=True)
rsi_overbought = IntParameter(65, 80, default=70, space='sell', optimize=True)
# ADX Parameters
adx_trend_min = IntParameter(15, 30, default=20, space='buy', optimize=True)
adx_strong_trend = IntParameter(25, 40, default=25, space='buy', optimize=True)
adx_weak_trend = IntParameter(10, 20, default=15, space='sell', optimize=True)
# EMA Parameters
ema_fast = IntParameter(8, 25, default=20, space='buy', optimize=True)
ema_slow = IntParameter(40, 100, default=50, space='buy', optimize=True)
# Order Flow
delta_threshold = DecimalParameter(0, 1000, default=100, space='buy', decimals=0, optimize=True)
cumulative_delta_period = IntParameter(10, 30, default=20, space='buy', optimize=True)
# Accumulation/Distribution
volatility_threshold = DecimalParameter(0.5, 0.9, default=0.7, space='buy', decimals=1, optimize=True)
volume_acc_multiplier = DecimalParameter(1.1, 1.5, default=1.2, space='buy', decimals=1, optimize=True)
# Exit Parameters
exit_volume_low = DecimalParameter(0.6, 0.9, default=0.8, space='sell', decimals=1, optimize=True)
exit_rsi_high = IntParameter(55, 70, default=60, space='sell', optimize=True)
startup_candle_count: int = 400
use_exit_signal = True
exit_profit_only = False
def informative_pairs(self):
"""
Define additional informative pairs for HTF analysis
"""
pairs = self.dp.current_whitelist()
informative_pairs = []
# Add higher timeframes for each pair
for pair in pairs:
informative_pairs.append((pair, '1h')) # Higher timeframe 1
informative_pairs.append((pair, '4h')) # Higher timeframe 2
informative_pairs.append((pair, '1d')) # Higher timeframe 3
return informative_pairs
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Generate indicators including HTF analysis
"""
# ===== BASE TIMEFRAME INDICATORS =====
# Price Action
dataframe['hl_avg'] = (dataframe['high'] + dataframe['low']) / 2
dataframe['body'] = abs(dataframe['close'] - dataframe['open'])
dataframe['range'] = dataframe['high'] - dataframe['low']
# Upper and Lower Wicks
dataframe['upper_wick'] = dataframe['high'] - dataframe[['open', 'close']].max(axis=1)
dataframe['lower_wick'] = dataframe[['open', 'close']].min(axis=1) - dataframe['low']
# Wick to body ratio (rejection signals)
dataframe['upper_wick_ratio'] = dataframe['upper_wick'] / (dataframe['range'] + 1e-10)
dataframe['lower_wick_ratio'] = dataframe['lower_wick'] / (dataframe['range'] + 1e-10)
# Long wick detection
dataframe['long_upper_wick'] = (dataframe['upper_wick_ratio'] > self.upper_wick_min.value) & \
(dataframe['upper_wick'] > dataframe['body'] * 2)
dataframe['long_lower_wick'] = (dataframe['lower_wick_ratio'] > self.lower_wick_min.value) & \
(dataframe['lower_wick'] > dataframe['body'] * 2)
# 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_100'] = ta.EMA(dataframe, timeperiod=100)
dataframe['ema_200'] = ta.EMA(dataframe, timeperiod=200)
# RSI
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
# Volume Analysis
dataframe['volume_sma'] = dataframe['volume'].rolling(window=20).mean()
dataframe['volume_ratio'] = dataframe['volume'] / (dataframe['volume_sma'] + 1e-10)
dataframe['high_volume'] = dataframe['volume_ratio'] > self.volume_surge.value
# Order Flow Approximation (using volume delta)
dataframe['buy_volume'] = dataframe['volume'] * ((dataframe['close'] - dataframe['low']) /
(dataframe['range'] + 1e-10))
dataframe['sell_volume'] = dataframe['volume'] * ((dataframe['high'] - dataframe['close']) /
(dataframe['range'] + 1e-10))
dataframe['volume_delta'] = dataframe['buy_volume'] - dataframe['sell_volume']
dataframe['cumulative_delta'] = dataframe['volume_delta'].rolling(window=self.cumulative_delta_period.value).sum()
# Volume Profile (simplified - volume at price levels)
dataframe['vwap'] = (dataframe['volume'] * dataframe['hl_avg']).rolling(window=20).sum() / \
(dataframe['volume'].rolling(window=20).sum() + 1e-10)
# ===== SUPPORT & RESISTANCE DETECTION =====
dataframe = self.detect_support_resistance(dataframe)
# ===== ACCUMULATION & DISTRIBUTION DETECTION =====
dataframe = self.detect_accumulation_distribution(dataframe)
# ADX - Trend Strength
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)
# Bollinger Bands
bollinger = qtpylib.bollinger_bands(dataframe['close'], window=20, stds=2)
dataframe['bb_lower'] = bollinger['lower']
dataframe['bb_middle'] = bollinger['mid']
dataframe['bb_upper'] = bollinger['upper']
# ATR
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
# ===== HIGHER TIMEFRAME ANALYSIS =====
# 1H Timeframe
informative_1h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1h')
informative_1h = self.populate_htf_indicators(informative_1h, '1h')
dataframe = merge_informative_pair(dataframe, informative_1h, self.timeframe, '1h', ffill=True)
# 4H Timeframe
informative_4h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='4h')
informative_4h = self.populate_htf_indicators(informative_4h, '4h')
dataframe = merge_informative_pair(dataframe, informative_4h, self.timeframe, '4h', ffill=True)
# Daily Timeframe
informative_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d')
informative_1d = self.populate_htf_indicators(informative_1d, '1d')
dataframe = merge_informative_pair(dataframe, informative_1d, self.timeframe, '1d', ffill=True)
# ===== HTF TREND CONFLUENCE =====
dataframe['htf_bullish'] = (
(dataframe['trend_1h'] == 'bullish') &
(dataframe['trend_4h'] == 'bullish') &
(dataframe['trend_1d'] == 'bullish')
)
dataframe['htf_bearish'] = (
(dataframe['trend_1h'] == 'bearish') &
(dataframe['trend_4h'] == 'bearish') &
(dataframe['trend_1d'] == 'bearish')
)
return dataframe
def populate_htf_indicators(self, dataframe: DataFrame, timeframe: str) -> DataFrame:
"""
Populate indicators for higher timeframes
"""
# EMAs for trend
dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=20)
dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=50)
dataframe['ema_trend'] = ta.EMA(dataframe, timeperiod=self.htf_trend_ema.value)
# Trend determination
dataframe['trend'] = 'neutral'
dataframe.loc[
(dataframe['close'] > dataframe['ema_trend']) &
(dataframe['ema_fast'] > dataframe['ema_slow']) &
(dataframe['ema_slow'].shift(1) < dataframe['ema_slow']),
'trend'
] = 'bullish'
dataframe.loc[
(dataframe['close'] < dataframe['ema_trend']) &
(dataframe['ema_fast'] < dataframe['ema_slow']) &
(dataframe['ema_slow'].shift(1) > dataframe['ema_slow']),
'trend'
] = 'bearish'
# ADX for trend strength
dataframe['adx'] = ta.ADX(dataframe, timeperiod=14)
# Support and Resistance on HTF
dataframe['htf_resistance'] = dataframe['high'].rolling(window=20).max()
dataframe['htf_support'] = dataframe['low'].rolling(window=20).min()
# Volume analysis
dataframe['volume_sma'] = dataframe['volume'].rolling(window=20).mean()
return dataframe
def detect_support_resistance(self, dataframe: DataFrame) -> DataFrame:
"""
Detect key support and resistance levels
"""
lookback = self.sr_lookback.value
threshold = self.sr_threshold.value
# Rolling pivot points
dataframe['pivot_high'] = dataframe['high'].rolling(window=lookback, center=True).max()
dataframe['pivot_low'] = dataframe['low'].rolling(window=lookback, center=True).min()
# Dynamic support/resistance
dataframe['resistance_1'] = dataframe['high'].rolling(window=20).max()
dataframe['resistance_2'] = dataframe['high'].rolling(window=50).max()
dataframe['support_1'] = dataframe['low'].rolling(window=20).min()
dataframe['support_2'] = dataframe['low'].rolling(window=50).min()
# Near support/resistance
dataframe['near_resistance'] = (
((dataframe['close'] >= dataframe['resistance_1'] * (1 - threshold)) &
(dataframe['close'] <= dataframe['resistance_1'] * (1 + threshold))) |
((dataframe['close'] >= dataframe['resistance_2'] * (1 - threshold)) &
(dataframe['close'] <= dataframe['resistance_2'] * (1 + threshold)))
)
dataframe['near_support'] = (
((dataframe['close'] >= dataframe['support_1'] * (1 - threshold)) &
(dataframe['close'] <= dataframe['support_1'] * (1 + threshold))) |
((dataframe['close'] >= dataframe['support_2'] * (1 - threshold)) &
(dataframe['close'] <= dataframe['support_2'] * (1 + threshold)))
)
# Order flow confirmation at support/resistance
dataframe['support_confirmed'] = (
dataframe['near_support'] &
(dataframe['cumulative_delta'] > 0) & # Positive order flow
dataframe['long_lower_wick'] # Rejection wick
)
dataframe['resistance_confirmed'] = (
dataframe['near_resistance'] &
(dataframe['cumulative_delta'] < 0) & # Negative order flow
dataframe['long_upper_wick'] # Rejection wick
)
return dataframe
def detect_accumulation_distribution(self, dataframe: DataFrame) -> DataFrame:
"""
Detect accumulation and distribution zones (Smart Money Concepts)
"""
period = self.accumulation_period.value
# Accumulation/Distribution Line (A/D Line)
dataframe['ad_line'] = ta.AD(dataframe)
dataframe['ad_line_sma'] = dataframe['ad_line'].rolling(window=period).mean()
# Price range compression (accumulation characteristic)
dataframe['price_std'] = dataframe['close'].rolling(window=period).std()
dataframe['price_std_ratio'] = dataframe['price_std'] / dataframe['price_std'].rolling(window=50).mean()
# Volume characteristics
dataframe['avg_volume'] = dataframe['volume'].rolling(window=period).mean()
dataframe['volume_std'] = dataframe['volume'].rolling(window=period).std()
# ACCUMULATION ZONE detection
# - Sideways price action (low volatility)
# - Higher volume than average
# - Positive cumulative delta
# - Price compression near support
dataframe['accumulation_zone'] = (
(dataframe['price_std_ratio'] < self.volatility_threshold.value) & # Low volatility
(dataframe['volume'] > dataframe['avg_volume'] * self.volume_acc_multiplier.value) & # Higher volume
(dataframe['cumulative_delta'] > 0) & # Buying pressure
(dataframe['close'] < dataframe['ema_slow']) & # Below mid-term MA
(dataframe['rsi'] < 50) # Not overbought
)
# DISTRIBUTION ZONE detection
# - Sideways price action at tops
# - Higher volume
# - Negative cumulative delta
# - Price compression near resistance
dataframe['distribution_zone'] = (
(dataframe['price_std_ratio'] < self.volatility_threshold.value) & # Low volatility
(dataframe['volume'] > dataframe['avg_volume'] * self.volume_acc_multiplier.value) & # Higher volume
(dataframe['cumulative_delta'] < 0) & # Selling pressure
(dataframe['close'] > dataframe['ema_slow']) & # Above mid-term MA
(dataframe['rsi'] > 50) # Not oversold
)
# Wyckoff Spring/Upthrust detection
dataframe['spring'] = ( # Fake breakdown (bullish)
(dataframe['low'] < dataframe['support_1']) &
(dataframe['close'] > dataframe['support_1']) &
dataframe['long_lower_wick'] &
(dataframe['volume'] > dataframe['volume_sma'] * 1.5)
)
dataframe['upthrust'] = ( # Fake breakout (bearish)
(dataframe['high'] > dataframe['resistance_1']) &
(dataframe['close'] < dataframe['resistance_1']) &
dataframe['long_upper_wick'] &
(dataframe['volume'] > dataframe['volume_sma'] * 1.5)
)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
LONG Entry with HTF trend confirmation and order flow
"""
conditions_long = []
# === LONG CONDITION 1: HTF Bullish + Accumulation Breakout ===
conditions_long.append(
dataframe['htf_bullish'] & # All HTFs bullish
(dataframe['accumulation_zone'].shift(1) == True) & # Previous accumulation
(dataframe['close'] > dataframe['ema_fast']) & # Breakout above fast EMA
(dataframe['cumulative_delta'] > self.delta_threshold.value) & # Positive order flow
(dataframe['volume_ratio'] > self.volume_confirmation.value) & # Volume surge
(dataframe['rsi'] > self.rsi_buy_min.value) & (dataframe['rsi'] < self.rsi_buy_max.value) & # RSI in range
(dataframe['adx'] > self.adx_trend_min.value) # Trending
)
# === LONG CONDITION 2: Spring (Wyckoff) + Order Flow Confirmation ===
conditions_long.append(
dataframe['htf_bullish'] &
(dataframe['spring'] == True) & # Spring pattern
(dataframe['cumulative_delta'] > dataframe['cumulative_delta'].shift(1)) & # Increasing buying
(dataframe['close'] > dataframe['open']) & # Bullish candle
(dataframe['adx'] > self.adx_weak_trend.value)
)
# === LONG CONDITION 3: Support Bounce + Order Flow ===
conditions_long.append(
(dataframe['trend_4h'] == 'bullish') & # 4H trend bullish
dataframe['support_confirmed'] & # Support with order flow confirmation
(dataframe['long_lower_wick'] == True) & # Rejection wick
(dataframe['close'] > dataframe['vwap']) & # Above VWAP
(dataframe['rsi'] < 50) & # Not overbought
(dataframe['volume_ratio'] > 1.3)
)
# === LONG CONDITION 4: Pullback Entry in Strong Trend ===
conditions_long.append(
dataframe['htf_bullish'] &
(dataframe['close'] > dataframe['ema_200']) & # Above 200 EMA
(dataframe['close'] < dataframe['bb_middle']) & # Pullback
(dataframe['rsi'] < self.rsi_buy_min.value) & # Oversold on pullback
qtpylib.crossed_above(dataframe['close'], dataframe['ema_fast']) & # Bounce
(dataframe['cumulative_delta'] > 0) & # Buying coming in
(dataframe['adx'] > self.adx_strong_trend.value) # Strong trend
)
if conditions_long:
dataframe.loc[
reduce(lambda x, y: x | y, conditions_long),
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
LONG Exit signals
"""
conditions_exit_long = []
# === EXIT 1: Distribution Zone + Order Flow Reversal ===
conditions_exit_long.append(
(dataframe['distribution_zone'] == True) &
(dataframe['cumulative_delta'] < 0) & # Selling pressure
(dataframe['rsi'] > self.rsi_sell_min.value)
)
# === EXIT 2: Resistance Rejection ===
conditions_exit_long.append(
dataframe['resistance_confirmed'] &
(dataframe['long_upper_wick'] == True) &
(dataframe['volume_ratio'] > 1.5)
)
# === EXIT 3: HTF Trend Reversal ===
conditions_exit_long.append(
(dataframe['trend_4h'] == 'bearish') &
(dataframe['close'] < dataframe['ema_fast']) &
(dataframe['cumulative_delta'] < 0)
)
# === EXIT 4: Upthrust (False Breakout) ===
conditions_exit_long.append(
(dataframe['upthrust'] == True) &
(dataframe['rsi'] > self.exit_rsi_high.value)
)
# === EXIT 5: Momentum Loss ===
conditions_exit_long.append(
(dataframe['adx'] < self.adx_weak_trend.value) & # Weak trend
(dataframe['rsi'] > self.exit_rsi_high.value) &
(dataframe['volume_ratio'] < self.exit_volume_low.value) # Low volume
)
if conditions_exit_long:
dataframe.loc[
reduce(lambda x, y: x | y, conditions_exit_long),
'exit_long'
] = 1
return dataframe
def leverage(self, pair: str, current_time, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: str,
side: str, **kwargs) -> float:
"""
Dynamic leverage based on market conditions
"""
return self.leverage_num
def custom_stoploss(self, pair: str, trade: 'Trade', current_time,
current_rate: float, current_profit: float, **kwargs) -> float:
"""
Custom stoploss with ATR-based dynamic adjustment
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
# ATR-based stop
atr = last_candle['atr']
current_price = last_candle['close']
atr_stop = (atr * 2) / current_price # 2x ATR stop
# Use the more conservative stop
return min(self.stoploss, -atr_stop)