This article describes a trading strategy implemented in Backtrader that identifies divergence between price movements and volatility oscillator changes to generate trade signals. The strategy combines trend confirmation, volatility-adjusted position sizing, and ATR-based trailing stops to capitalize on potential reversals or continuations in trending markets.
The Volatility Oscillator Divergence Trading Strategy includes the following components:
Below is the complete Backtrader code for the strategy:
import backtrader as bt
import numpy as np
class VolatilityOscillatorDivergenceStrategy(bt.Strategy):
= (
params 'vol_window', 20),
('vol_roc_period', 5),
('price_lookback', 5),
('sma_period', 30),
('atr_period', 14),
('atr_multiplier', 3.0),
('rsi_period', 14),
('max_hold_bars', 30),
('min_gap_bars', 1),
(
)
def __init__(self):
self.returns = bt.indicators.PctChange(period=1)
self.volatility = bt.indicators.StdDev(self.returns, period=self.params.vol_window)
self.vol_osc = bt.indicators.ROC(self.volatility, period=self.params.vol_roc_period)
self.sma = bt.indicators.SMA(period=self.params.sma_period)
self.atr = bt.indicators.ATR(period=self.params.atr_period)
self.rsi = bt.indicators.RSI(period=self.params.rsi_period)
self.entry_bar = None
self.last_trade_bar = None
self.stop_price = None
def next(self):
if len(self.data) < 50:
return
= self.data.close[0]
current_price
# Exit on hold limit
if self.position and self.entry_bar:
if (len(self.data) - self.entry_bar) >= self.params.max_hold_bars:
self.close()
self.entry_bar = None
self.last_trade_bar = len(self.data)
return
# Exit on trailing stop
if self.position and self.stop_price:
if self.position.size > 0 and current_price <= self.stop_price:
self.close()
self.entry_bar = None
self.last_trade_bar = len(self.data)
return
elif self.position.size < 0 and current_price >= self.stop_price:
self.close()
self.entry_bar = None
self.last_trade_bar = len(self.data)
return
# Update trailing stop
if self.position:
if self.position.size > 0:
= current_price - self.params.atr_multiplier * self.atr[0]
new_stop if self.stop_price is None or new_stop > self.stop_price:
self.stop_price = new_stop
else:
= current_price + self.params.atr_multiplier * self.atr[0]
new_stop if self.stop_price is None or new_stop < self.stop_price:
self.stop_price = new_stop
# Check minimum gap
if self.last_trade_bar and (len(self.data) - self.last_trade_bar) < self.params.min_gap_bars:
return
# Entry signals
if not self.position:
= self.data.close[0] - self.data.close[-self.params.price_lookback]
price_change = self.vol_osc[0] - self.vol_osc[-self.params.price_lookback]
vol_osc_change
# Volatility-adjusted position size
= self.atr[0] / current_price
atr_pct = min(0.8, max(0.2, 0.1 / atr_pct)) if atr_pct > 0 else 0.5
size_factor = self.broker.getcash()
cash = int(cash * size_factor / current_price)
size
# Bullish divergence: price up + vol osc down + uptrend + RSI not overbought
if (price_change > 0 and vol_osc_change < 0 and
> self.sma[0] and self.rsi[0] < 70 and size > 0):
current_price self.buy(size=size)
self.stop_price = current_price - self.params.atr_multiplier * self.atr[0]
self.entry_bar = len(self.data)
self.last_trade_bar = len(self.data)
# Bearish divergence: price down + vol osc up + downtrend + RSI not oversold
elif (price_change < 0 and vol_osc_change > 0 and
< self.sma[0] and self.rsi[0] > 30 and size > 0):
current_price self.sell(size=size)
self.stop_price = current_price + self.params.atr_multiplier * self.atr[0]
self.entry_bar = len(self.data)
self.last_trade_bar = len(self.data)
The strategy identifies divergences between price and volatility oscillator movements to generate trade signals, with risk management and trend confirmation:
Indicators:
Trading Logic (next
):
price_change > 0
).vol_osc_change < 0
).price_change < 0
).vol_osc_change > 0
).atr_pct
):
cash * size_factor / current_price
.Tracking:
vol_window
,
vol_roc_period
, sma_period
, or
atr_multiplier
for specific assets.This strategy is designed for markets with periodic divergences between price and volatility, such as stocks or cryptocurrencies, and can be backtested to evaluate performance across various timeframes and assets.