Auf GitHub ansehen

Cross-Strategien für den gleitenden Durchschnittspreis

Überblick

Dieses Paket enthält zwei C#-Strategieports der MetaTrader 5 Beispiele, die sich in MQL/50198 befinden:

  • MovingAveragePriceCrossStrategy – ein minimalistisches Crossover-System mit gleitendem Durchschnitt und Preis, das jeweils eine einzelne Position handelt.
  • MovingAverageMartingaleStrategy – eine erweiterte Version, die die Positionsgröße im Martingal-Stil nach Verlusten anwendet und gleichzeitig die gleiche Preis-/Durchschnitts-Crossover-Logik beibehält.

Beide Implementierungen basieren auf dem übergeordneten StockSharp API, verwenden Kerzenabonnements für die Signalauswertung und stellen MetaTrader-kompatible Parameter für Stop-Loss- und Take-Profit-Abstände bereit.

Dateien

Datei Beschreibung
CS/MovingAveragePriceCrossStrategy.cs Basispreis/MA-Crossover mit festem Volumen und statischen Schutzaufträgen.
CS/MovingAverageMartingaleStrategy.cs Martingale-Variante, die Volumen und Schutzabstände nach verlorenen Trades skaliert.

Handelslogik

MovingAveragePriceCrossStrategy

  1. Abonniert Kerzen des konfigurierten Zeitrahmens und berechnet einen einfachen gleitenden Durchschnitt (SMA).
  2. Wertet Signale nur bei fertigen Kerzen aus, um das MT5-Expertenverhalten nachzuahmen.
  3. Erkennt Überschneidungen zwischen dem SMA und dem Schlusskurs der Kerze anhand der letzten beiden abgeschlossenen Kerzen:
    • Verkaufen, wenn der gleitende Durchschnitt über den Kerzenschluss steigt (der Preis hat den Durchschnitt unterschritten).
    • Kaufen, wenn der gleitende Durchschnitt unter den Kerzenschluss fällt (der Preis hat den Durchschnitt überschritten).
  4. Platziert eine einzelne Marktorder pro Signal, wenn derzeit keine Position offen ist.
  5. Wendet automatischen Schutz über StartProtection an, wobei MetaTrader Punktabstände in absolute Preisversätze umgewandelt werden.

MovingAverageMartingaleStrategie

  1. Hat das gleiche Kerzenabonnement und die gleiche SMA-Signalgenerierung wie die Basisstrategie.
  2. Verfolgt den realisierten PnL nach jeder geschlossenen Position und speichert das letzte Handelsergebnis.
  3. Wenn ein neues Crossover-Signal erscheint und keine Position offen ist:
    • Wenn der letzte Trade verlustbringend war, multipliziert das nächste Handelsvolumen mit VolumeMultiplier (begrenzt auf MaxVolume) und vergrößert die Stop-Loss- und Take-Profit-Distanzen um TargetMultiplier.
    • Wenn der letzte Handel profitabel war, werden das Handelsvolumen und die Schutzabstände auf ihre ursprünglichen Werte zurückgesetzt.
  4. Wendet StartProtection mit den dynamisch angepassten Offsets unmittelbar vor dem Senden der Marktorder an.
  5. Handelt weiterhin jeweils nur eine Position, entsprechend der ursprünglichen Expert-Advisor-Logik.

Risikomanagement

  • Schutzniveaus werden in MetaTrader Punkten ausgedrückt und anhand der erkannten Pip-Größe (PriceStep angepasst an 3/5 Dezimal-FX-Symbole) automatisch in absolute Preisversätze übersetzt.
  • Die Martingale-Strategie hält die Stop-Loss- und Take-Profit-Multiplikatoren begrenzt, um außer Kontrolle geratene Distanzen zu verhindern.
  • Das Positionsvolumen wird an VolumeStep, MinVolume und optional an MaxVolume des Instruments angepasst, um ungültige Aufträge zu vermeiden.

Parameter

Gemeinsame Eingaben

Parameter Strategie Standard Beschreibung
CandleType beides 1 minute Kerzendatentyp, der für die Signalberechnung verwendet wird.
MaPeriod beides 50 Länge des einfachen gleitenden Durchschnitts.

MovingAveragePriceCrossStrategy

Parameter Standard Beschreibung
OrderVolume 1 Auftragsvolumen auf den Instrumentenschritt abgestimmt.
TakeProfitPoints 150 Take-Profit-Distanz in MetaTrader Punkten (0 Deaktivierungen).
StopLossPoints 150 Stop-Loss-Distanz in MetaTrader Punkten (0 deaktiviert).

MovingAverageMartingaleStrategie

