Skip to content
All library documents

EMA Trend Crossovers Confirmed by On-Balance Volume

Article Strategy library · Author: freqtrade

Summary

This short-timeframe strategy uses a 20-period exponential moving average as a trend line and on-balance volume (OBV) as confirmation. It enters long when price crosses above the EMA and OBV rises from the prior candle; it enters short when price crosses below the EMA and OBV falls. Exit conditions also use price crossings, with the OBV direction specified separately for long and short exits. The framework sets a five-minute timeframe, a tiered return-on-investment schedule, a fixed stop loss, and a trailing stop configuration.

The source is an implementation example rather than a research report: it provides no market or exchange, backtest period, performance results, or evidence for the signal choices. The entry and exit rules can also coincide only under particular OBV directions, so the volume confirmation affects both opening and closing behavior. The stated risk controls are configuration values, not proof that losses or drawdowns will be limited in practice. Users would need to evaluate the strategy across assets, fees, and market conditions before drawing conclusions.

Key ideas

  • A 20-period EMA defines the price trend threshold on a five-minute timeframe.
  • Long and short entries require price to cross the EMA with OBV moving in the same directional sense.
  • Exit signals use EMA crossings paired with distinct OBV conditions.
  • The implementation includes tiered profit targets, a fixed stop loss, and trailing-stop settings.
  • No backtest results or tested market are provided.

Tags

Full text
# TrendFollowingStrategy


# TrendFollowingStrategy









## Source (GPL-3.0)

```python
from functools import reduce
from pandas import DataFrame
from freqtrade.strategy import IStrategy

import talib.abstract as ta

from freqtrade.strategy.interface import IStrategy

class TrendFollowingStrategy(IStrategy):

    INTERFACE_VERSION: int = 3
    # ROI table:
    minimal_roi = {"0": 0.15, "30": 0.1, "60": 0.05}
    # minimal_roi = {"0": 1}

    # Stoploss:
    stoploss = -0.265

    # Trailing stop:
    trailing_stop = True
    trailing_stop_positive = 0.05
    trailing_stop_positive_offset = 0.1
    trailing_only_offset_is_reached = False

    timeframe = "5m"

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Calculate OBV
        dataframe['obv'] = ta.OBV(dataframe['close'], dataframe['volume'])
        
        # Add your trend following indicators here
        dataframe['trend'] = dataframe['close'].ewm(span=20, adjust=False).mean()
        
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Add your trend following buy signals here
        dataframe.loc[
            (dataframe['close'] > dataframe['trend']) & 
            (dataframe['close'].shift(1) <= dataframe['trend'].shift(1)) &
            (dataframe['obv'] > dataframe['obv'].shift(1)), 
            'enter_long'] = 1
        
        # Add your trend following sell signals here
        dataframe.loc[
            (dataframe['close'] < dataframe['trend']) & 
            (dataframe['close'].shift(1) >= dataframe['trend'].shift(1)) &
            (dataframe['obv'] < dataframe['obv'].shift(1)), 
            'enter_short'] = 1
        
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Add your trend following exit signals for long positions here
        dataframe.loc[
            (dataframe['close'] < dataframe['trend']) & 
            (dataframe['close'].shift(1) >= dataframe['trend'].shift(1)) &
            (dataframe['obv'] > dataframe['obv'].shift(1)), 
            'exit_long'] = 1
        
        # Add your trend following exit signals for short positions here
        dataframe.loc[
            (dataframe['close'] > dataframe['trend']) & 
            (dataframe['close'].shift(1) <= dataframe['trend'].shift(1)) &
            (dataframe['obv'] < dataframe['obv'].shift(1)), 
            'exit_short'] = 1
        
        return dataframe


```

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.