View on GitHub

VLT Trader Straddle Strategy

Overview

The VLT Trader strategy is a StockSharp conversion of the MetaTrader 4 expert advisor "VLT_TRADER". The original idea searches for a period of extremely low volatility and then prepares a breakout straddle around the most recent candle. When the latest completed candle has the smallest range compared with a configurable number of earlier candles, the strategy positions stop orders above and below that candle in anticipation of a volatility expansion.

Trading logic

  • Subscribe to the configured candle series and compute the range (high minus low) for each bar.
  • Track the minimum range among the previous LookbackCandles bars using the Lowest indicator.
  • Once the most recent finished candle has a smaller range than this historical minimum, prepare the breakout orders for the following session.
  • Place a buy stop above the previous high plus EntryOffsetPoints and a sell stop below the previous low minus the same offset.
  • Attach fixed-distance stops and targets to every pending order (StopLossPoints and TakeProfitPoints).
  • Leave both pending orders active. Whichever side triggers first becomes a market position, while the opposite stop remains in the book and can activate later if the market reverses.
  • When a pending order is filled or cancelled, the corresponding reference is cleared so that new straddles can be created after all positions and orders are closed.

Risk management

  • Trade size is controlled through OrderVolume and is rounded to the instrument's volume step and limits.
  • Stop loss and take profit distances are expressed in price steps (points) and converted to actual prices using the instrument's PriceStep.

Parameters

Parameter Description
OrderVolume Lot size used when creating the pending orders.
EntryOffsetPoints Additional points added to the previous high/low when placing stop entries.
TakeProfitPoints Take profit distance attached to each order.
StopLossPoints Stop loss distance attached to each order.
LookbackCandles Number of prior candles used to measure the minimum historical range.
CandleType Timeframe of the candle series that feeds the strategy.

Notes

  • The strategy requires a valid PriceStep on the instrument; otherwise no orders are placed.
  • Because stop and take-profit levels are transmitted alongside the pending orders, fill prices in StockSharp may differ slightly from MetaTrader depending on broker execution rules.
  • The implementation relies exclusively on high-level APIs (SubscribeCandles + Bind) and the standard Lowest indicator to mirror the volatility check from the original EA.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class VltTraderStraddleStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevMid;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public VltTraderStraddleStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 48).SetDisplay("Channel Period", "Breakout lookback", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14).SetDisplay("ATR Period", "ATR lookback", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 150).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = default;
		_prevMid = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = 0;
		_prevMid = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var highest = new Highest { Length = ChannelPeriod };
		var lowest = new Lowest { Length = ChannelPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(highest, lowest, atr, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest, decimal atr)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		var mid = (highest + lowest) / 2;
		if (!_hasPrev) { _prevClose = close; _prevMid = mid; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevClose = close;
			_prevMid = mid;
			return;
		}

		if (_prevClose <= _prevMid && close > mid && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevClose >= _prevMid && close < mid && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevClose = close;
		_prevMid = mid;
	}
}