Auf GitHub ansehen

MA zur Momentum-Min-Profit-Strategie

Überblick

Diese Strategie repliziert den MetaTrader 5 Expert Advisor MA on Momentum Min Profit.mq5, indem sie den Crossover zwischen einem Momentum-Indikator und einem gleitenden Durchschnitt handelt, der auf der Grundlage der Momentum-Reihe berechnet wird. Ein zinsbullisches Signal erscheint, wenn das Momentum seinen Durchschnitt überschreitet, während der vorherige Balken das Momentum unter der neutralen 100-Marke hielt. Ein rückläufiges Signal wird erzeugt, wenn das Momentum unter den Durchschnitt fällt und der vorherige Balken über 100 liegt. Die Implementierung behält den ursprünglichen geldbasierten Aktienstopp und die feste Take-Profit-Distanz, gemessen in Punkten, bei.

Handelslogik

  1. Fordern Sie durch CandleType definierte Kerzen an und geben Sie sie in den Momentum-Indikator ein.
  2. Glätten Sie den Impulsstrom mit einem gleitenden Durchschnitt, der durch MomentumMovingAverageType und MomentumMovingAveragePeriod definiert ist.
  3. Erkennen Sie Überkreuzungen anhand der vorherigen Balkenwerte, um Doppelsignale zu vermeiden.
  4. Optionale Funktionen aus der MQL-Version:
    • Kehren Sie die Richtung der erzeugten Signale um.
    • Schließen Sie das Gegenrisiko, bevor Sie einen neuen Trade eingehen, oder überspringen Sie die Eingabe ganz.
    • Erzwingen Sie jederzeit eine einzelne Nettoposition.
    • Ermöglichen Sie das Auslösen auf die aktuelle (sich bildende) Kerze statt auf den vollständig geschlossenen Balken.
  5. Risikomanagement anwenden:
    • Eigenkapital-Stopp in Geld: PnL + Position * (close - PositionPrice) muss über StopLossMoney bleiben.
    • Take-Profit-Distanz in Punkten umgerechnet durch Security.PriceStep.

Parameter

Parameter Typ Standard Beschreibung
CandleType DataType TimeSpan.FromMinutes(5).TimeFrame() Kerzen zur Berechnung des Impulses.
MomentumPeriod int 14 Rückblickzeitraum des Momentum-Indikators.
MomentumMovingAveragePeriod int 6 Länge des auf den Impuls angewendeten gleitenden Durchschnitts.
MomentumMovingAverageType MomentumMovingAverageType Smoothed Algorithmus für gleitenden Durchschnitt (einfach, exponentiell, geglättet, gewichtet).
ReverseSignals bool false Spiegeln Sie MetaTrader Kauf-/Verkaufssignale.
CloseOpposite bool true Schließen Sie die gegenüberliegende Position, bevor Sie eine neue Position eröffnen.
OnlyOnePosition bool true Behalten Sie eine einzige Nettoposition bei.
UseCurrentCandle bool false Bewerten Sie Signale anhand der sich aktuell bildenden Kerze anstelle des geschlossenen Balkens.
StopLossMoney decimal 15 Kapitalabnahme vor Abschluss aller Geschäfte möglich.
TakeProfitPoints decimal 460 Gewinnziel in Instrumentenpunkten (multipliziert mit PriceStep).
MomentumReference decimal 100 Neutrales Momentumniveau, kopiert von der MQL-Strategie.

Hinweise zur Implementierung

  • Der gleitende Durchschnitt wird mit LengthIndicator<decimal> Instanzen implementiert, um die integrierten SMA/EMA/SMMA/WMA-Klassen von StockSharp wiederzuverwenden.
  • Die ursprüngliche Orderwarteschlange und die Magic-Number-Filter werden auf Nettopositionen von StockSharp abgebildet. Daher sendet die Strategie eine einzelne Marktorder in der Größe, um sowohl die Gegenseite abzuflachen als auch das neue Risiko zu eröffnen, wenn CloseOpposite aktiviert ist.
  • Der Aktienschutz schließt alle Positionen über CloseAll(), sobald der variable Verlust den Schwellenwert überschreitet, was genau dem MetaTrader-Verhalten der Überwachung der kombinierten Provision, des Swaps und des Gewinns entspricht.
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>
/// Momentum crossing its own moving average strategy.
/// Converted from MetaTrader 5 (MA on Momentum Min Profit.mq5).
/// </summary>
public class MaOnMomentumMinProfitStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<int> _maPeriod;

	private Momentum _momentum;
	private readonly Queue<decimal> _momentumHistory = new();
	private decimal? _prevMomentum;
	private decimal? _prevSignal;

	/// <summary>
	/// Candle type used for signal calculation.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Momentum period.
	/// </summary>
	public int MomentumPeriod
	{
		get => _momentumPeriod.Value;
		set => _momentumPeriod.Value = value;
	}

	/// <summary>
	/// Moving average period applied to momentum values.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="MaOnMomentumMinProfitStrategy"/>.
	/// </summary>
	public MaOnMomentumMinProfitStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used for the momentum calculation", "General");

		_momentumPeriod = Param(nameof(MomentumPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Period", "Lookback for the momentum indicator", "Momentum");

		_maPeriod = Param(nameof(MaPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Period of the moving average applied to momentum", "Momentum");
	}

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

		_prevMomentum = null;
		_prevSignal = null;
		_momentumHistory.Clear();

		_momentum = new Momentum { Length = MomentumPeriod };

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

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

		_momentumHistory.Enqueue(momentumValue);
		while (_momentumHistory.Count > MaPeriod)
			_momentumHistory.Dequeue();

		if (!_momentum.IsFormed)
			return;

		if (_momentumHistory.Count < MaPeriod)
		{
			_prevMomentum = momentumValue;
			return;
		}

		// Calculate SMA of momentum
		var sum = 0m;
		var history = _momentumHistory.ToArray();
		foreach (var v in history)
			sum += v;
		var signalValue = sum / history.Length;

		if (_prevMomentum is null || _prevSignal is null)
		{
			_prevMomentum = momentumValue;
			_prevSignal = signalValue;
			return;
		}

		var crossUp = _prevMomentum < _prevSignal && momentumValue > signalValue;
		var crossDown = _prevMomentum > _prevSignal && momentumValue < signalValue;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		var minSpread = 0.5m;

		if (crossUp && Math.Abs(momentumValue - signalValue) >= minSpread)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (crossDown && Math.Abs(momentumValue - signalValue) >= minSpread)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevMomentum = momentumValue;
		_prevSignal = signalValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_prevMomentum = null;
		_prevSignal = null;
		_momentum = null;
		_momentumHistory.Clear();

		base.OnReseted();
	}
}