Skip to content
All library documents

ATR-Scaled Volatility Breakout Signals with Position Additions

Article Strategy library · Author: freqtrade

Summary

This strategy seeks sharp price moves in either direction using three-hour data resampled from a one-hour chart. It calculates a 14-period average true range multiplied by two, then enters long when the close rises more than the prior ATR or short when it falls by more than that threshold. An opposite-direction entry signal also exits the existing position. The entry logic can therefore act as a volatility breakout trigger in both directions.

The strategy starts with half the proposed stake and can add an equal stake after a fresh signal, subject to a limit of two successful entries. It sets leverage to two in futures mode, while its description advises keeping leverage low to limit liquidation risk. The document supplies implementation details but no backtest results, market-by-market evidence, or comparison with alternatives. Its configured stop loss is very wide, and the code does not show a conventional ATR-based protective stop; readers should assess execution, position exposure, and signal behavior before drawing conclusions about risk or performance.

Key ideas

  • The strategy uses a doubled 14-period ATR calculated on resampled three-hour candles.
  • A price change exceeding the prior ATR triggers a long or short entry.
  • An opposite-direction entry signal closes the existing position.
  • The initial stake is half the proposed amount, with one possible equal-sized addition on a fresh signal.
  • The document provides strategy code but no evidence of historical performance.

Tags

Full text
# VolatilitySystem


# VolatilitySystem









Volatility System strategy.
    Based on https://www.tradingview.com/script/3hhs0XbR/

    Leverage is optional but the lower the better to limit liquidations

## Source (GPL-3.0)

```python
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs ---
from datetime import datetime
from typing import Optional

import numpy as np  # noqa
import pandas as pd  # noqa
from pandas import DataFrame

import talib.abstract as ta
from freqtrade.persistence import Trade
from freqtrade.strategy import (CategoricalParameter, DecimalParameter,
                                IntParameter, IStrategy)
from freqtrade.exchange import date_minus_candles
import freqtrade.vendor.qtpylib.indicators as qtpylib

from technical.util import resample_to_interval, resampled_merge


class VolatilitySystem(IStrategy):
    """
    Volatility System strategy.
    Based on https://www.tradingview.com/script/3hhs0XbR/

    Leverage is optional but the lower the better to limit liquidations
    """
    can_short = True

    minimal_roi = {
        "0": 100
    }

    stoploss = -1

    # Optimal ticker interval for the strategy
    timeframe = '1h'

    plot_config = {
        # Main plot indicators (Moving averages, ...)
        'main_plot': {
        },
        'subplots': {
            "Volatility system": {
                "atr": {"color": "white"},
                "abs_close_change": {"color": "red"},
            }
        }
    }

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Adds several indicators to the given DataFrame

        Performance Note: For the best performance be frugal on the number of indicators
        you are using. Let easyprofiler do the work for you in finding out which indicators
        are worth adding.
        """
        resample_int = 60 * 3
        resampled = resample_to_interval(dataframe, resample_int)
        # Average True Range (ATR)
        resampled['atr'] = ta.ATR(resampled, timeperiod=14) * 2.0
        # Absolute close change
        resampled['close_change'] = resampled['close'].diff()
        resampled['abs_close_change'] = resampled['close_change'].abs()

        dataframe = resampled_merge(dataframe, resampled, fill_na=True)
        dataframe['atr'] = dataframe[f'resample_{resample_int}_atr']
        dataframe['close_change'] = dataframe[f'resample_{resample_int}_close_change']
        dataframe['abs_close_change'] = dataframe[f'resample_{resample_int}_abs_close_change']

        # Average True Range (ATR)
        # dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) * 2.0
        # Absolute close change
        # dataframe['close_change'] = dataframe['close'].diff()
        # dataframe['abs_close_change'] = dataframe['close_change'].abs()

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the buy and sell signals for the given dataframe
        :param dataframe: DataFrame
        :return: DataFrame with buy and sell columns
        """
        # Use qtpylib.crossed_above to get only one signal, otherwise the signal is active
        # for the whole "long" timeframe.
        dataframe.loc[
            # qtpylib.crossed_above(dataframe['close_change'] * 1, dataframe['atr']),
            (dataframe['close_change'] * 1 > dataframe['atr'].shift(1)),
            'enter_long'] = 1
        dataframe.loc[
            # qtpylib.crossed_above(dataframe['close_change'] * -1, dataframe['atr']),
            (dataframe['close_change'] * -1 > dataframe['atr'].shift(1)),
            'enter_short'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        use sell/buy signals as long/short indicators
        """
        dataframe.loc[
            dataframe['enter_long'] == 1,
            'exit_short'] = 1
        dataframe.loc[
            dataframe['enter_short'] == 1,
            'exit_long'] = 1
        return dataframe

    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: Optional[float], max_stake: float,
                            leverage: float, entry_tag: Optional[str], side: str,
                            **kwargs) -> float:
        # 50% stake amount on initial entry
        return proposed_stake / 2

    position_adjustment_enable = True

    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]:
        dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
        if len(dataframe) > 2:
            last_candle = dataframe.iloc[-1].squeeze()
            previous_candle = dataframe.iloc[-2].squeeze()
            signal_name = 'enter_long' if not trade.is_short else 'enter_short'
            prior_date = date_minus_candles(self.timeframe, 1, current_time)
            # Only enlarge position on new signal.
            if (
                last_candle[signal_name] == 1
                and previous_candle[signal_name] != 1
                and trade.nr_of_successful_entries < 2
                and trade.orders[-1].order_date_utc < prior_date
            ):
                return trade.stake_amount
        return None

    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, side: str,
                 **kwargs) -> float:
        """
        Customize leverage for each new trade. This method is only called in futures mode.

        :param pair: Pair that's currently analyzed
        :param current_time: datetime object, containing the current datetime
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
        :param proposed_leverage: A leverage proposed by the bot.
        :param max_leverage: Max leverage allowed on this pair
        :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
        :param side: 'long' or 'short' - indicating the direction of the proposed trade
        :return: A leverage amount, which is between 1.0 and max_leverage.
        """
        return 2.0

```

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.