Auf GitHub ansehen

VIDYA N-Bars-Ränder Martingale

Überblick

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

  1. Abonnieren Sie die ausgewählten Zeitrahmenkerzen.
  2. Berechnen Sie einen Kaufman Adaptive Moving Average als VIDYA-Ersatz und einen ATR-Kanal um ihn herum.
  3. 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).
  4. Wenn der Schlusskurs das obere Band überschreitet, öffnen Sie oder gehen Sie eine Short-Position ein (oder eine Long-Position, wenn Reverse wahr ist).
  5. Erzwingen Sie einen Mindestpreisabstand zwischen aufeinanderfolgenden Einträgen, um einen erneuten Einstieg zu nahe an der vorherigen Füllung zu vermeiden.
  6. Wenn der variable Gewinn über die offene Position das festgelegte Geldziel erreicht, glätten Sie alles und warten Sie auf das nächste Signal.
  7. 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();
	}
}