Volume Supertrend Strategy
This strategy uses Volume Supertrend indicators to generate signals. Long entry occurs when Volume > Avg(Volume) && Price > Supertrend (volume surge with uptrend). Short entry occurs when Volume > Avg(Volume) && Price < Supertrend (volume surge with downtrend). It is suitable for traders seeking opportunities in trend markets.
Testing indicates an average annual return of about 64%. It performs best in the forex market.
Details
- Entry Criteria:
- Long: Volume > Avg(Volume) && Price > Supertrend (volume surge with uptrend)
- Short: Volume > Avg(Volume) && Price < Supertrend (volume surge with downtrend)
- Long/Short: Both sides.
- Exit Criteria:
- Long: Exit long position when Supertrend turns down
- Short: Exit short position when Supertrend turns up
- Stops: Yes.
- Default Values:
VolumeAvgPeriod= 20SupertrendPeriod= 10SupertrendMultiplier= 3CandleType= TimeSpan.FromMinutes(5)StopLossPercent= 2 Avg(Volume) is the average volume of the previous VolumeAvgPeriod candles. A long closes once the Supertrend turns down and a short once it turns up; the stop is a fixed StopLossPercent of the entry price, watched between candles as well. An entry signal against an open position reverses it.
- Filters:
- Category: Trend
- Direction: Both
- Indicators: Volume Supertrend
- 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>
/// Volume Supertrend strategy.
/// A close above the Supertrend line on volume above the average of the previous VolumeAvgPeriod candles goes long and a close below it
/// on such volume goes short, reversing an opposite position. A long closes once the Supertrend turns down and a short once it turns up,
/// and a percent stop limits the loss.
/// </summary>
public class VolumeSupertrendStrategy : Strategy
{
private readonly StrategyParam<int> _volumeAvgPeriod;
private readonly StrategyParam<int> _supertrendPeriod;
private readonly StrategyParam<decimal> _supertrendMultiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _volumes = [];
/// <summary>
/// Previous candles the volume is averaged over.
/// </summary>
public int VolumeAvgPeriod
{
get => _volumeAvgPeriod.Value;
set => _volumeAvgPeriod.Value = value;
}
/// <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>
/// 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 VolumeSupertrendStrategy()
{
_volumeAvgPeriod = Param(nameof(VolumeAvgPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Avg Period", "Previous candles the volume is averaged over", "Volume");
_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");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_volumes.Clear();
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 == VolumeAvgPeriod ? _volumes.Average() : (decimal?)null;
_volumes.Add(candle.TotalVolume);
if (_volumes.Count > VolumeAvgPeriod)
_volumes.RemoveAt(0);
if (!supertrendValue.IsFormed || supertrendValue is not SuperTrendIndicatorValue value || average is not decimal avgVolume)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var line = value.Value;
var close = candle.ClosePrice;
var surge = candle.TotalVolume > avgVolume;
if (surge && close > line && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (surge && close < line && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && !value.IsUpTrend)
SellMarket(Position);
else if (Position < 0 && value.IsUpTrend)
BuyMarket(-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 volume_supertrend_strategy(Strategy):
"""
Volume Supertrend strategy.
A close above the Supertrend line on volume above the average of the previous VolumeAvgPeriod candles goes long and a close below it
on such volume goes short, reversing an opposite position. A long closes once the Supertrend turns down and a short once it turns up,
and a percent stop limits the loss.
"""
def __init__(self):
super(volume_supertrend_strategy, self).__init__()
self._volume_avg_period = self.Param("VolumeAvgPeriod", 20).SetGreaterThanZero().SetDisplay("Volume Avg Period", "Previous candles the volume is averaged over", "Volume")
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._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(5))).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 = []
def OnReseted(self):
super(volume_supertrend_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(volume_supertrend_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_avg_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 or average is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
line = supertrend_value.Value
close = candle.ClosePrice
surge = candle.TotalVolume > average
if surge and close > line and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif surge and close < line and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and not supertrend_value.IsUpTrend:
self.SellMarket(self.Position)
elif self.Position < 0 and supertrend_value.IsUpTrend:
self.BuyMarket(-self.Position)
def CreateClone(self):
return volume_supertrend_strategy()