Get the full strategy library: This case study 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 to stop building strategies from scratch and start testing hundreds of ready-made systems across stocks, crypto, ETFs, and other markets, this is the package built for that workflow.
Ethereum is volatile. That volatility can punish simple buy-and-hold entries, but it also creates structure: swing highs, swing lows, breakouts, reversals, support, and resistance.
This article walks through ZigZagStrategy, 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 | 86.58% |
| ETH buy-and-hold return | -48.06% |
| Excess return | 134.64% |
| Sharpe ratio | 2.49 |
| Max drawdown | 7.27% |
| Closed trades | 16 |
| Open trades | 0 |
| Win rate | 56.25% |
This is exactly the kind of strategy case study the package is built for: take a real strategy module, run it through the batch framework, inspect the code, publish the results, and show the charts.
ZigZagStrategy tries to turn noisy price action into
tradable swing structure.
It does four things:
The core idea is simple: do not trade every candle. Wait for meaningful swing structure, then trade confirmed breakouts or reversals.
The strategy exposes its behavior through Backtrader params:
params = (
('zigzag_pct', 0.01),
('pattern_lookback', 7),
('support_resistance_strength', 2),
('breakout_volume_multiplier', 1.2),
('volume_period', 7),
('trailing_stop_pct', 0.02),
)These settings define:
The strategy first tracks price and volume:
self.high = self.data.high
self.low = self.data.low
self.close = self.data.close
self.volume = self.data.volume
self.volume_sma = bt.indicators.SMA(
self.volume,
period=self.params.volume_period,
)It then maintains internal ZigZag state:
self.zigzag_points = []
self.current_direction = 0
self.current_extreme = None
self.support_levels = []
self.resistance_levels = []The ZigZag engine identifies reversals when price moves far enough from the prior extreme:
if self.current_direction == 1:
if current_high > prev_high:
self.current_extreme = (current_bar, current_high, current_low)
elif current_low < prev_high * (1 - threshold):
self.add_zigzag_point(prev_bar, prev_high, 'high')
self.current_direction = -1
self.current_extreme = (current_bar, current_high, current_low)Support and resistance levels are built from repeated pivot zones:
if point_type == 'low':
for i, (level, count) in enumerate(self.support_levels):
if abs(price - level) / level < tolerance:
self.support_levels[i] = (level, count + 1)
support_confirmed = True
breakThe pattern recognition logic checks common market structures. For example, a double bottom is detected when two lows form near the same area with a high between them:
if abs(low1 - low2) / max(low1, low2) < self.params.zigzag_pct:
return {
'type': 'double_bottom',
'support': min(low1, low2),
'resistance': high_between,
}The actual trading logic then requires volume confirmation:
volume_confirmation = (
self.volume[0] >
self.volume_sma[0] * self.params.breakout_volume_multiplier
)If a double bottom breaks above resistance with volume, the strategy buys:
if pattern['type'] == 'double_bottom' and volume_confirmation:
if current_price > pattern['resistance'] and not self.position:
self.order = self.buy()If a double top breaks below support with volume, the strategy sells:
elif pattern['type'] == 'double_top' and volume_confirmation:
if current_price < pattern['support'] and not self.position:
self.order = self.sell()The strategy also trades support/resistance breakouts when no named pattern is active:
if nearest_resistance and volume_confirmation:
if current_price > nearest_resistance * 1.001 and not self.position:
self.order = self.buy()Risk is managed with a trailing stop that adjusts as the trade moves in the strategy's favor:
new_stop_price = self.highest_price_since_entry * (
1 - self.params.trailing_stop_pct
)
if new_stop_price > self.trailing_stop_price:
self.trailing_stop_price = new_stop_price
self.cancel(self.stop_order)
self.stop_order = self.sell(
exectype=bt.Order.Stop,
price=self.trailing_stop_price,
)That makes the strategy more than a simple pattern detector. It has pivot detection, pattern logic, volume confirmation, support/resistance handling, and active stop management.
The article result was generated with:
python run_backtest.py --fast --fast-plots --fast-equity --refresh-data \
--workers 1 \
--symbol ETH-USD \
--period 1y \
--interval 1d \
--benchmark ETH-USD \
--stake-percent 99 \
--strategies strategies \
--out results \
--strategy-filter ZigZagStrategyBacktest 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 | $18,657.72 |
| Strategy file | strategies/ZigZagStrategy.py |
| Metric | Value |
|---|---|
| Strategy return | 86.58% |
| ETH buy-and-hold return | -48.06% |
| Excess return vs benchmark | 134.64% |
| Sharpe ratio | 2.49 |
| Max drawdown | 7.27% |
| Total trades | 16 |
| Closed trades | 16 |
| Open trades | 0 |
| Winning trades | 9 |
| Losing trades | 7 |
| Win rate | 56.25% |
| Runtime | 2.41 seconds |
The strategy did not need hundreds of trades to produce the result. It made 16 closed trades, won slightly more than half of them, and kept drawdown controlled while ETH buy-and-hold struggled over the same period.
The equity curve shows the strategy compounding from $10,000 to $18,657.72 while the ETH-USD benchmark declined over the same window.
The drawdown chart shows how risk was contained during the test. The strategy's maximum drawdown was 7.27%, while buy-and-hold carried much deeper downside exposure.
The rolling return chart shows how the strategy's edge developed over the backtest window instead of depending on only one isolated trade.
The daily return distribution gives a compact view of how the strategy's day-to-day returns were shaped during the test.
This strategy is a good example of why a large Backtrader library is useful.
Instead of starting from a blank file, the package gives you a complete research loop:
For this ETH-USD run, the ZigZag approach worked well because it was built for swing structure. It did not blindly hold through the whole market move. It reacted to pivots, patterns, support/resistance, volume confirmation, and trailing stops.
ZigZagStrategy 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:
strategies/
package.This makes it possible to publish many strategy articles like this one:
The package is not just a folder of code. It is a repeatable content and research engine for systematic trading strategy analysis.
If you want the same workflow used in this article, download the full package:
Get the Mega Backtrader Strategy Pack
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.