Timeframe
1d
Direction
Long & Short
Stoploss
-5.0%
Trailing Stop
Yes
ROI
0m: 15.0%, 1440m: 10.0%, 4320m: 5.0%, 8640m: 0.0%
Interface Version
3
Startup Candles
200
Indicators
8
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
1d Macro Strategies — ML + Rule-based, using macro regime factors
"""
from pandas import DataFrame
import talib.abstract as ta
import numpy as np
from freqtrade.strategy import IStrategy
from datetime import datetime, timezone
# ============================================================
# A: Rule-based Macro Trend + Regime Filter
# ============================================================
class Macro1D_Trend(IStrategy):
INTERFACE_VERSION = 3
timeframe = '1d'
can_short = True
stoploss = -0.05
trailing_stop = True
trailing_stop_positive = 0.01
trailing_stop_positive_offset = 0.04
trailing_only_offset_is_reached = True
minimal_roi = {"0": 0.15, "1440": 0.10, "4320": 0.05, "8640": 0}
max_open_trades = 4
startup_candle_count = 200
process_only_new_candles = True
use_exit_signal = False
def populate_indicators(self, d, m):
# Trend
d['e20'] = ta.EMA(d, 20)
d['e50'] = ta.EMA(d, 50)
d['e200'] = ta.EMA(d, 200)
macd = ta.MACD(d, 12, 26, 9)
d['md'] = macd['macd']
d['ms'] = macd['macdsignal']
d['adx'] = ta.ADX(d, 14)
d['di_plus'] = ta.PLUS_DI(d, 14)
d['di_minus'] = ta.MINUS_DI(d, 14)
# Regime
d['atr'] = ta.ATR(d, 14)
d['atr_pct'] = d['atr'] / d['close'] * 100
d['atr_high'] = d['atr_pct'] > d['atr_pct'].rolling(50).mean() * 1.3 # high vol
d['dd_ath'] = d['close'] / d['close'].expanding().max() - 1
# Volume
d['vr'] = d['volume'] / ta.SMA(d['volume'], 20)
# RSI
d['rsi'] = ta.RSI(d, 14)
# BB
bb = ta.BBANDS(d, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
d['bb_width'] = (bb['upperband'] - bb['lowerband']) / bb['middleband']
return d
def populate_entry_trend(self, d, m):
# === LONG: Macro bull regime ===
long_cond = (
# Trend: above EMAs, MACD bullish
(d['close'] > d['e50']) &
(d['e20'] > d['e50']) &
(d['md'] > d['ms']) &
# DMI confirmation
(d['adx'] > 18) & (d['di_plus'] > d['di_minus']) &
# Not severely overbought
(d['rsi'] < 75) &
# Volume present
(d['vr'] > 0.6) &
(d['volume'] > 0)
)
d.loc[long_cond, ['enter_long', 'enter_tag']] = (1, 'L_macro')
# === SHORT: Macro bear regime ===
short_cond = (
# Trend: below EMAs, MACD bearish
(d['close'] < d['e50']) &
(d['e20'] < d['e50']) &
(d['md'] < d['ms']) &
# DMI confirmation
(d['adx'] > 18) & (d['di_minus'] > d['di_plus']) &
# Not severely oversold
(d['rsi'] > 25) &
# Volume present
(d['vr'] > 0.6) &
(d['volume'] > 0)
)
d.loc[short_cond, ['enter_short', 'enter_tag']] = (1, 'S_macro')
return d
def populate_exit_trend(self, d, m): return d
# ============================================================
# B: Aggressive Macro — wider signals, more entries
# ============================================================
class Macro1D_Aggressive(IStrategy):
INTERFACE_VERSION = 3
timeframe = '1d'
can_short = True
stoploss = -0.04
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.030
trailing_only_offset_is_reached = True
minimal_roi = {"0": 0.10, "1440": 0.07, "4320": 0.04, "8640": 0}
max_open_trades = 4
startup_candle_count = 200
process_only_new_candles = True
use_exit_signal = False
def populate_indicators(self, d, m):
d['e12'] = ta.EMA(d, 12)
d['e26'] = ta.EMA(d, 26)
macd = ta.MACD(d, 8, 21, 5)
d['md'] = macd['macd']
d['ms'] = macd['macdsignal']
d['adx'] = ta.ADX(d, 10)
d['di_plus'] = ta.PLUS_DI(d, 10)
d['di_minus'] = ta.MINUS_DI(d, 10)
d['rsi'] = ta.RSI(d, 10)
d['vr'] = d['volume'] / ta.SMA(d['volume'], 10)
d['mom_5'] = ta.ROC(d, 5)
d['mom_20'] = ta.ROC(d, 20)
return d
def populate_entry_trend(self, d, m):
d.loc[
(d['close'] > d['e26']) & (d['md'] > d['ms']) &
(d['adx'] > 14) & (d['di_plus'] > d['di_minus']) &
(d['mom_5'] > 2) & (d['rsi'] > 40) &
(d['vr'] > 0.5) & (d['volume'] > 0),
['enter_long', 'enter_tag']
] = (1, 'L_agg')
d.loc[
(d['close'] < d['e26']) & (d['md'] < d['ms']) &
(d['adx'] > 14) & (d['di_minus'] > d['di_plus']) &
(d['mom_5'] < -2) & (d['rsi'] < 60) &
(d['vr'] > 0.5) & (d['volume'] > 0),
['enter_short', 'enter_tag']
] = (1, 'S_agg')
return d
def populate_exit_trend(self, d, m): return d
# ============================================================
# C: Regime-Switch — different rules for bull/bear macro
# ============================================================
class Macro1D_RegimeSwitch(IStrategy):
INTERFACE_VERSION = 3
timeframe = '1d'
can_short = True
stoploss = -0.06
trailing_stop = True
trailing_stop_positive = 0.01
trailing_stop_positive_offset = 0.05
trailing_only_offset_is_reached = True
minimal_roi = {"0": 0.20, "2880": 0.10, "5760": 0.05, "11520": 0}
max_open_trades = 4
startup_candle_count = 200
process_only_new_candles = True
use_exit_signal = False
def populate_indicators(self, d, m):
d['e20'] = ta.EMA(d, 20)
d['e50'] = ta.EMA(d, 50)
d['e200'] = ta.EMA(d, 200)
macd = ta.MACD(d, 12, 26, 9)
d['md'] = macd['macd']
d['ms'] = macd['macdsignal']
d['adx'] = ta.ADX(d, 14)
d['di_plus'] = ta.PLUS_DI(d, 14)
d['di_minus'] = ta.MINUS_DI(d, 14)
d['rsi'] = ta.RSI(d, 14)
d['atr'] = ta.ATR(d, 14)
d['atr_pct'] = d['atr'] / d['close'] * 100
d['vr'] = d['volume'] / ta.SMA(d['volume'], 20)
# ATH distance
d['ath'] = d['close'].expanding().max()
d['dd_pct'] = (d['close'] / d['ath'] - 1) * 100
# Regime detection: bull (>EMA200, rising), bear (<EMA200, falling), neutral
d['ema200_rising'] = d['e200'] > d['e200'].shift(20)
d['ema200_falling'] = d['e200'] < d['e200'].shift(20)
# BB for squeeze
bb = ta.BBANDS(d, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
d['bb_squeeze'] = (bb['upperband'] - bb['lowerband']) / bb['middleband'] < 0.10
return d
def populate_entry_trend(self, d, m):
# Bull regime: only long, wait for pullback to EMA50
d.loc[
(d['close'] > d['e200']) & d['ema200_rising'] &
(d['close'] > d['e50'] * 0.95) & (d['close'] < d['e20'] * 1.05) &
(d['rsi'] > 30) & (d['rsi'] < 70) &
(d['md'] > d['ms']) & (d['di_plus'] > d['di_minus']) &
(d['adx'] > 15) & (d['volume'] > 0),
['enter_long', 'enter_tag']
] = (1, 'L_bull')
# Bear regime: only short, wait for bounce to EMA50
d.loc[
(d['close'] < d['e200']) & d['ema200_falling'] &
(d['close'] < d['e50'] * 1.05) & (d['close'] > d['e20'] * 0.95) &
(d['rsi'] > 30) & (d['rsi'] < 70) &
(d['md'] < d['ms']) & (d['di_minus'] > d['di_plus']) &
(d['adx'] > 15) & (d['volume'] > 0),
['enter_short', 'enter_tag']
] = (1, 'S_bear')
return d
def populate_exit_trend(self, d, m): return d
# ============================================================
# D: Pure Trend Following — breakout style on 1d
# ============================================================
class Macro1D_Breakout(IStrategy):
INTERFACE_VERSION = 3
timeframe = '1d'
can_short = True
stoploss = -0.04
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.025
trailing_only_offset_is_reached = True
minimal_roi = {"0": 0.12, "1440": 0.08, "4320": 0.04, "8640": 0}
max_open_trades = 4
startup_candle_count = 200
process_only_new_candles = True
use_exit_signal = False
def populate_indicators(self, d, m):
# Price channels
d['hh_20'] = d['high'].rolling(20).max()
d['ll_20'] = d['low'].rolling(20).min()
d['hh_50'] = d['high'].rolling(50).max()
d['ll_50'] = d['low'].rolling(50).min()
# Trend
d['e50'] = ta.EMA(d, 50)
d['e200'] = ta.EMA(d, 200)
# Volatility
d['atr'] = ta.ATR(d, 14)
d['atr_pct'] = d['atr'] / d['close'] * 100
# Volume
d['vr'] = d['volume'] / ta.SMA(d['volume'], 20)
d['vol_spike'] = d['vr'] > 2.0
# ADX
d['adx'] = ta.ADX(d, 14)
d['di_plus'] = ta.PLUS_DI(d, 14)
d['di_minus'] = ta.MINUS_DI(d, 14)
# RSI
d['rsi'] = ta.RSI(d, 14)
return d
def populate_entry_trend(self, d, m):
# Long breakout
d.loc[
(d['close'] > d['hh_20'].shift(1)) &
(d['close'] > d['e50']) &
(d['adx'] > 20) & (d['di_plus'] > d['di_minus']) &
(d['rsi'] > 50) & (d['vr'] > 1.2) &
(d['volume'] > 0),
['enter_long', 'enter_tag']
] = (1, 'L_break')
# Short breakdown
d.loc[
(d['close'] < d['ll_20'].shift(1)) &
(d['close'] < d['e50']) &
(d['adx'] > 20) & (d['di_minus'] > d['di_plus']) &
(d['rsi'] < 50) & (d['vr'] > 1.2) &
(d['volume'] > 0),
['enter_short', 'enter_tag']
] = (1, 'S_break')
return d
def populate_exit_trend(self, d, m): return d