Volume ValueWhen Velocity Strategy
This strategy seeks long entries when volume expands, the market is oversold based on RSI, volatility measured by ATR is contracting and the distance between recent SMA breakouts exceeds a specified value. When all conditions are satisfied a market buy order is issued.
Parameters
- RSI Length – period for RSI.
- RSI Oversold – oversold threshold.
- ATR Small / ATR Big – periods for ATR comparison.
- Distance – minimum difference between breakout prices.
- Candle Type – timeframe of input candles.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy inspired by "Volume ValueWhen Velocity".
/// It looks for oversold RSI, low volatility ATR condition and compares previous
/// SMA breakout prices to measure distance. When all conditions are met a long trade is opened.
/// </summary>
public class VolumeValueWhenVelocityStrategy : Strategy
{
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _rsiOversold;
private readonly StrategyParam<int> _atrSmall;
private readonly StrategyParam<int> _atrBig;
private readonly StrategyParam<int> _distance;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevVolume1;
private decimal _prevVolume2;
private decimal _lastCross;
private decimal _prevCross;
private int _barsSinceCross;
/// <summary>
/// RSI length.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Oversold level.
/// </summary>
public int RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// Short ATR period.
/// </summary>
public int AtrSmall
{
get => _atrSmall.Value;
set => _atrSmall.Value = value;
}
/// <summary>
/// Long ATR period.
/// </summary>
public int AtrBig
{
get => _atrBig.Value;
set => _atrBig.Value = value;
}
/// <summary>
/// Minimum distance between SMA breakout prices.
/// </summary>
public int Distance
{
get => _distance.Value;
set => _distance.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="VolumeValueWhenVelocityStrategy"/>.
/// </summary>
public VolumeValueWhenVelocityStrategy()
{
_rsiLength = Param(nameof(RsiLength), 40)
.SetDisplay("RSI Length", "RSI period", "Indicators");
_rsiOversold = Param(nameof(RsiOversold), 60)
.SetDisplay("RSI Oversold", "Oversold level", "Indicators");
_atrSmall = Param(nameof(AtrSmall), 5)
.SetDisplay("ATR Small", "Short ATR period", "Indicators");
_atrBig = Param(nameof(AtrBig), 14)
.SetDisplay("ATR Big", "Long ATR period", "Indicators");
_distance = Param(nameof(Distance), 170)
.SetDisplay("Distance", "Minimum distance between breakouts", "Strategy");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevVolume1 = 0;
_prevVolume2 = 0;
_lastCross = 0;
_prevCross = 0;
_barsSinceCross = int.MaxValue;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atrShort = new AverageTrueRange { Length = AtrSmall };
var atrLong = new AverageTrueRange { Length = AtrBig };
var sma = new SMA { Length = 13 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, atrShort, atrLong, sma, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(3, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal atrShortValue, decimal atrLongValue, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
// track volumes for simple comparison
if (_prevVolume1 == 0)
{
_prevVolume1 = candle.TotalVolume;
return;
}
// update bars since last SMA breakout
if (candle.ClosePrice > smaValue)
{
_barsSinceCross = 0;
_prevCross = _lastCross;
_lastCross = candle.ClosePrice;
}
else
{
_barsSinceCross++;
}
var prevCloseChange = _prevCross - _lastCross;
var wasOversold = rsiValue <= RsiOversold;
var atrCondition = atrShortValue < atrLongValue;
var volumeCondition = candle.TotalVolume > _prevVolume1 && _prevVolume1 > _prevVolume2;
_prevVolume2 = _prevVolume1;
_prevVolume1 = candle.TotalVolume;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (volumeCondition && atrCondition && wasOversold && prevCloseChange > Distance && _barsSinceCross < 5 && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import AverageTrueRange, RelativeStrengthIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class volume_value_when_velocity_strategy(Strategy):
def __init__(self):
super(volume_value_when_velocity_strategy, self).__init__()
self._rsi_length = self.Param("RsiLength", 40) \
.SetDisplay("RSI Length", "RSI period", "Indicators")
self._rsi_oversold = self.Param("RsiOversold", 60) \
.SetDisplay("RSI Oversold", "Oversold level", "Indicators")
self._atr_small = self.Param("AtrSmall", 5) \
.SetDisplay("ATR Small", "Short ATR period", "Indicators")
self._atr_big = self.Param("AtrBig", 14) \
.SetDisplay("ATR Big", "Long ATR period", "Indicators")
self._distance = self.Param("Distance", 170) \
.SetDisplay("Distance", "Minimum distance between breakouts", "Strategy")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_volume1 = 0.0
self._prev_volume2 = 0.0
self._last_cross = 0.0
self._prev_cross = 0.0
self._bars_since_cross = 2147483647
@property
def rsi_length(self):
return self._rsi_length.Value
@property
def rsi_oversold(self):
return self._rsi_oversold.Value
@property
def atr_small(self):
return self._atr_small.Value
@property
def atr_big(self):
return self._atr_big.Value
@property
def distance(self):
return self._distance.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(volume_value_when_velocity_strategy, self).OnReseted()
self._prev_volume1 = 0.0
self._prev_volume2 = 0.0
self._last_cross = 0.0
self._prev_cross = 0.0
self._bars_since_cross = 2147483647
def OnStarted2(self, time):
super(volume_value_when_velocity_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self.rsi_length
atr_short = AverageTrueRange()
atr_short.Length = self.atr_small
atr_long = AverageTrueRange()
atr_long.Length = self.atr_big
sma = SimpleMovingAverage()
sma.Length = 13
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, atr_short, atr_long, sma, self.on_process).Start()
self.StartProtection(Unit(2, UnitTypes.Percent), Unit(3, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def on_process(self, candle, rsi_value, atr_short_value, atr_long_value, sma_value):
if candle.State != CandleStates.Finished:
return
rsi_value = float(rsi_value)
atr_short_value = float(atr_short_value)
atr_long_value = float(atr_long_value)
sma_value = float(sma_value)
close = float(candle.ClosePrice)
vol = float(candle.TotalVolume)
# track volumes for simple comparison
if self._prev_volume1 == 0:
self._prev_volume1 = vol
return
# update bars since last SMA breakout
if close > sma_value:
self._bars_since_cross = 0
self._prev_cross = self._last_cross
self._last_cross = close
else:
self._bars_since_cross += 1
prev_close_change = self._prev_cross - self._last_cross
was_oversold = rsi_value <= float(self.rsi_oversold)
atr_condition = atr_short_value < atr_long_value
volume_condition = vol > self._prev_volume1 and self._prev_volume1 > self._prev_volume2
self._prev_volume2 = self._prev_volume1
self._prev_volume1 = vol
if volume_condition and atr_condition and was_oversold and prev_close_change > float(self.distance) and self._bars_since_cross < 5 and self.Position <= 0:
self.BuyMarket()
def CreateClone(self):
return volume_value_when_velocity_strategy()