Parameter Standard Beschreibung
StartingVolume 1 Nach profitablen Trades wird das Basisvolumen wiederhergestellt.
MaxVolume 5 Maximale Lautstärke nach Anwendung von Multiplikatoren.
TakeProfitPoints 100 Anfängliche Take-Profit-Distanz in MetaTrader Punkten.
StopLossPoints 300 Anfängliche Stop-Loss-Distanz in MetaTrader Punkten.
VolumeMultiplier 2 Faktor, der nach einem Verlust auf das nächste Auftragsvolumen angewendet wird.
TargetMultiplier 2 Faktor, der auf Stop-Loss- und Take-Profit-Distanzen nach einem Verlust angewendet wird.

Nutzungshinweise

  • MetaTrader „Punkte“ entsprechen bei den meisten Instrumenten einem PriceStep; Die Strategien multiplizieren sich automatisch mit 10 für FX-Symbole mit 3 oder 5 Dezimalstellen, um dem MT5-Verhalten zu entsprechen.
  • Beide Strategien erfordern nur ein Wertpapier und ignorieren Signale, während eine Position offen ist, wodurch die ursprüngliche PositionsTotal()-Abwehr der Experten reproduziert wird.
  • Aktivieren Sie die Optimierung der bereitgestellten Parameter im StockSharp-Designer, um die MT5-Eingabeoptimierung zu replizieren.
namespace StockSharp.Samples.Strategies;

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

using StockSharp.Algo;

