Auf GitHub ansehen

Moving Average Handelssystem-Strategie (2518)

Überblick

Diese Strategie ist ein StockSharp-Port des MetaTrader "Moving Average Trade System" Expertenberaters. Sie analysiert den Trend mithilfe von vier einfachen gleitenden Durchschnitten (SMA), die auf dem Mediankerzpreis berechnet werden. Das System wartet auf einen bestätigten Kreuzungspunkt zwischen den mittel- und langfristigen Durchschnitten, während die schnelleren Durchschnitte die Trendalignment bestätigen. Sobald die Bestätigung eintrifft, dreht die Strategie ihre Position in Richtung des neuen Trends und verwaltet das Risiko mit festen Take-Profit-, Stop-Loss- und Trailing-Stop-Offsets, die in Preisschritten definiert werden.

Handelslogik

  1. Indikatoren

    • SMA(5) (schnell) auf Medianpreis.
    • SMA(20) (mittel) auf Medianpreis.
    • SMA(40) (Signal) auf Medianpreis.
    • SMA(60) (langsam) auf Medianpreis.
  2. Long-Einstieg

    • SMA(5) > SMA(20) > SMA(40).
    • SMA(40) liegt mindestens SlopeThresholdSteps Preisschritte über SMA(60).
    • SMA(40) kreuzte auf dem aktuellen Bar über SMA(60) (vorheriges SMA(40) war unter oder gleich dem langsamen SMA).
    • Wenn eine Short-Position offen ist, kauft die Strategie genug Volumen, um sie zu schließen und die gewünschte Long-Größe aufzubauen.
  3. Short-Einstieg

    • SMA(5) < SMA(20) < SMA(40).
    • SMA(40) liegt mindestens SlopeThresholdSteps Preisschritte unter SMA(60).
    • SMA(40) kreuzte auf dem aktuellen Bar unter SMA(60) (vorheriges SMA(40) war über oder gleich dem langsamen SMA).
    • Wenn eine Long-Position offen ist, verkauft die Strategie genug Volumen, um sie zu schließen und die gewünschte Short-Größe aufzubauen.
  4. Risikomanagement (nur ausgewertet, wenn kein neuer Einstieg auf dem Bar ausgelöst wird):

    • Trend-Ausstieg: Longs schließen, wenn SMA(40) <= SMA(60) und Shorts schließen, wenn SMA(40) >= SMA(60).
    • Take-Profit: Aussteigen, sobald der Preis die konfigurierte Take-Profit-Distanz vom Einstiegspreis erreicht.
    • Stop-Loss: Aussteigen, wenn sich der Preis um die konfigurierte Stop-Loss-Distanz gegen die Position bewegt.
    • Trailing Stop: Sobald der Preis über den Einstieg hinausgeht, wird der Schutz-Stop um TrailingStopSteps Preisschritte nachgezogen, wobei das höchste Hoch (für Longs) oder das niedrigste Tief (für Shorts) seit dem Einstieg verwendet wird.

Alle Stop- und Gewinn-Offsets werden in Preisschritten (dem PriceStep des Instruments) gemessen. Wenn das Wertpapier keinen Preisschritt meldet, wird 1 als Fallback verwendet.

Parameter

Name Beschreibung Standardwert Optimierbar
Volume Ordervolumen beim Eröffnen neuer Positionen. 1 Nein
TakeProfitSteps Distanz zum Take-Profit-Ziel in Preisschritten. 50 Ja
StopLossSteps Distanz zum Schutzstop in Preisschritten. 50 Ja
TrailingStopSteps Trailing-Stop-Offset in Preisschritten (0 deaktiviert das Trailing). 11 Ja
SlopeThresholdSteps Mindestabstand zwischen SMA(40) und SMA(60) zur Validierung eines Ausbruchs (in Preisschritten). 1 Ja
FastPeriod Länge des schnellen SMA. 5 Ja
MediumPeriod Länge des mittleren SMA. 20 Ja
SignalPeriod Länge des Signal-SMA (verglichen mit dem langsamen SMA). 40 Ja
SlowPeriod Länge des langsamen SMA, der den Hintergrundtrend definiert. 60 Ja
CandleType Kerzenserie für Indikatorberechnungen. 1h Zeitrahmen Nein

