← Back to Home
Volume Spread Analysis Strategy Backtest: ETH-USD 43.25% Return With 12 Closed Trades

Volume Spread Analysis Strategy Backtest: ETH-USD 43.25% Return With 12 Closed Trades

Get the full strategy library: This article uses one strategy from the Mega Backtrader Strategy Pack, a package of 500+ Backtrader-ready Python trading strategies with batch backtesting, dashboards, metrics, charts, and documentation.

Download the package here: Mega Backtrader Strategy Pack

If you want a practical research workflow for testing hundreds of trading systems across crypto, stocks, ETFs, and other markets, the package gives you strategy code, batch runners, metrics, charts, and publishable reports in one place.

Volume Spread Analysis is built around a simple idea: price movement is more meaningful when it is read together with volume and bar structure. A wide range candle on heavy volume does not say the same thing as a narrow range candle on light volume. A close near the high of the bar does not say the same thing as a close near the low.

This article walks through VSAStrategy, one of the strategies included in the Mega Backtrader Strategy Pack. The strategy was tested on ETH-USD daily candles from July 23, 2025 to July 23, 2026.

The result:

Result Value
Strategy return 43.25%
ETH buy-and-hold return -48.06%
Excess return 91.31%
Sharpe ratio 1.39
Max drawdown 11.98%
Closed trades 12
Open trades 0
Win rate 66.67%

This is a clean case study: the strategy made 12 closed trades, had no open trade left at the end of the run, and beat ETH buy-and-hold by 91.31 percentage points over the same period.

Strategy Idea

VSAStrategy trades Volume Spread Analysis patterns.

The strategy looks at four pieces of information on each bar:

  1. Volume compared with recent average volume.
  2. Spread, measured as high - low, compared with recent average spread.
  3. Where the candle closes inside its range.
  4. Whether price is above or below a trend moving average.

From those inputs, the strategy classifies market behavior into VSA-style patterns such as stopping volume, strength, weakness, climax, tests of support, and tests of resistance.

The goal is not to buy every uptick or sell every downtick. The goal is to find bars where volume, range, close location, and trend context agree strongly enough to justify a trade.

Strategy Parameters

The strategy exposes its behavior through Backtrader params:

params = (
    ('volume_period', 7),
    ('volume_threshold', 1.2),
    ('spread_period', 7),
    ('spread_threshold', 1.2),
    ('trend_period', 30),
    ('climax_volume_mult', 2.0),
    ('test_volume_mult', 0.5),
    ('trail_stop_pct', 0.05),
)

These settings define:

Code Walkthrough

The strategy starts by storing OHLCV data and calculating the core VSA components:

self.high = self.data.high
self.low = self.data.low
self.close = self.data.close
self.open = self.data.open
self.volume = self.data.volume

self.spread = self.high - self.low
self.close_position = (
    self.close - self.low
) / (self.high - self.low)

It then builds moving averages for volume, spread, and trend:

self.volume_ma = bt.indicators.SMA(
    self.volume,
    period=self.params.volume_period,
)
self.spread_ma = bt.indicators.SMA(
    self.spread,
    period=self.params.spread_period,
)
self.trend_ma = bt.indicators.SMA(
    self.close,
    period=self.params.trend_period,
)

Volume is classified as climactic, high, low, or normal:

volume_ratio = self.volume[0] / self.volume_ma[0]

if volume_ratio >= self.params.climax_volume_mult:
    return 'climax'
elif volume_ratio >= self.params.volume_threshold:
    return 'high'
elif volume_ratio <= self.params.test_volume_mult:
    return 'low'
else:
    return 'normal'

The candle range is classified separately:

spread_ratio = self.spread[0] / self.spread_ma[0]

if spread_ratio >= self.params.spread_threshold:
    return 'wide'
elif spread_ratio <= (2 - self.params.spread_threshold):
    return 'narrow'
else:
    return 'normal'

The strategy also checks where the candle closes inside the bar:

close_pos = self.close_position[0]

if close_pos >= 0.7:
    return 'high'
elif close_pos <= 0.3:
    return 'low'
else:
    return 'middle'

The trend filter is simple and direct:

if self.close[0] > self.trend_ma[0]:
    return 'up'
elif self.close[0] < self.trend_ma[0]:
    return 'down'
else:
    return 'sideways'

These classifications feed the pattern engine. For example, a stopping-volume pattern is detected when volume is climactic after a decline and the candle does not close weakly:

if (
    volume_class == 'climax' and
    trend == 'down' and
    is_down_bar and
    close_class in ['middle', 'high']
):
    return 'stopping_volume', 4

A bearish climax pattern is detected when climactic volume appears on a wide up bar during an uptrend:

if (
    volume_class == 'climax' and
    spread_class == 'wide' and
    close_class == 'high' and
    is_up_bar and
    trend == 'up'
):
    return 'climax', 4

