The original MetaTrader strategy combines the "VIDYA N Bars Borders" channel indicator with a martingale position sizing module. The StockSharp port keeps the idea of buying when price drops below the adaptive lower band and selling when price rallies above the upper band. The channel centre is produced by an adaptive moving average (VIDYA analogue) and its width is controlled by an Average True Range envelope. A money management block increases the trade size after losing trades while observing maximum position and exposure limits.
Trading logic
Subscribe to the selected timeframe candles.
Calculate a Kaufman Adaptive Moving Average as a VIDYA substitute and an ATR channel around it.
When the close of a finished candle crosses below the lower band, open or reverse into a long position (unless the Reverse flag is enabled, in which case a short is opened).
When the close crosses above the upper band, open or reverse into a short position (or a long if Reverse is true).
Enforce a minimum price distance between consecutive entries to avoid re-entering too close to the previous fill.
If the floating profit across the open position reaches the specified money target, flatten everything and wait for the next signal.
After each closed trade the next base volume is either reset to the initial size (after a profitable trade) or multiplied by the martingale ratio (after a losing trade). The resulting volume is aligned to the instrument step, and both per-trade and total volume caps are applied.
Parameters
Name
Description
Candle Type
Data type of candles to trade.
CMO Period
Efficiency ratio window for the adaptive moving average.
EMA Period
Smoothing period of the adaptive moving average.
ATR Period
Number of bars for the ATR channel half-width.
Profit Target
Money profit threshold that triggers a full exit.
Increase Ratio
Multiplier applied to the next trade volume after a losing trade.
Max Position Volume
Hard ceiling for a single order/position volume.
Max Total Volume
Upper bound on the total exposure opened by the strategy.
Max Positions
Maximum number of concurrent positions (the port keeps one net position).
Minimum Step
Minimum distance between two consecutive entries, measured in points.
Base Volume
Starting order size before martingale adjustments.
Reverse Signals
Inverts the long/short interpretation of the channel breakout.
Implementation notes
StockSharp does not include a direct VIDYA implementation. The strategy uses KaufmanAdaptiveMovingAverage with configurable efficiency and smoothing windows to mimic the adaptive behaviour of VIDYA. This keeps responsiveness close to the original indicator while relying on built-in components.
Only one net position is managed at a time. The MetaTrader version queued multiple pending entries; in StockSharp each signal either opens a fresh position or reverses the current one. Martingale scaling is applied to the next entry size instead of adding new layers immediately.
Minimum step and volume alignment rely on the instrument metadata (PriceStep, VolumeStep, MinVolume, MaxVolume). Provide these values when configuring the strategy for accurate execution limits.
Profit tracking is based on the strategy PnL and the latest candle close, which is sufficient for high-level backtests. For live trading connect the strategy to a portfolio that updates realized PnL values.
Files
CS/VidyaNBarsBordersMartingaleStrategy.cs — C# implementation of 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>
/// Simplified from "VIDYA N Bars Borders Martingale" MetaTrader expert.
/// Uses EMA as adaptive MA proxy and a range-based channel from recent N bars.
/// Buys when price closes below lower band, sells when above upper band.
/// Includes simple martingale volume increase on losing trades.
/// </summary>
public class VidyaNBarsBordersMartingaleStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _rangePeriod;
private readonly StrategyParam<decimal> _martingaleMultiplier;
private ExponentialMovingAverage _ema;
private readonly Queue<decimal> _highHistory = new();
private readonly Queue<decimal> _lowHistory = new();
private decimal _currentVolume;
private decimal _entryPrice;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
public int RangePeriod
{
get => _rangePeriod.Value;
set => _rangePeriod.Value = value;
}
public decimal MartingaleMultiplier
{
get => _martingaleMultiplier.Value;
set => _martingaleMultiplier.Value = value;
}
public VidyaNBarsBordersMartingaleStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Trading candle type", "General");
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Smoothing period for adaptive MA proxy", "Indicators");
_rangePeriod = Param(nameof(RangePeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Range Period", "Number of bars for high/low range channel", "Indicators");
_martingaleMultiplier = Param(nameof(MartingaleMultiplier), 1.25m)
.SetDisplay("Martingale Multiplier", "Volume multiplier after losing trade", "Risk");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
_highHistory.Clear();
_lowHistory.Clear();
_currentVolume = Volume > 0 ? Volume : 1;
_entryPrice = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
// Track high/low for range calculation
_highHistory.Enqueue(candle.HighPrice);
_lowHistory.Enqueue(candle.LowPrice);
if (_highHistory.Count > RangePeriod)
{
_highHistory.Dequeue();
_lowHistory.Dequeue();
}
if (!_ema.IsFormed || _highHistory.Count < RangePeriod)
return;
// Compute range from recent bars
decimal highest = decimal.MinValue;
decimal lowest = decimal.MaxValue;
var highs = _highHistory.ToArray();
var lows = _lowHistory.ToArray();
foreach (var h in highs)
if (h > highest) highest = h;
foreach (var l in lows)
if (l < lowest) lowest = l;
var range = (highest - lowest) * 0.75m;
if (range <= 0)
return;
var upper = emaValue + range;
var lower = emaValue - range;
var close = candle.ClosePrice;
var baseVolume = Volume > 0 ? Volume : 1;
var maxVolume = baseVolume * 8;
var nextVolume = _currentVolume;
if (close < lower)
{
// Price below lower band -> buy signal
if (Position < 0)
{
var wasLoss = close > _entryPrice;
nextVolume = wasLoss
? Math.Min(_currentVolume * MartingaleMultiplier, maxVolume)
: baseVolume;
}
if (Position <= 0)
{
BuyMarket(Position < 0 ? Math.Abs(Position) + nextVolume : nextVolume);
_currentVolume = nextVolume;
_entryPrice = close;
}
}
else if (close > upper)
{
// Price above upper band -> sell signal
if (Position > 0)
{
var wasLoss = close < _entryPrice;
nextVolume = wasLoss
? Math.Min(_currentVolume * MartingaleMultiplier, maxVolume)
: baseVolume;
}
if (Position >= 0)
{
SellMarket(Position > 0 ? Math.Abs(Position) + nextVolume : nextVolume);
_currentVolume = nextVolume;
_entryPrice = close;
}
}
}
/// <inheritdoc />
protected override void OnReseted()
{
_ema = null;
_highHistory.Clear();
_lowHistory.Clear();
_currentVolume = 0;
_entryPrice = 0;
base.OnReseted();
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class vidya_n_bars_borders_martingale_strategy(Strategy):
def __init__(self):
super(vidya_n_bars_borders_martingale_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._ema_period = self.Param("EmaPeriod", 20)
self._range_period = self.Param("RangePeriod", 10)
self._high_history = []
self._low_history = []
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def RangePeriod(self):
return self._range_period.Value
@RangePeriod.setter
def RangePeriod(self, value):
self._range_period.Value = value
def OnReseted(self):
super(vidya_n_bars_borders_martingale_strategy, self).OnReseted()
self._high_history = []
self._low_history = []
self._entry_price = 0.0
def OnStarted2(self, time):
super(vidya_n_bars_borders_martingale_strategy, self).OnStarted2(time)
self._high_history = []
self._low_history = []
self._entry_price = 0.0
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, self._process_candle).Start()
def _process_candle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_value)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
rng_period = self.RangePeriod
self._high_history.append(high)
self._low_history.append(low)
if len(self._high_history) > rng_period:
self._high_history.pop(0)
self._low_history.pop(0)
if len(self._high_history) < rng_period:
return
highest = max(self._high_history)
lowest = min(self._low_history)
rng = (highest - lowest) * 0.75
if rng <= 0:
return
upper = ema_val + rng
lower = ema_val - rng
if close < lower and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
elif close > upper and self.Position >= 0:
self.SellMarket()
self._entry_price = close
def CreateClone(self):
return vidya_n_bars_borders_martingale_strategy()