Skip to content
All library documents

Using Parabolic SAR as a Custom Trailing Stop

Article Strategy library · Author: freqtrade

Summary

This Freqtrade example shows how to use Parabolic SAR (PSAR) values to set a custom stop loss. The indicator is calculated for each candle, and the callback retrieves the latest analyzed value when a trade is active. It then converts the difference between the current rate and SAR into the relative offset expected by Freqtrade’s custom stop loss interface. A fallback return value leaves the stop unchanged when the needed data is unavailable.

The example also stores SAR data by pair during backtests and hyperparameter optimization, and includes a simple placeholder entry rule based on SAR movement. That entry logic is explicitly incidental to the stop loss demonstration. No performance results are reported. The notes distinguish backtest behavior from live or dry-run timing, and the method depends on the data provider returning the expected analyzed candle. Traders adapting it should check how the stop behaves for their position direction and framework settings.

Key ideas

  • The strategy calculates Parabolic SAR values as an indicator for each candle.
  • A custom stop loss callback reads the latest SAR value and converts it to a relative rate offset.
  • The example stores SAR data during backtests and hyperparameter optimization.
  • Its entry rule is only a placeholder and is separate from the trailing stop method.
  • The document provides implementation guidance but no evidence of trading performance.

Tags

Full text
# CustomStoplossWithPSAR


# CustomStoplossWithPSAR









this is an example class, implementing a PSAR based trailing stop loss
    you are supposed to take the `custom_stoploss()` and `populate_indicators()`
    parts and adapt it to your own strategy

    the populate_entry_trend() function is pretty nonsencial

## Source (GPL-3.0)

```python
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# isort: skip_file
# --- Do not remove these libs ---
import numpy as np  # noqa
import pandas as pd  # noqa
from pandas import DataFrame

from freqtrade.strategy import IStrategy

# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from datetime import datetime
from freqtrade.persistence import Trade


class CustomStoplossWithPSAR(IStrategy):
    """
    this is an example class, implementing a PSAR based trailing stop loss
    you are supposed to take the `custom_stoploss()` and `populate_indicators()`
    parts and adapt it to your own strategy

    the populate_entry_trend() function is pretty nonsencial
    """
    INTERFACE_VERSION: int = 3
    timeframe = '1h'
    stoploss = -0.2
    custom_info = {}
    use_custom_stoploss = True

    startup_candle_count = 199

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, **kwargs) -> float:

        result = 1
        if self.custom_info and pair in self.custom_info and trade:
            # using current_time directly (like below) will only work in backtesting/hyperopt.
            # in live / dry-run, it'll be really the current time
            relative_sl = None
            if self.dp:
                # so we need to get analyzed_dataframe from dp
                dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
                # only use .iat[-1] in callback methods, never in "populate_*" methods.
                # see: https://www.freqtrade.io/en/latest/strategy-customization/#common-mistakes-when-developing-strategies
                last_candle = dataframe.iloc[-1].squeeze()
                relative_sl = last_candle['sar']

            if (relative_sl is not None):
                # print("custom_stoploss().relative_sl: {}".format(relative_sl))
                # calculate new_stoploss relative to current_rate
                new_stoploss = (current_rate - relative_sl) / current_rate
                # turn into relative negative offset required by `custom_stoploss` return implementation
                result = new_stoploss - 1

        # print("custom_stoploss() -> {}".format(result))
        return result

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['sar'] = ta.SAR(dataframe)
        if self.dp.runmode.value in ('backtest', 'hyperopt'):
            self.custom_info[metadata['pair']] = dataframe[['date', 'sar']].copy().set_index('date')

        # all "normal" indicators:
        # e.g.
        # dataframe['rsi'] = ta.RSI(dataframe)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Placeholder Strategy: buys when SAR is smaller then candle before
        Based on TA indicators, populates the buy signal for the given dataframe
        :param dataframe: DataFrame
        :return: DataFrame with buy column
        """
        dataframe.loc[
            (
                (dataframe['sar'] < dataframe['sar'].shift())
            ),
            'enter_long'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Placeholder Strategy: does nothing
        Based on TA indicators, populates the sell signal for the given dataframe
        :param dataframe: DataFrame
        :return: DataFrame with buy column
        """
        # Deactivated sell signal to allow the strategy to work correctly
        dataframe.loc[:, 'exit_long'] = 0
        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.