This strategy converts the MQL5 expert Exp_BlauTVI into a StockSharp high level strategy. It uses the Blau True Volume Index (TVI) to detect reversals in the tick volume flow.
Idea
The True Volume Index separates up‑ticks and down‑ticks and smooths them with three exponential moving averages. The final value oscillates between -100 and +100 and represents the dominance of buyers or sellers. The strategy opens a long position when the indicator turns upward after a decline and opens a short position when the indicator turns downward after a rise. Existing positions in the opposite direction are closed.
Parameters
Length1 – first smoothing period for up and down ticks.
Length2 – second smoothing period.
Length3 – final smoothing period applied to the TVI.
CandleType – type of candles used for calculations (default: 4‑hour time frame).
Allow Buy Open – enable opening long positions.
Allow Sell Open – enable opening short positions.
Allow Buy Close – enable closing long positions when a sell signal appears.
Allow Sell Close – enable closing short positions when a buy signal appears.
Enable Stop Loss – use stop‑loss protection in points.
Stop Loss – stop‑loss value in points.
Enable Take Profit – use take‑profit protection in points.
Take Profit – take‑profit value in points.
Volume – order volume in lots.
Signals
Buy – when the previous TVI value is lower than the one before it and the current TVI value is greater than the previous value. If enabled, existing short positions are closed.
Sell – when the previous TVI value is higher than the one before it and the current TVI value is less than the previous value. If enabled, existing long positions are closed.
Only finished candles are processed and all calculations use tick volume of the candle. Stop‑loss and take‑profit are optional and expressed in price points.
Notes
The strategy uses the high level API: it subscribes to candles, calculates the indicator internally with ExponentialMovingAverage instances, and manages positions with BuyMarket and SellMarket methods. The chart shows the TVI indicator along with trades executed by the strategy.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on smoothed momentum direction changes (Blau TVI concept).
/// Uses triple-smoothed EMA direction changes for signals.
/// </summary>
public class BlauTviStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevEma;
private decimal _prevPrevEma;
private int _count;
public int Length { get => _length.Value; set => _length.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BlauTviStrategy()
{
_length = Param(nameof(Length), 12)
.SetGreaterThanZero()
.SetDisplay("Length", "Smoothing length", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevEma = 0;
_prevPrevEma = 0;
_count = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = Length };
SubscribeCandles(CandleType)
.Bind(ema, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
_count++;
if (_count < 3)
{
_prevPrevEma = _prevEma;
_prevEma = emaValue;
return;
}
var turnUp = _prevEma < _prevPrevEma && emaValue > _prevEma;
var turnDown = _prevEma > _prevPrevEma && emaValue < _prevEma;
if (turnUp && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (turnDown && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
_prevPrevEma = _prevEma;
_prevEma = emaValue;
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class blau_tvi_strategy(Strategy):
def __init__(self):
super(blau_tvi_strategy, self).__init__()
self._length = self.Param("Length", 12) \
.SetDisplay("Length", "Smoothing length", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_ema = 0.0
self._prev_prev_ema = 0.0
self._count = 0
@property
def Length(self):
return self._length.Value
@Length.setter
def Length(self, value):
self._length.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(blau_tvi_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self.Length
self.SubscribeCandles(self.CandleType) \
.Bind(ema, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_value)
self._count += 1
if self._count < 3:
self._prev_prev_ema = self._prev_ema
self._prev_ema = ema_val
return
turn_up = self._prev_ema < self._prev_prev_ema and ema_val > self._prev_ema
turn_down = self._prev_ema > self._prev_prev_ema and ema_val < self._prev_ema
if turn_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif turn_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_prev_ema = self._prev_ema
self._prev_ema = ema_val
def OnReseted(self):
super(blau_tvi_strategy, self).OnReseted()
self._prev_ema = 0.0
self._prev_prev_ema = 0.0
self._count = 0
def CreateClone(self):
return blau_tvi_strategy()