Implementierungshinweise

  • Indikatoren sind über die High-Level-Bind-API an das Kerzen-Abonnement gebunden, was sicherstellt, dass Berechnungen ereignisgesteuert sind und nicht auf manuellem Buffer-Zugriff basieren.
  • Der Medianpreis wird für alle SMA-Berechnungen verwendet, um das Verhalten des originalen MetaTrader-EAs zu replizieren.
  • Das Positionsmanagement speichert den tatsächlichen Füllpreis mit OnNewMyTrade, um Stop-Loss-, Take-Profit- und Trailing-Stop-Niveaus nach jeder Füllung neu zu berechnen.
  • Beim Umkehren einer Position sendet die Strategie eine einzelne Marktorder, die sowohl die bestehende Exposure schließt als auch die neue eröffnet, was das Hedging-fähige Verhalten des ursprünglichen Algorithmus widerspiegelt.
  • Alle Kommentare in der C#-Quelldatei sind auf Englisch, gemäß den Repository-Richtlinien.

Verwendungstipps

  • Konfigurieren Sie den Volume-Parameter entsprechend der Lot-Größe des Instruments oder des Kontraktmultiplikators.
  • Passen Sie Stop- und Gewinnabstände an die Volatilität des Instruments an (die Standardwerte entsprechen den MetaTrader-Einstellungen von 50 Pips Stop/Take-Profit und 11 Pips Trailing Stop bei FX-Paaren).
  • Der Parameter SlopeThresholdSteps kann auf 0 gesetzt werden, um den zusätzlichen Abstandsfilter zu entfernen und auf jeden SMA(40)/SMA(60)-Kreuzungspunkt zu reagieren.
  • Stellen Sie für Backtesting oder Live-Trading sicher, dass das Wertpapier einen gültigen PriceStep bereitstellt; andernfalls behandelt die Strategie eine Preiseinheit als einen einzigen Schritt.
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Moving Average Trade System originally written for MetaTrader.
/// Uses four simple moving averages on median price to detect medium-term trend reversals.
/// </summary>
public class MovingAverageTradeSystemStrategy : Strategy
{
	private readonly StrategyParam<decimal> _takeProfitSteps;
	private readonly StrategyParam<decimal> _stopLossSteps;
	private readonly StrategyParam<decimal> _trailingStopSteps;
	private readonly StrategyParam<decimal> _slopeThresholdSteps;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _mediumPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _smaFast;
	private SimpleMovingAverage _smaMedium;
	private SimpleMovingAverage _smaSignal;
	private SimpleMovingAverage _smaSlow;

	private decimal? _previousSignal;
	private decimal? _previousSlow;

	private decimal? _longEntryPrice;
	private decimal? _longTakeProfit;
	private decimal? _longStopLoss;
	private decimal _longHigh;

	private decimal? _shortEntryPrice;
	private decimal? _shortTakeProfit;
	private decimal? _shortStopLoss;
	private decimal _shortLow;


	/// <summary>
	/// Desired take profit distance in price steps.
	/// </summary>
	public decimal TakeProfitSteps
	{
		get => _takeProfitSteps.Value;
		set => _takeProfitSteps.Value = value;
	}

	/// <summary>
	/// Desired stop loss distance in price steps.
	/// </summary>
	public decimal StopLossSteps
	{
		get => _stopLossSteps.Value;
		set => _stopLossSteps.Value = value;
	}

	/// <summary>
	/// Trailing stop offset in price steps.
	/// </summary>
	public decimal TrailingStopSteps
	{
		get => _trailingStopSteps.Value;
		set => _trailingStopSteps.Value = value;
	}

	/// <summary>
	/// Minimum separation between the signal SMA and the slow SMA (in price steps) to validate breakouts.
	/// </summary>
	public decimal SlopeThresholdSteps
	{
		get => _slopeThresholdSteps.Value;
		set => _slopeThresholdSteps.Value = value;
	}

	/// <summary>
	/// Fast SMA period (originally 5).
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Medium SMA period (originally 20).
	/// </summary>
	public int MediumPeriod
	{
		get => _mediumPeriod.Value;
		set => _mediumPeriod.Value = value;
	}

