A estratégia original MetaTrader combina o indicador de canal "VIDYA N Bars Borders" com um módulo de dimensionamento de posição martingale. A porta StockSharp mantém a ideia de comprar quando o preço cair abaixo da banda inferior adaptativa e vender quando o preço subir acima da banda superior. O centro do canal é produzido por uma média móvel adaptativa (analógico VIDYA) e sua largura é controlada por um envelope Average True Range. Um bloco de gerenciamento de dinheiro aumenta o tamanho da negociação após perdas, ao mesmo tempo em que observa a posição máxima e os limites de exposição.
Lógica de negociação
Assine as velas do período selecionado.
Calcule uma média móvel adaptativa de Kaufman como um substituto VIDYA e um canal ATR em torno dele.
Quando o fechamento de uma vela finalizada cruzar abaixo da banda inferior, abra ou reverta para uma posição longa (a menos que a bandeira Reverse esteja habilitada, caso em que uma posição curta é aberta).
Quando o fechamento cruzar acima da banda superior, abra ou reverta para uma posição curta (ou longa se Reverse for verdadeiro).
Imponha uma distância mínima de preço entre entradas consecutivas para evitar reinserções muito próximas do preenchimento anterior.
Se o lucro flutuante na posição aberta atingir a meta monetária especificada, achate tudo e aguarde o próximo sinal.
Após cada negociação fechada, o próximo volume base é redefinido para o tamanho inicial (após uma negociação lucrativa) ou multiplicado pelo índice de martingale (após uma negociação perdedora). O volume resultante é alinhado à etapa do instrumento e são aplicados limites de volume por negociação e total.
Parâmetros
Nome
Descrição
Candle Type
Tipo de dados das velas a serem negociadas.
CMO Period
Janela de índice de eficiência para a média móvel adaptativa.
EMA Period
Período de suavização da média móvel adaptativa.
ATR Period
Número de barras para a meia largura do canal ATR.
Profit Target
Limite de lucro monetário que desencadeia uma saída total.
Increase Ratio
Multiplicador aplicado ao próximo volume de negociação após uma negociação perdida.
Max Position Volume
Teto rígido para um único volume de ordem/posição.
Max Total Volume
Limite superior da exposição total aberta pela estratégia.
Max Positions
Número máximo de posições simultâneas (a porta mantém uma posição líquida).
Minimum Step
Distância mínima entre duas entradas consecutivas, medida em pontos.
Base Volume
Tamanho inicial do pedido antes dos ajustes de martingale.
Reverse Signals
Inverte a interpretação longa/curta do rompimento do canal.
Notas de implementação
StockSharp não inclui uma implementação direta do VIDYA. A estratégia usa KaufmanAdaptiveMovingAverage com eficiência configurável e janelas de suavização para imitar o comportamento adaptativo do VIDYA. Isso mantém a capacidade de resposta próxima ao indicador original, ao mesmo tempo em que depende de componentes integrados.
Apenas uma posição líquida é gerenciada por vez. A versão MetaTrader enfileirou várias entradas pendentes; em StockSharp cada sinal abre uma nova posição ou reverte a atual. O dimensionamento Martingale é aplicado ao próximo tamanho de entrada em vez de adicionar novas camadas imediatamente.
O alinhamento mínimo de passos e volumes depende dos metadados do instrumento (PriceStep, VolumeStep, MinVolume, MaxVolume). Forneça esses valores ao configurar a estratégia para obter limites de execução precisos.
O rastreamento de lucro é baseado na estratégia PnL e no último fechamento da vela, o que é suficiente para backtests de alto nível. Para negociação ao vivo, conecte a estratégia a um portfólio que atualize os valores de PnL realizados.
Arquivos
CS/VidyaNBarsBordersMartingaleStrategy.cs — Implementação da estratégia em C#.
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()