/// <summary>
/// Moving average crossover strategy with martingale money management converted from the MT5 "MovingAverageMartinGale" expert advisor.
/// Scales trade volume and protective distances after losses while resetting to the base configuration after profitable trades.
/// </summary>
public class MovingAverageMartingaleStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<decimal> _startingVolume;
	private readonly StrategyParam<decimal> _maxVolume;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<decimal> _volumeMultiplier;
	private readonly StrategyParam<decimal> _targetMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private SMA _sma;
	private decimal? _previousClose;
	private decimal? _previousMa;
	private decimal? _currentClose;
	private decimal? _currentMa;
	private decimal _pipSize;

	private decimal _currentVolume;
	private decimal _currentTakeProfitPoints;
	private decimal _currentStopLossPoints;
	private decimal _lastRealizedPnL;
	private decimal _previousPosition;
	private decimal _lastTradeResult;

	/// <summary>
	/// Initializes a new instance of the <see cref="MovingAverageMartingaleStrategy"/> class.
	/// </summary>
	public MovingAverageMartingaleStrategy()
	{
		_maPeriod = Param(nameof(MaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("MA period", "Length of the simple moving average used for entries.", "Indicator")
			;

		_startingVolume = Param(nameof(StartingVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Starting volume", "Base order volume used after profitable trades.", "Money management")
			;

		_maxVolume = Param(nameof(MaxVolume), 5m)
			.SetGreaterThanZero()
			.SetDisplay("Maximum volume", "Upper limit for martingale scaling.", "Money management")
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 100)
			.SetNotNegative()
			.SetDisplay("Take profit (points)", "Initial profit target distance expressed in MetaTrader points.", "Risk")
			;

		_stopLossPoints = Param(nameof(StopLossPoints), 300)
			.SetNotNegative()
			.SetDisplay("Stop loss (points)", "Initial stop-loss distance expressed in MetaTrader points.", "Risk")
			;

		_volumeMultiplier = Param(nameof(VolumeMultiplier), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Volume multiplier", "Factor applied to the next trade volume after a loss.", "Money management")
			;

		_targetMultiplier = Param(nameof(TargetMultiplier), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Target multiplier", "Factor applied to stop-loss and take-profit distances after a loss.", "Money management")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle type", "Primary timeframe processed by the strategy.", "General");
	}

	/// <summary>
	/// Moving average period used for generating signals.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Base position volume restored after profitable trades.
	/// </summary>
	public decimal StartingVolume
	{
		get => _startingVolume.Value;
		set => _startingVolume.Value = value;
	}

	/// <summary>
	/// Maximum position volume allowed by the martingale logic.
	/// </summary>
	public decimal MaxVolume
	{
		get => _maxVolume.Value;
		set => _maxVolume.Value = value;
	}

	/// <summary>
	/// Initial take-profit distance expressed in MetaTrader points.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initial stop-loss distance expressed in MetaTrader points.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Multiplier applied to the trade volume after a losing trade.
	/// </summary>
	public decimal VolumeMultiplier
	{
		get => _volumeMultiplier.Value;
		set => _volumeMultiplier.Value = value;
	}

	/// <summary>
	/// Multiplier applied to stop-loss and take-profit distances after a losing trade.
	/// </summary>
	public decimal TargetMultiplier
	{
		get => _targetMultiplier.Value;
		set => _targetMultiplier.Value = value;
	}

	/// <summary>
	/// Candle type used to read market data.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_sma = null;
		_previousClose = null;
		_previousMa = null;
		_currentClose = null;
		_currentMa = null;
		_pipSize = 0m;

		_currentVolume = 0m;
		_currentTakeProfitPoints = 0m;
		_currentStopLossPoints = 0m;
		_lastRealizedPnL = 0m;
		_previousPosition = 0m;
		_lastTradeResult = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_pipSize = CalculatePipSize();

		_currentVolume = NormalizeVolume(StartingVolume);
		_currentTakeProfitPoints = TakeProfitPoints;
		_currentStopLossPoints = StopLossPoints;
		_lastRealizedPnL = PnL;
		_previousPosition = Position;
		_lastTradeResult = 0m;

		_sma = new SMA { Length = MaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_sma, ProcessCandle)
			.Start();
	}

	/// <inheritdoc />
	protected override void OnPositionReceived(Position position)
	{
		base.OnPositionReceived(position);

		if (_previousPosition != 0m && Position == 0m)
		{
			var tradePnL = PnL - _lastRealizedPnL;
			_lastRealizedPnL = PnL;
			_lastTradeResult = tradePnL;
		}

		_previousPosition = Position;
	}

	private void ProcessCandle(ICandleMessage candle, decimal maValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_currentClose is null)
		{
			_currentClose = candle.ClosePrice;
			_currentMa = maValue;
			return;
		}

		if (_previousClose is null)
		{
			_previousClose = _currentClose;
			_previousMa = _currentMa;
			_currentClose = candle.ClosePrice;
			_currentMa = maValue;
			return;
		}

		if (_sma?.IsFormed != true)
		{
			_previousClose = _currentClose;
			_previousMa = _currentMa;
			_currentClose = candle.ClosePrice;
			_currentMa = maValue;
			return;
		}

		var previousClose = _previousClose.Value;
		var previousMa = _previousMa!.Value;
		var currentClose = _currentClose.Value;
		var currentMa = _currentMa!.Value;

		var crossedBelowPrice = previousMa < previousClose && currentMa > currentClose;
		var crossedAbovePrice = previousMa > previousClose && currentMa < currentClose;

		if (Position == 0m && (crossedBelowPrice || crossedAbovePrice))
		{
			ApplyMartingaleAdjustments();
		}

		if (Position == 0m)
		{
			var volume = NormalizeVolume(_currentVolume);

			if (volume <= 0m)
			{
				ShiftBuffers(candle, maValue);
				return;
			}

			if (crossedBelowPrice)
			{
				ApplyProtection();
				SellMarket(volume);
			}
			else if (crossedAbovePrice)
			{
				ApplyProtection();
				BuyMarket(volume);
			}
		}

		ShiftBuffers(candle, maValue);
	}

	private void ApplyMartingaleAdjustments()
	{
		if (_lastTradeResult < 0m)
		{
			var nextVolume = Math.Min(_currentVolume * VolumeMultiplier, MaxVolume);
			_currentVolume = NormalizeVolume(nextVolume);

			_currentTakeProfitPoints = Math.Min(_currentTakeProfitPoints * TargetMultiplier, 100000m);
			_currentStopLossPoints = Math.Min(_currentStopLossPoints * TargetMultiplier, 100000m);
		}
		else if (_lastTradeResult > 0m)
		{
			_currentVolume = NormalizeVolume(StartingVolume);
			_currentTakeProfitPoints = TakeProfitPoints;
			_currentStopLossPoints = StopLossPoints;
		}

		_lastTradeResult = 0m;
	}

	private void ApplyProtection()
	{
		var stopDistance = _currentStopLossPoints > 0m ? _currentStopLossPoints * _pipSize : 0m;
		var takeDistance = _currentTakeProfitPoints > 0m ? _currentTakeProfitPoints * _pipSize : 0m;

		StartProtection(
			stopLoss: stopDistance > 0m ? new Unit(stopDistance, UnitTypes.Absolute) : null,
			takeProfit: takeDistance > 0m ? new Unit(takeDistance, UnitTypes.Absolute) : null);

		Volume = NormalizeVolume(_currentVolume);
	}

	private void ShiftBuffers(ICandleMessage candle, decimal maValue)
	{
		_previousClose = _currentClose;
		_previousMa = _currentMa;
		_currentClose = candle.ClosePrice;
		_currentMa = maValue;
	}

	private decimal NormalizeVolume(decimal volume)
	{
		var step = Security?.VolumeStep ?? 1m;
		if (step <= 0m)
			step = 1m;

		var minVolume = Security?.MinVolume ?? step;
		if (volume < minVolume)
			volume = minVolume;

		var multiplier = volume / step;
		var rounded = Math.Round(multiplier, MidpointRounding.AwayFromZero) * step;

		if (rounded < minVolume)
			rounded = minVolume;

		var maxVolume = Security?.MaxVolume;
		if (maxVolume is decimal max && rounded > max)
			rounded = max;

		rounded = Math.Min(rounded, MaxVolume);

		return Math.Max(rounded, step);
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			step = 1m;

		var decimals = Security?.Decimals ?? 0;
		if (decimals == 3 || decimals == 5)
			step *= 10m;

		return step;
	}
}