	/// <summary>
	/// Signal SMA period that must cross the slow SMA upward or downward (originally 40).
	/// </summary>
	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

	/// <summary>
	/// Slow SMA period defining the background trend (originally 60).
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the <see cref="MovingAverageTradeSystemStrategy"/> class.
	/// </summary>
	public MovingAverageTradeSystemStrategy()
	{

		_takeProfitSteps = Param(nameof(TakeProfitSteps), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (steps)", "Distance to take profit in price steps", "Risk Management")
			
			.SetOptimize(10m, 200m, 10m);

		_stopLossSteps = Param(nameof(StopLossSteps), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (steps)", "Distance to stop loss in price steps", "Risk Management")
			
			.SetOptimize(10m, 200m, 10m);

		_trailingStopSteps = Param(nameof(TrailingStopSteps), 11m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop (steps)", "Trailing stop offset in price steps", "Risk Management")
			
			.SetOptimize(0m, 100m, 5m);

		_slopeThresholdSteps = Param(nameof(SlopeThresholdSteps), 10m)
			.SetNotNegative()
			.SetDisplay("Slope Threshold", "Minimum SMA40 vs SMA60 distance in steps", "Signals")
			
			.SetOptimize(0m, 10m, 1m);

		_fastPeriod = Param(nameof(FastPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast SMA", "Fast SMA length", "Signals")
			
			.SetOptimize(3, 20, 1);

		_mediumPeriod = Param(nameof(MediumPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Medium SMA", "Medium SMA length", "Signals")
			
			.SetOptimize(10, 60, 1);

		_signalPeriod = Param(nameof(SignalPeriod), 40)
			.SetGreaterThanZero()
			.SetDisplay("Signal SMA", "Crossing SMA length", "Signals")
			
			.SetOptimize(20, 80, 1);

		_slowPeriod = Param(nameof(SlowPeriod), 60)
			.SetGreaterThanZero()
			.SetDisplay("Slow SMA", "Slow SMA length", "Signals")
			
			.SetOptimize(30, 120, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle type for calculations", "Data");
	}

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

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

		_previousSignal = null;
		_previousSlow = null;

		ResetLongState();
		ResetShortState();
	}

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

		// Create the moving averages on median price to match the original indicator setup.
		_smaFast = new SMA { Length = FastPeriod };
		_smaMedium = new SMA { Length = MediumPeriod };
		_smaSignal = new SMA { Length = SignalPeriod };
		_smaSlow = new SMA { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(_smaFast, _smaMedium, _smaSignal, _smaSlow, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _smaFast);
			DrawIndicator(area, _smaMedium);
			DrawIndicator(area, _smaSignal);
			DrawIndicator(area, _smaSlow);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal smaFast, decimal smaMedium, decimal smaSignal, decimal smaSlow)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_smaFast.IsFormed || !_smaMedium.IsFormed || !_smaSignal.IsFormed || !_smaSlow.IsFormed)
		{
			_previousSignal = smaSignal;
			_previousSlow = smaSlow;
			return;
		}

		var previousSignal = _previousSignal;
		var previousSlow = _previousSlow;
		_previousSignal = smaSignal;
		_previousSlow = smaSlow;

		if (previousSignal is null || previousSlow is null)
			return;

		var priceStep = GetPriceStep();
		var slopeThreshold = SlopeThresholdSteps * priceStep;

		var bullishStructure = smaFast > smaMedium && smaMedium > smaSlow;
		var bearishStructure = smaFast < smaMedium && smaMedium < smaSlow;
		var bullishSlope = (smaSignal - smaSlow) >= slopeThreshold;
		var bearishSlope = (smaSlow - smaSignal) >= slopeThreshold;
		var bullishCross = previousSignal.Value <= previousSlow.Value && smaSignal > smaSlow;
		var bearishCross = previousSignal.Value >= previousSlow.Value && smaSignal < smaSlow;

		var buySignal = bullishStructure && bullishSlope && bullishCross;
		var sellSignal = bearishStructure && bearishSlope && bearishCross;

		if (buySignal && Position <= 0m)
		{
			// Flip the position by buying enough volume to cover shorts and add the desired long exposure.
			var volume = Volume + (Position < 0m ? Math.Abs(Position) : 0m);

			if (volume > 0m)
				BuyMarket();
		}
		else if (sellSignal && Position >= 0m)
		{
			// Flip the position by selling enough volume to cover longs and add the desired short exposure.
			var volume = Volume + (Position > 0m ? Position : 0m);

			if (volume > 0m)
				SellMarket();
		}
		else
		{
			// Manage open positions when no new entry is triggered this bar.
			if (Position > 0m)
			{
				if (smaSignal <= smaSlow)
				{
					SellMarket();
				}
				else
				{
					ManageLongPosition(candle, priceStep);
				}
			}
			else if (Position < 0m)
			{
				if (smaSignal >= smaSlow)
				{
					BuyMarket();
				}
				else
				{
					ManageShortPosition(candle, priceStep);
				}
			}
		}
	}

	private void ManageLongPosition(ICandleMessage candle, decimal priceStep)
	{
		if (!_longEntryPrice.HasValue)
			return;

		_longHigh = Math.Max(_longHigh, candle.HighPrice);

		if (_longTakeProfit.HasValue && candle.HighPrice >= _longTakeProfit.Value)
		{
			SellMarket();
			return;
		}

		if (_longStopLoss.HasValue && candle.LowPrice <= _longStopLoss.Value)
		{
			SellMarket();
			return;
		}

		if (TrailingStopSteps <= 0m)
			return;

		var trailingLevel = _longHigh - TrailingStopSteps * priceStep;
		if (candle.ClosePrice <= trailingLevel)
			SellMarket();
	}

	private void ManageShortPosition(ICandleMessage candle, decimal priceStep)
	{
		if (!_shortEntryPrice.HasValue)
			return;

		_shortLow = Math.Min(_shortLow, candle.LowPrice);

		if (_shortTakeProfit.HasValue && candle.LowPrice <= _shortTakeProfit.Value)
		{
			BuyMarket();
			return;
		}

		if (_shortStopLoss.HasValue && candle.HighPrice >= _shortStopLoss.Value)
		{
			BuyMarket();
			return;
		}

		if (TrailingStopSteps <= 0m)
			return;

		var trailingLevel = _shortLow + TrailingStopSteps * priceStep;
		if (candle.ClosePrice >= trailingLevel)
			BuyMarket();
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		var price = trade.Trade?.Price;
		if (price is null)
			return;

		if (Position > 0m)
		{
			// After switching to long reset the short tracking and configure profit/stop levels.
			ResetShortState();
			SetupLongState(price.Value);
		}
		else if (Position < 0m)
		{
			// After switching to short reset the long tracking and configure profit/stop levels.
			ResetLongState();
			SetupShortState(price.Value);
		}
		else
		{
			// Flat position clears both trackers.
			ResetLongState();
			ResetShortState();
		}
	}

	private void SetupLongState(decimal entryPrice)
	{
		var priceStep = GetPriceStep();

		_longEntryPrice = entryPrice;
		_longHigh = entryPrice;
		_longTakeProfit = TakeProfitSteps > 0m ? entryPrice + TakeProfitSteps * priceStep : null;
		_longStopLoss = StopLossSteps > 0m ? entryPrice - StopLossSteps * priceStep : null;
	}

	private void SetupShortState(decimal entryPrice)
	{
		var priceStep = GetPriceStep();

		_shortEntryPrice = entryPrice;
		_shortLow = entryPrice;
		_shortTakeProfit = TakeProfitSteps > 0m ? entryPrice - TakeProfitSteps * priceStep : null;
		_shortStopLoss = StopLossSteps > 0m ? entryPrice + StopLossSteps * priceStep : null;
	}

	private void ResetLongState()
	{
		_longEntryPrice = null;
		_longTakeProfit = null;
		_longStopLoss = null;
		_longHigh = 0m;
	}

	private void ResetShortState()
	{
		_shortEntryPrice = null;
		_shortTakeProfit = null;
		_shortStopLoss = null;
		_shortLow = 0m;
	}

	private decimal GetPriceStep()
	{
		var step = Security?.PriceStep;
		return step is null || step == 0m ? 1m : step.Value;
	}
}