Die ursprüngliche MetaTrader-Strategie kombiniert den Kanalindikator „VIDYA N Bars Borders“ mit einem Martingal-Positionsgrößenmodul. Der StockSharp-Port behält die Idee bei, zu kaufen, wenn der Preis unter das adaptive untere Band fällt, und zu verkaufen, wenn der Preis über das obere Band steigt. Die Kanalmitte wird durch einen adaptiven gleitenden Durchschnitt (VIDYA-Analog) erzeugt und seine Breite wird durch eine Average True Range-Hüllkurve gesteuert. Ein Money-Management-Block erhöht die Handelsgröße nach verlorenen Geschäften unter Einhaltung maximaler Positions- und Risikogrenzen.
Handelslogik
Abonnieren Sie die ausgewählten Zeitrahmenkerzen.
Berechnen Sie einen Kaufman Adaptive Moving Average als VIDYA-Ersatz und einen ATR-Kanal um ihn herum.
Wenn der Schlusskurs einer fertigen Kerze das untere Band unterschreitet, öffnen oder kehren Sie in eine Long-Position um (es sei denn, die Flagge Reverse ist aktiviert, in diesem Fall wird ein Short eröffnet).
Wenn der Schlusskurs das obere Band überschreitet, öffnen Sie oder gehen Sie eine Short-Position ein (oder eine Long-Position, wenn Reverse wahr ist).
Erzwingen Sie einen Mindestpreisabstand zwischen aufeinanderfolgenden Einträgen, um einen erneuten Einstieg zu nahe an der vorherigen Füllung zu vermeiden.
Wenn der variable Gewinn über die offene Position das festgelegte Geldziel erreicht, glätten Sie alles und warten Sie auf das nächste Signal.
Nach jedem abgeschlossenen Trade wird das nächste Basisvolumen entweder auf die ursprüngliche Größe zurückgesetzt (nach einem profitablen Trade) oder mit dem Martingal-Verhältnis multipliziert (nach einem verlustbringenden Trade). Das resultierende Volumen wird an den Instrumentenschritt angepasst, und es werden sowohl Obergrenzen pro Trade als auch Gesamtvolumen angewendet.
Parameter
Name
Beschreibung
Candle Type
Datentyp der zu handelnden Kerzen.
CMO Period
Effizienzverhältnisfenster für den adaptiven gleitenden Durchschnitt.
EMA Period
Glättungszeitraum des adaptiven gleitenden Durchschnitts.
ATR Period
Anzahl der Balken für die Kanalhalbbreite ATR.
Profit Target
Geldgewinnschwelle, die einen vollständigen Ausstieg auslöst.
Increase Ratio
Der Multiplikator wird auf das nächste Handelsvolumen nach einem Verlusthandel angewendet.
Max Position Volume
Harte Obergrenze für ein einzelnes Order-/Positionsvolumen.
Max Total Volume
Obergrenze für das durch die Strategie eröffnete Gesamtengagement.
Max Positions
Maximale Anzahl gleichzeitiger Positionen (der Port behält eine Nettoposition).
Minimum Step
Mindestabstand zwischen zwei aufeinanderfolgenden Einträgen, gemessen in Punkten.
Base Volume
Ausgangsordergröße vor Martingal-Anpassungen.
Reverse Signals
Kehrt die Long/Short-Interpretation des Kanalausbruchs um.
Hinweise zur Implementierung
StockSharp beinhaltet keine direkte VIDYA-Implementierung. Die Strategie nutzt KaufmanAdaptiveMovingAverage mit konfigurierbarer Effizienz und Glättungsfenstern, um das adaptive Verhalten von VIDYA nachzuahmen. Dadurch bleibt die Reaktionsfähigkeit nahe an der Originalanzeige, während auf eingebaute Komponenten zurückgegriffen wird.
Es wird jeweils nur eine Nettoposition verwaltet. Die Version MetaTrader hat mehrere ausstehende Einträge in die Warteschlange gestellt. In StockSharp öffnet jedes Signal entweder eine neue Position oder kehrt die aktuelle um. Die Martingale-Skalierung wird auf die nächste Eintragsgröße angewendet, anstatt sofort neue Ebenen hinzuzufügen.
Die Ausrichtung von Mindestschritt und Lautstärke hängt von den Instrumentenmetadaten ab (PriceStep, VolumeStep, MinVolume, MaxVolume). Geben Sie diese Werte an, wenn Sie die Strategie für genaue Ausführungslimits konfigurieren.
Die Gewinnverfolgung basiert auf der Strategie PnL und dem letzten Kerzenschluss, was für Backtests auf hohem Niveau ausreichend ist. Für den Live-Handel verbinden Sie die Strategie mit einem Portfolio, das die realisierten PnL-Werte aktualisiert.
Dateien
CS/VidyaNBarsBordersMartingaleStrategy.cs – C#-Implementierung der Strategie.
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()