← Back to Home
Adaptive VIDYA Trading Strategy with CMO, ADX, and Momentum Filters

Adaptive VIDYA Trading Strategy with CMO, ADX, and Momentum Filters

This article explores a trading strategy built using Backtrader, combining the Variable Index Dynamic Average (VIDYA) with the Chande Momentum Oscillator (CMO), Average Directional Index (ADX), and momentum filters. The strategy aims to adapt to market conditions by dynamically adjusting the VIDYA period based on momentum, incorporating trend strength and momentum validation to filter trades, and using trailing stops for risk management.

Strategy Overview

The VIDYA strategy leverages the following components:

Code Implementation

Below is the complete Backtrader code for the strategy:

import backtrader as bt
import numpy as np

class CMOIndicator(bt.Indicator):
    lines = ('cmo',)
    params = (('period', 14),)
    
    def __init__(self):
        self.addminperiod(self.params.period)
        
    def next(self):
        gains = losses = 0.0
        for i in range(1, self.params.period + 1):
            if len(self.data) > i:
                change = self.data.close[-i] - self.data.close[-i-1]
                if change > 0:
                    gains += change
                else:
                    losses += abs(change)
        
        self.lines.cmo[0] = 100 * (gains - losses) / (gains + losses) if gains + losses > 0 else 0

class VIDYAStrategy(bt.Strategy):
    params = (
        ('cmo_period', 14),
        ('period_min', 10),
        ('period_max', 60),
        ('atr_period', 14),
        ('atr_multiplier', 1.5),
        ('cooldown_bars', 3),
        ('threshold_pct', 0.01),  
        ('adx_period', 14),
        ('adx_threshold', 20),     
        ('momentum_period', 30),
        ('momentum_threshold', 0.01),  
    )
    
    def __init__(self):
        self.cmo = CMOIndicator(period=self.params.cmo_period)
        self.atr = bt.indicators.ATR(period=self.params.atr_period)
        self.adx = bt.indicators.AverageDirectionalMovementIndex(period=self.params.adx_period)
        self.momentum = bt.indicators.Momentum(period=self.params.momentum_period)
        self.vidya_value = None
        self.prev_vidya_value = None
        self.last_exit_bar = 0
        self.order = None
        
    def next(self):
        min_periods = max(self.params.cmo_period, self.params.period_max, self.params.atr_period, 
                         self.params.adx_period, self.params.momentum_period)
        if len(self.data) < min_periods + 1:
            return
            
        # Cancel pending orders
        if self.order:
            return
            
        # Store previous VIDYA before update
        self.prev_vidya_value = self.vidya_value
        
        # Calculate adaptive period using lagged CMO
        lagged_norm_abs_cmo = min(1.0, abs(self.cmo[-1]) / 100.0)
        adaptive_period = self.params.period_max - lagged_norm_abs_cmo * (self.params.period_max - self.params.period_min)
        alpha = 2.0 / (adaptive_period + 1)
        
        # Initialize/update VIDYA
        if self.vidya_value is None:
            self.vidya_value = self.data.close[0]
            return
        self.vidya_value = alpha * self.data.close[0] + (1 - alpha) * self.vidya_value
        
        # Cooldown check
        if (len(self.data) - self.last_exit_bar) < self.params.cooldown_bars:
            return
            
        # TREND STRENGTH FILTER - ADX must be above threshold
        if self.adx[0] < self.params.adx_threshold:
            return
            
        # MOMENTUM VALIDATOR - Recent momentum must be strong enough
        momentum_pct = (self.momentum[0] / self.data.close[-self.params.momentum_period]) * 100
        if abs(momentum_pct) < self.params.momentum_threshold:
            return
        
        # Entry with threshold confirmation + filters
        if not self.position:
            threshold = lagged_vidya * self.params.threshold_pct
            
            # Long: price above VIDYA + positive momentum + strong trend
            if (lagged_close > (lagged_vidya + threshold) and 
                momentum_pct > self.params.momentum_threshold):
                self.order = self.buy()
                # Set trailing stop
                self.sell(exectype=bt.Order.StopTrail, trailamount=self.params.atr_multiplier * self.atr[0])
                
            # Short: price below VIDYA + negative momentum + strong trend  
            elif (lagged_close < (lagged_vidya - threshold) and 
                  momentum_pct < -self.params.momentum_threshold):
                self.order = self.sell()
                # Set trailing stop
                self.buy(exectype=bt.Order.StopTrail, trailamount=self.params.atr_multiplier * self.atr[0])
        
        # Exit on signal reversal
        elif self.position.size > 0 and lagged_close < lagged_vidya:
            self.close()
            self.last_exit_bar = len(self.data)
        elif self.position.size < 0 and lagged_close > lagged_vidya:
            self.close()
            self.last_exit_bar = len(self.data)
    
    def notify_order(self, order):
        if order.status in [order.Completed, order.Canceled, order.Margin, order.Rejected]:
            self.order = None

Strategy Explanation

1. CMOIndicator

The CMOIndicator calculates the Chande Momentum Oscillator, which compares the sum of price gains to losses over a specified period (default: 14). The formula is:

\[ \text{CMO} = 100 \times \frac{\text{Gains} - \text{Losses}}{\text{Gains} + \text{Losses}} \]

This produces a value between -100 and 100, reflecting momentum strength. High absolute CMO values indicate strong trends, used to adjust VIDYA’s responsiveness.

2. VIDYAStrategy

The strategy initializes indicators (CMO, ATR, ADX, Momentum) and tracks VIDYA values. Key logic in the next method includes:

\[\text{Adaptive Period} = \text{period}_{\text{max}} - |\text{CMO}| \times \left( \text{period}_{\text{max}} - \text{period}_{\text{min}} \right) \]

\[ \alpha = \frac{2}{\text{Adaptive Period} + 1} \]

\[ \text{VIDYA}_t = \alpha \times \text{Close}_t + (1 - \alpha) \times \text{VIDYA}_{t-1} \]

Key Features

Pasted image 20250714065733.png Pasted image 20250714065742.png

Potential Improvements

This strategy is designed for trending markets and can be backtested with historical data to evaluate performance across different assets and timeframes.