The strategy also uses recent bars as background context:

context_score = 0

for i in range(1, 4):
    if len(self.data) <= i:
        continue

    prev_volume = self.volume[-i]

    if prev_volume > self.volume_ma[-i]:
        context_score += 1

The pattern strength and background context are combined before a trade is allowed:

pattern, strength = self.detect_vsa_patterns()

if pattern is None or strength < 2:
    return

context = self.check_background_context()
total_strength = strength + context

if total_strength < 3:
    return

Bullish patterns can open long positions or close short positions:

if pattern in [
    'no_demand',
    'no_supply',
    'stopping_volume',
    'strength',
    'test_support',
    'effort_down',
]:
    if self.position.size < 0:
        self.order = self.close()
    elif not self.position:
        if total_strength >= 4 or pattern in [
            'stopping_volume',
            'no_supply',
        ]:
            self.order = self.buy()

Bearish patterns can open short positions or close long positions:

elif pattern in [
    'climax',
    'weakness',
    'test_resistance',
    'effort_up',
]:
    if self.position.size > 0:
        self.order = self.close()
    elif not self.position:
        if total_strength >= 4 or pattern in [
            'climax',
            'weakness',
        ]:
            self.order = self.sell()

Risk is managed with a trailing stop after entry:

if order.isbuy() and self.position.size > 0:
    self.entry_price = order.executed.price
    self.trail_stop_price = (
        order.executed.price *
        (1 - self.params.trail_stop_pct)
    )
    self.stop_order = self.sell(
        exectype=bt.Order.Stop,
        price=self.trail_stop_price,
    )

That gives the strategy a complete trading loop: classify the bar, detect the VSA pattern, score the context, enter only when strength is high enough, and manage the trade with a stop.

Backtest Command

The article result was generated with:

python run_backtest.py --fast --fast-plots --fast-equity \
  --workers 1 \
  --symbol ETH-USD \
  --period 1y \
  --interval 1d \
  --benchmark ETH-USD \
  --stake-percent 99 \
  --strategies strategies \
  --out results \
  --strategy-filter VSAStrategy

Backtest setup:

Setting Value
Asset ETH-USD
Benchmark ETH-USD buy-and-hold
Period 1y
Data window July 23, 2025 to July 23, 2026
Interval 1d
Starting cash $10,000
Final value $14,324.86
Strategy file strategies/VSAStrategy.py

Results

Metric Value
Strategy return 43.25%
ETH buy-and-hold return -48.06%
Excess return vs benchmark 91.31%
Sharpe ratio 1.39
Max drawdown 11.98%
Total trades 12
Closed trades 12
Open trades 0
Winning trades 8
Losing trades 4
Win rate 66.67%
Runtime 2.26 seconds

The strategy did not rely on one open position to create the result. It finished with 12 closed trades, no open trades, and a two-thirds win rate.

Equity Curve vs Benchmark

VSA ETH-USD equity curve versus ETH buy-and-hold benchmark

The equity curve shows the strategy growing from $10,000 to $14,324.86 while ETH buy-and-hold declined sharply over the same period.

Drawdown vs Benchmark

VSA ETH-USD drawdown versus benchmark

The drawdown chart shows the strategy's largest pullback at 11.98%, with the strategy avoiding the full downside profile of buy-and-hold.

Rolling Return vs Benchmark

VSA ETH-USD rolling return versus benchmark

The rolling return chart shows how the strategy's edge developed across the test window.

Daily Return Distribution

VSA ETH-USD daily returns histogram

The daily return distribution summarizes how the strategy's day-to-day returns were distributed during the run.

What This Shows

This VSA example is useful because it is not a simple moving-average crossover. It combines volume expansion, spread behavior, close location, trend context, pattern labels, signal strength, background context, and trailing stops.

That is exactly where a large strategy library becomes valuable. You can move beyond one generic template and test many different market ideas:

  1. Volume Spread Analysis.
  2. Cloud breakouts.
  3. ZigZag pivots.
  4. Volatility breakouts.
  5. Mean reversion.
  6. Momentum filters.
  7. Trend-following exits.

For this ETH-USD run, the VSA approach worked because it treated volume and bar structure as trade context instead of relying only on price direction.

Get the Complete Strategy Library

VSAStrategy is one of 500+ Backtrader-ready strategies included in the Mega Backtrader Strategy Pack.

Get the full package here: Mega Backtrader Strategy Pack

The package includes:

If you want to test, compare, document, and publish Backtrader strategies faster, this package gives you the full workflow.

Get the Mega Backtrader Strategy Pack

Disclaimer

This article is for research and educational use only. Backtest results are not financial advice, investment advice, or a guarantee of future performance. Always validate strategy logic, data quality, execution assumptions, costs, slippage, and risk before using any trading system.