← Back to Home
Riding the Waves of Volatility A Simple Momentum Strategy

Riding the Waves of Volatility A Simple Momentum Strategy

In the world of algorithmic trading, strategies often try to capitalize on market trends and volatility. One such approach is the volatility momentum strategy. The core idea is simple: when the market’s volatility starts to pick up, it often signals the beginning of a strong price move. This strategy aims to ride that wave by entering a trade in the direction of the price trend as soon as volatility accelerates.

This article breaks down a Python implementation of a simple volatility momentum strategy using the popular backtrader library for backtesting. We’ll explore the logic behind the strategy, how to set it up, and how to evaluate its performance.


The Strategy’s Core Logic

The strategy is encapsulated within a backtrader Strategy class. Let’s look at the initial setup and the main indicators.

class SimpleVolatilityMomentumStrategy(bt.Strategy):
    """Simple Volatility Momentum: When vol accelerates, trade with price direction"""
    
    params = (
        ('vol_window', 30),          # Volatility calculation period
        ('vol_momentum_window', 7),  # Vol momentum lookback (σt – σt–N)
        ('price_sma_window', 30),    # Price trend SMA
        ('atr_window', 14),          # ATR stop loss period
        ('atr_multiplier', 1.0),     # ATR stop multiplier
    )
    
    def __init__(self):
        # Calculate daily returns
        self.returns = bt.indicators.PctChange(self.data.close, period=1)
        
        # Volatility = rolling std of returns
        self.volatility = bt.indicators.StandardDeviation(self.returns, period=self.params.vol_window)
        
        # Volatility momentum = σt – σt–N
        self.vol_momentum = self.volatility - self.volatility(-self.params.vol_momentum_window)
        
        # Price trend = SMA
        self.price_sma = bt.indicators.SMA(self.data.close, period=self.params.price_sma_window)
        
        # ATR for stops
        self.atr = bt.indicators.ATR(self.data, period=self.params.atr_window)

In the __init__ method, we define the indicators that will drive our trading decisions:

The trading logic resides in the next method, which is executed on each bar of the data. The strategy enters a trade only when volatility momentum is positive. If the price is above its 30-day SMA, it initiates a long position. If it’s below, it goes short. A crucial part of this strategy is risk management, which is handled by a trailing stop loss based on the ATR. The position is exited if the volatility momentum turns negative or if the stop loss is hit.


Setting Up the Backtest

With the strategy defined, the next step is to prepare the environment to backtest it. This involves fetching historical price data, in this case for ETH-USD, and configuring the backtrader engine, which is known as Cerebro.

# Download ETH-USD data
print("Downloading data...")
ticker = "ETH-USD"
data = yf.download(ticker, start="2020-01-01", end="2024-12-31", auto_adjust=False)

# Clean multi-level columns if necessary
if isinstance(data.columns, pd.MultiIndex):
    data.columns = data.columns.droplevel(1)

# Create backtrader data feed
bt_data = bt.feeds.PandasData(dataname=data)

# Initialize Cerebro
cerebro = bt.Cerebro()

# Add Simple Volatility Momentum strategy
cerebro.addstrategy(SimpleVolatilityMomentumStrategy)

# Add data
cerebro.adddata(bt_data)

# Set initial capital and commission
cerebro.broker.setcash(10000.0)
cerebro.broker.setcommission(commission=0.001)  # 0.1%

# Add a sizer
cerebro.addsizer(bt.sizers.PercentSizer, percents=95)

print(f'Starting Portfolio Value: {cerebro.broker.getvalue():.2f}')

# Run backtest
results = cerebro.run()

This snippet handles the entire setup process. It downloads five years of daily data for Ethereum, converts it into a format that backtrader can understand, and adds our SimpleVolatilityMomentumStrategy to the Cerebro engine. We start with an initial capital of $10,000 and apply a commission of 0.1% to simulate more realistic trading costs. The PercentSizer tells backtrader to invest 95% of the portfolio’s cash in each trade.


Analyzing the Performance

A backtest is only as good as the analysis that follows. After the backtest completes, we can extract valuable performance metrics to understand the strategy’s effectiveness. Backtrader’s built-in analyzers make this straightforward.

# Add analyzers
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')

# ... (code to run cerebro) ...

strat = results[0]
print(f'Final Portfolio Value: {cerebro.broker.getvalue():.2f}')

# Print performance metrics
try:
    sharpe = strat.analyzers.sharpe.get_analysis()
    if sharpe and 'sharperatio' in sharpe and sharpe['sharperatio'] is not None:
        print(f'Sharpe Ratio: {sharpe["sharperatio"]:.2f}')
    else:
        print('Sharpe Ratio: N/A')
except:
    print('Sharpe Ratio: N/A')

try:
    drawdown = strat.analyzers.drawdown.get_analysis()
    if drawdown and 'max' in drawdown and 'drawdown' in drawdown['max']:
        print(f'Max Drawdown: {drawdown["max"]["drawdown"]:.2f}%')
    else:
        print('Max Drawdown: N/A')
except:
    print('Max Drawdown: N/A')

try:
    returns = strat.analyzers.returns.get_analysis()
    if returns and 'rtot' in returns:
        print(f'Total Return: {returns["rtot"]:.2%}')
    else:
        print('Total Return: N/A')
except:
    print('Total Return: N/A')

# Plot results
print("\nPlotting Simple Volatility Momentum Strategy results...")
cerebro.plot(iplot=False, style='line')
plt.show()

Before running the backtest, we add analyzers for the Sharpe Ratio (risk-adjusted return), Maximum Drawdown (largest peak-to-trough decline), and Total Returns. After the simulation, we extract the results from these analyzers and print them. This gives us a quantitative look at the strategy’s historical performance. Finally, cerebro.plot() provides a visual representation of the portfolio’s equity curve over time, along with the trades executed on the price chart.

Pasted image 20250615175625.png

This simple volatility momentum strategy provides a solid framework. From here, one could experiment with different lookback windows, alternative trend filters, or more sophisticated risk management techniques to potentially enhance its performance.