Vlt Trader
This strategy detects periods of very low volatility and prepares breakout orders. When the range of the current candle becomes the smallest over the specified lookback period, the strategy places buy stop and sell stop orders around the previous candle.
Parameters
- Period – lookback period for the minimum range calculation.
- Pending level – distance in ticks from the previous high/low to place stop orders.
- Stop loss – protective stop in ticks.
- Take profit – profit target in ticks.
- Candle type – timeframe used for analysis.
Logic
- For each finished candle, compute its range (
High - Low). - Track the smallest range over the last Period candles.
- When the current range sets a new minimum, cancel existing orders and place stop orders above and below the previous candle at the given offset.
StartProtectionmanages stop-loss and take-profit once a position is opened.
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>
/// Volatility breakout strategy. Enters on a breakout of a low-volatility bar.
/// Uses market orders when price exceeds the high/low of the signal bar.
/// </summary>
public class VltTraderStrategy : Strategy
{
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _signalLifeBars;
private Lowest _lowest = null!;
private decimal _prevRange;
private decimal _prevMinRange;
private decimal _signalHigh;
private decimal _signalLow;
private bool _pendingBreakout;
private int _remainingSignalBars;
/// <summary>
/// Indicator period for lowest range.
/// </summary>
public int Period { get => _period.Value; set => _period.Value = value; }
/// <summary>
/// Stop loss in price steps.
/// </summary>
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
/// <summary>
/// Take profit in price steps.
/// </summary>
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
/// <summary>
/// Candle type for processing.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Number of bars to keep a pending breakout signal alive.
/// </summary>
public int SignalLifeBars { get => _signalLifeBars.Value; set => _signalLifeBars.Value = value; }
/// <summary>
/// Initializes a new instance of the <see cref="VltTraderStrategy"/> class.
/// </summary>
public VltTraderStrategy()
{
_period = Param(nameof(Period), 6)
.SetDisplay("Period", "Indicator period", "General")
.SetOptimize(5, 30, 5);
_stopLoss = Param(nameof(StopLoss), 550m)
.SetDisplay("Stop loss", "Stop loss in price steps", "Risk")
.SetOptimize(100m, 2000m, 100m);
_takeProfit = Param(nameof(TakeProfit), 550m)
.SetDisplay("Take profit", "Take profit in price steps", "Risk")
.SetOptimize(100m, 2000m, 100m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle type", "Candles for calculation", "General");
_signalLifeBars = Param(nameof(SignalLifeBars), 3)
.SetDisplay("Signal Life Bars", "Number of bars to keep pending breakout signal", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRange = 0m;
_prevMinRange = decimal.MaxValue;
_signalHigh = 0m;
_signalLow = 0m;
_pendingBreakout = false;
_remainingSignalBars = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_lowest = new Lowest { Length = Period };
_prevRange = 0m;
_prevMinRange = decimal.MaxValue;
_signalHigh = 0m;
_signalLow = 0m;
_pendingBreakout = false;
_remainingSignalBars = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var step = Security?.PriceStep ?? 1m;
StartProtection(
takeProfit: new Unit(TakeProfit * step, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss * step, UnitTypes.Absolute));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var range = candle.HighPrice - candle.LowPrice;
var lowestResult = _lowest.Process(range, candle.OpenTime, true);
if (!_lowest.IsFormed)
return;
var minRange = lowestResult.ToDecimal();
// Check for pending breakout entry
if (_pendingBreakout && Position == 0)
{
if (candle.HighPrice >= _signalHigh && candle.ClosePrice > _signalHigh)
{
BuyMarket();
_pendingBreakout = false;
_remainingSignalBars = 0;
}
else if (candle.LowPrice <= _signalLow && candle.ClosePrice < _signalLow)
{
SellMarket();
_pendingBreakout = false;
_remainingSignalBars = 0;
}
else if (--_remainingSignalBars <= 0)
_pendingBreakout = false;
}
// Detect low-volatility signal: range drops below minimum
var hasPreviousRange = _prevMinRange != decimal.MaxValue;
var isSignal = hasPreviousRange &&
range <= minRange * 1.08m &&
_prevRange > _prevMinRange * 1.05m;
_prevRange = range;
_prevMinRange = minRange;
if (isSignal && Position == 0)
{
_signalHigh = candle.HighPrice;
_signalLow = candle.LowPrice;
_pendingBreakout = true;
_remainingSignalBars = SignalLifeBars;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import Lowest
from StockSharp.Algo.Strategies import Strategy
class vlt_trader_strategy(Strategy):
def __init__(self):
super(vlt_trader_strategy, self).__init__()
self._period = self.Param("Period", 6) \
.SetDisplay("Period", "Indicator period", "General")
self._stop_loss = self.Param("StopLoss", 550.0) \
.SetDisplay("Stop loss", "Stop loss in price steps", "Risk")
self._take_profit_param = self.Param("TakeProfit", 550.0) \
.SetDisplay("Take profit", "Take profit in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle type", "Candles for calculation", "General")
self._signal_life_bars = self.Param("SignalLifeBars", 3) \
.SetDisplay("Signal Life Bars", "Number of bars to keep pending breakout signal", "General")
self._prev_range = 0.0
self._prev_min_range = None
self._signal_high = 0.0
self._signal_low = 0.0
self._pending_breakout = False
self._remaining_signal_bars = 0
self._ranges = []
@property
def period(self):
return self._period.Value
@property
def stop_loss(self):
return self._stop_loss.Value
@property
def take_profit(self):
return self._take_profit_param.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def signal_life_bars(self):
return self._signal_life_bars.Value
def OnReseted(self):
super(vlt_trader_strategy, self).OnReseted()
self._prev_range = 0.0
self._prev_min_range = None
self._signal_high = 0.0
self._signal_low = 0.0
self._pending_breakout = False
self._remaining_signal_bars = 0
self._ranges = []
def OnStarted2(self, time):
super(vlt_trader_strategy, self).OnStarted2(time)
self._prev_range = 0.0
self._prev_min_range = None
self._signal_high = 0.0
self._signal_low = 0.0
self._pending_breakout = False
self._remaining_signal_bars = 0
self._ranges = []
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 1.0
self.StartProtection(
Unit(float(self.take_profit) * step, UnitTypes.Absolute),
Unit(float(self.stop_loss) * step, UnitTypes.Absolute))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
range_val = float(candle.HighPrice) - float(candle.LowPrice)
self._ranges.append(range_val)
period = self.period
if len(self._ranges) > period:
self._ranges.pop(0)
if len(self._ranges) < period:
return
min_range = min(self._ranges)
# Check for pending breakout entry
if self._pending_breakout and self.Position == 0:
if float(candle.HighPrice) >= self._signal_high and float(candle.ClosePrice) > self._signal_high:
self.BuyMarket()
self._pending_breakout = False
self._remaining_signal_bars = 0
elif float(candle.LowPrice) <= self._signal_low and float(candle.ClosePrice) < self._signal_low:
self.SellMarket()
self._pending_breakout = False
self._remaining_signal_bars = 0
else:
self._remaining_signal_bars -= 1
if self._remaining_signal_bars <= 0:
self._pending_breakout = False
# Detect low-volatility signal
has_previous_range = self._prev_min_range is not None
is_signal = (has_previous_range and
range_val <= min_range * 1.08 and
self._prev_range > self._prev_min_range * 1.05)
self._prev_range = range_val
self._prev_min_range = min_range
if is_signal and self.Position == 0:
self._signal_high = float(candle.HighPrice)
self._signal_low = float(candle.LowPrice)
self._pending_breakout = True
self._remaining_signal_bars = self.signal_life_bars
def CreateClone(self):
return vlt_trader_strategy()