EMA and Heikin-Ashi Signals with an RSI Profit Exit
Summary
This example describes a five-minute long-only strategy built from moving averages, Heikin-Ashi candles, and RSI. It enters when the 20-period EMA crosses above the 50-period EMA, the Heikin-Ashi close is above the faster average, and the candle is positive. Its regular exit signal uses a 50-period EMA crossing above the 100-period EMA together with a close below the 20-period EMA and a negative Heikin-Ashi candle.
A custom exit additionally closes a trade when RSI exceeds 70 while the position is profitable. The configuration also specifies a stepped minimal-return schedule, a fixed stop loss, and limit entry and exit orders, while trailing stops are disabled. These are code settings, not evidence of performance: the document supplies no backtest results, market selection, fee assumptions, or robustness analysis. The code depends on the framework’s configuration and data provider, so those settings and execution behavior should be checked before use.
Key ideas
- The entry combines a fast EMA crossover with bullish Heikin-Ashi conditions.
- The regular exit combines a slower EMA crossover with bearish price conditions.
- A custom exit closes profitable positions when RSI is above its threshold.
- The configuration includes ROI targets, a stop loss, and limit orders, but no performance evidence.
- Backtesting should account for market selection, fees, and execution behavior.
Tags
Full text
# Strategy001_custom_exit.py
```py
# --- Do not remove these libs ---
from freqtrade.strategy import IStrategy
from typing import Dict, List
from functools import reduce
from pandas import DataFrame
# --------------------------------
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class Strategy001_custom_exit(IStrategy):
"""
Strategy 001_custom_exit
author@: Gerald Lonlas, froggleston
github@: https://github.com/freqtrade/freqtrade-strategies
How to use it?
> python3 ./freqtrade/main.py -s Strategy001_custom_exit
"""
INTERFACE_VERSION: int = 3
# Minimal ROI designed for the strategy.
# This attribute will be overridden if the config file contains "minimal_roi"
minimal_roi = {
"60": 0.01,
"30": 0.03,
"20": 0.04,
"0": 0.05
}
# Optimal stoploss designed for the strategy
# This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.10
# Optimal timeframe for the strategy
timeframe = '5m'
# trailing stoploss
trailing_stop = False
trailing_stop_positive = 0.01
trailing_stop_positive_offset = 0.02
# run "populate_indicators" only for new candle
process_only_new_candles = True
# Experimental settings (configuration will overide these if set)
use_exit_signal = True
exit_profit_only = True
ignore_roi_if_entry_signal = False
# Optional order type mapping
order_types = {
'entry': 'limit',
'exit': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
def informative_pairs(self):
"""
Define additional, informative pair/interval combinations to be cached from the exchange.
These pair/interval combinations are non-tradeable, unless they are part
of the whitelist as well.
For more information, please consult the documentation
:return: List of tuples in the format (pair, interval)
Sample: return [("ETH/USDT", "5m"),
("BTC/USDT", "15m"),
]
"""
return []
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Adds several different TA indicators to the given DataFrame
Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
"""
dataframe['ema20'] = ta.EMA(dataframe, timeperiod=20)
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
heikinashi = qtpylib.heikinashi(dataframe)
dataframe['ha_open'] = heikinashi['open']
dataframe['ha_close'] = heikinashi['close']
dataframe['rsi'] = ta.RSI(dataframe, 14)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
qtpylib.crossed_above(dataframe['ema20'], dataframe['ema50']) &
(dataframe['ha_close'] > dataframe['ema20']) &
(dataframe['ha_open'] < dataframe['ha_close']) # green bar
),
'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
qtpylib.crossed_above(dataframe['ema50'], dataframe['ema100']) &
(dataframe['ha_close'] < dataframe['ema20']) &
(dataframe['ha_open'] > dataframe['ha_close']) # red bar
),
'exit_long'] = 1
return dataframe
def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, current_profit: float, **kwargs):
"""
Sell only when matching some criteria other than those used to generate the sell signal
:return: str sell_reason, if any, otherwise None
"""
# get dataframe
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
# get the current candle
current_candle = dataframe.iloc[-1].squeeze()
# if RSI greater than 70 and profit is positive, then sell
if (current_candle['rsi'] > 70) and (current_profit > 0):
return "rsi_profit_sell"
# else, hold
return None
```Shown in full with attribution under the source's licence. Licence: GPL-3.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.