← Back to Home
Vortex Trend Capture Trading Strategy with ATR Trailing Stops

Vortex Trend Capture Trading Strategy with ATR Trailing Stops

This article describes a professional trend-following trading strategy implemented in Backtrader that uses the Vortex Indicator to identify trend signals, filtered by a long-term moving average and a volatility condition. The strategy employs ATR-based trailing stops to manage risk and capture profits during sustained trends.

Strategy Overview

The Vortex Trend Capture Trading Strategy integrates the following components:

Code Implementation

Below is the complete Backtrader code for the strategy:

import backtrader as bt

class VortexTrendCaptureStrategy(bt.Strategy):
    """
    A professional trend-following system that uses the Vortex Indicator for entry
    signals, filtered by a long-term MA and a volatility condition.
    """
    params = (
        # Vortex Indicator
        ('vortex_period', 30),
        # Macro Trend Filter
        ('long_term_ma_period', 30),
        # Volatility Filter
        ('atr_period', 7),
        ('atr_threshold', 0.05), # Max ATR as % of price to allow trades
        # Risk Management
        ('atr_stop_multiplier', 3.0),
    )

    def __init__(self):
        self.order = None

        # --- Indicators ---
        self.vortex = bt.indicators.Vortex(self.data, period=self.p.vortex_period)
        self.long_term_ma = bt.indicators.SimpleMovingAverage(self.data, period=self.p.long_term_ma_period)
        self.atr = bt.indicators.AverageTrueRange(self.data, period=self.p.atr_period)

        # Crossover for the Vortex signal
        self.vortex_cross = bt.indicators.CrossOver(self.vortex.lines.vi_plus, self.vortex.lines.vi_minus)

        # --- Trailing Stop State ---
        self.stop_price = None
        self.highest_price_since_entry = None
        self.lowest_price_since_entry = None

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]: 
            return
        if order.status in [order.Completed]:
            if self.position and self.stop_price is None:
                if order.isbuy():
                    self.highest_price_since_entry = self.data.high[0]
                    self.stop_price = self.highest_price_since_entry - (self.atr[0] * self.p.atr_stop_multiplier)
                elif order.issell():
                    self.lowest_price_since_entry = self.data.low[0]
                    self.stop_price = self.lowest_price_since_entry + (self.atr[0] * self.p.atr_stop_multiplier)
            elif not self.position:
                self.stop_price = None
                self.highest_price_since_entry = None
                self.lowest_price_since_entry = None
        self.order = None

    def next(self):
        if self.order: 
            return

        if not self.position:
            # --- Filter Conditions ---
            # 1. Is market volatility stable?
            is_stable = (self.atr[0] / self.data.close[0]) < self.p.atr_threshold
            # 2. Is price aligned with the macro trend?
            is_macro_uptrend = self.data.close[0] > self.long_term_ma[0]
            is_macro_downtrend = self.data.close[0] < self.long_term_ma[0]
            # 3. Has a Vortex crossover signal occurred?
            is_buy_signal = self.vortex_cross[0] > 0
            is_sell_signal = self.vortex_cross[0] < 0

            # --- Entry Logic ---
            if is_stable and is_macro_uptrend and is_buy_signal:
                self.order = self.buy()
            elif is_stable and is_macro_downtrend and is_sell_signal:
                self.order = self.sell()
                
        elif self.position:
            # --- Manual ATR Trailing Stop Logic ---
            if self.position.size > 0: # Long
                self.highest_price_since_entry = max(self.highest_price_since_entry, self.data.high[0])
                new_stop = self.highest_price_since_entry - (self.atr[0] * self.p.atr_stop_multiplier)
                self.stop_price = max(self.stop_price, new_stop)
                if self.data.close[0] < self.stop_price: 
                    self.order = self.close()
            elif self.position.size < 0: # Short
                self.lowest_price_since_entry = min(self.lowest_price_since_entry, self.data.low[0])
                new_stop = self.lowest_price_since_entry + (self.atr[0] * self.p.atr_stop_multiplier)
                self.stop_price = min(self.stop_price, new_stop)
                if self.data.close[0] > self.stop_price: 
                    self.order = self.close()

Strategy Explanation

1. VortexTrendCaptureStrategy

The strategy uses the Vortex Indicator with trend and volatility filters to trade trend changes:

Key Features

Pasted image 20250717135247.png Pasted image 20250717135411.png

Potential Improvements

This strategy is designed for trending markets where the Vortex Indicator can effectively capture directional moves, suitable for assets like forex, stocks, or cryptocurrencies, and can be backtested to evaluate its effectiveness across various timeframes and assets.