Supertrend Volume Strategy
Supertrend Volume augments the Supertrend indicator with volume confirmation. Rising volume during a Supertrend flip strengthens the likelihood of a new impulse move.
Testing indicates an average annual return of about 145%. It performs best in the crypto market.
The strategy enters with the trend on a Supertrend signal only when accompanied by above-average volume.
Stops track the Supertrend line, exiting when price closes on the other side.
Details
- Entry Criteria: indicator signal
- Long/Short: Both
- Exit Criteria: stop-loss or opposite signal
- Stops: Yes, percent based
- Default Values:
CandleType= 15 minuteStopLossPercent= 2SupertrendPeriod= 10SupertrendMultiplier= 3VolumePeriod= 20 A Supertrend flip, a close on the other side of the line, closes a position against it. The flip opens a position in its direction only when the candle's volume is above the average of the previous VolumePeriod candles, so a confirmed flip reverses the position.
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Supertrend, Volume
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Supertrend Volume strategy.
/// A Supertrend flip happens when a candle closes on the other side of the line. A position closes on the flip against it,
/// and the flip opens a position in its direction only when the candle's volume is above the average of the previous VolumePeriod candles,
/// so a confirmed flip reverses the position. A percent stop limits the loss.
/// </summary>
public class SupertrendVolumeStrategy : Strategy
{
private readonly StrategyParam<int> _supertrendPeriod;
private readonly StrategyParam<decimal> _supertrendMultiplier;
private readonly StrategyParam<int> _volumePeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _volumes = [];
private bool? _prevIsUpTrend;
/// <summary>
/// ATR period of Supertrend.
/// </summary>
public int SupertrendPeriod
{
get => _supertrendPeriod.Value;
set => _supertrendPeriod.Value = value;
}
/// <summary>
/// ATR multiplier of Supertrend.
/// </summary>
public decimal SupertrendMultiplier
{
get => _supertrendMultiplier.Value;
set => _supertrendMultiplier.Value = value;
}
/// <summary>
/// Previous candles the volume is averaged over.
/// </summary>
public int VolumePeriod
{
get => _volumePeriod.Value;
set => _volumePeriod.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public SupertrendVolumeStrategy()
{
_supertrendPeriod = Param(nameof(SupertrendPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Supertrend Period", "ATR period of Supertrend", "Supertrend");
_supertrendMultiplier = Param(nameof(SupertrendMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("Supertrend Multiplier", "ATR multiplier of Supertrend", "Supertrend");
_volumePeriod = Param(nameof(VolumePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Period", "Previous candles the volume is averaged over", "Volume");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_volumes.Clear();
_prevIsUpTrend = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_volumes.Clear();
_prevIsUpTrend = null;
var supertrend = new SuperTrend { Length = SupertrendPeriod, Multiplier = SupertrendMultiplier };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(supertrend, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, supertrend);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue supertrendValue)
{
if (candle.State != CandleStates.Finished)
return;
// Volume is compared with the candles before this one.
var average = _volumes.Count == VolumePeriod ? _volumes.Average() : (decimal?)null;
_volumes.Add(candle.TotalVolume);
if (_volumes.Count > VolumePeriod)
_volumes.RemoveAt(0);
if (!supertrendValue.IsFormed || supertrendValue is not SuperTrendIndicatorValue value)
return;
var isUpTrend = value.IsUpTrend;
var wasUpTrend = _prevIsUpTrend;
_prevIsUpTrend = isUpTrend;
if (wasUpTrend is not bool was || was == isUpTrend)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var confirmed = average is decimal avgVolume && candle.TotalVolume > avgVolume;
if (isUpTrend)
{
if (confirmed && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (Position < 0)
BuyMarket(-Position);
}
else
{
if (confirmed && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0)
SellMarket(Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SuperTrend
from StockSharp.Algo.Strategies import Strategy
class supertrend_volume_strategy(Strategy):
"""
Supertrend Volume strategy.
A Supertrend flip happens when a candle closes on the other side of the line. A position closes on the flip against it,
and the flip opens a position in its direction only when the candle's volume is above the average of the previous VolumePeriod candles,
so a confirmed flip reverses the position. A percent stop limits the loss.
"""
def __init__(self):
super(supertrend_volume_strategy, self).__init__()
self._supertrend_period = self.Param("SupertrendPeriod", 10).SetGreaterThanZero().SetDisplay("Supertrend Period", "ATR period of Supertrend", "Supertrend")
self._supertrend_multiplier = self.Param("SupertrendMultiplier", 3.0).SetGreaterThanZero().SetDisplay("Supertrend Multiplier", "ATR multiplier of Supertrend", "Supertrend")
self._volume_period = self.Param("VolumePeriod", 20).SetGreaterThanZero().SetDisplay("Volume Period", "Previous candles the volume is averaged over", "Volume")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._volumes = []
self._prev_is_up_trend = None
def OnReseted(self):
super(supertrend_volume_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(supertrend_volume_strategy, self).OnStarted2(time)
self._reset_state()
supertrend = SuperTrend()
supertrend.Length = self._supertrend_period.Value
supertrend.Multiplier = Decimal(self._supertrend_multiplier.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(supertrend, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, supertrend)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, supertrend_value):
if candle.State != CandleStates.Finished:
return
# Volume is compared with the candles before this one.
period = self._volume_period.Value
average = None
if len(self._volumes) == period:
total = Decimal(0)
for volume in self._volumes:
total += volume
average = total / Decimal(period)
self._volumes.append(candle.TotalVolume)
if len(self._volumes) > period:
self._volumes.pop(0)
if not supertrend_value.IsFormed:
return
is_up_trend = supertrend_value.IsUpTrend
was_up_trend = self._prev_is_up_trend
self._prev_is_up_trend = is_up_trend
if was_up_trend is None or was_up_trend == is_up_trend:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
confirmed = average is not None and candle.TotalVolume > average
if is_up_trend:
if confirmed and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif self.Position < 0:
self.BuyMarket(-self.Position)
else:
if confirmed and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0:
self.SellMarket(self.Position)
def CreateClone(self):
return supertrend_volume_strategy()