Auf GitHub ansehen

GreenTrade Strategie

Überblick

Die GreenTrade-Strategie ist eine Konvertierung des ursprünglichen MQL5-Experten. Sie folgt mittelfristigen Trends durch Kombination eines geglätteten gleitenden Durchschnitt (SMMA)-Steigungsfilters mit Momentum-Bestätigung vom Relative Strength Index (RSI). Signale werden auf abgeschlossenen Kerzen des konfigurierten Zeitrahmens berechnet, und die Strategie kann bis zu einer konfigurierbaren Anzahl von Positionseinheiten pyramidisieren, während sie feste Risikokontrollen und einen schrittweisen Trailing-Stop anwendet.

Handelslogik

  1. Indikatorvorbereitung
    • Die SMMA wird auf dem Median-Preis ((High + Low) / 2) mit dem MaPeriod-Parameter berechnet.
    • Der RSI wird auf dem Schlusskurs mit dem RsiPeriod-Rückblick berechnet.
  2. Trendformfilter
    • Vier historische SMMA-Proben werden gemäß den Bar-Shift-Parametern (ShiftBar, ShiftBar1, ShiftBar2, ShiftBar3) untersucht.
    • Ein bullischer Trend erfordert SMMA(shift0) > SMMA(shift1) > SMMA(shift2) > SMMA(shift3).
    • Ein bärischer Trend erfordert SMMA(shift0) < SMMA(shift1) < SMMA(shift2) < SMMA(shift3).
  3. Momentum-Bestätigung
    • Der RSI muss über RsiBuyLevel für Long-Einstiege und unter RsiSellLevel für Short-Einstiege liegen. Der RSI-Wert wird ShiftBar Bars zurück genommen, um die MQL5-Logik zu spiegeln, die die sich bildende Kerze ignoriert.
  4. Orderausführung
    • Wenn ein Signal bestätigt ist und das Positionslimit nicht überschritten wird, sendet die Strategie eine Marktorder für TradeVolume.
    • Wenn eine Position in der entgegengesetzten Richtung existiert, neutralisiert die Strategie sie zuerst und eröffnet dann eine neue Position mit dem konfigurierten Volumen.
    • Wenn eine Position in dieselbe Richtung existiert, wird das Handelsvolumen bis zu MaxPositions * TradeVolume zur Nettoexposition hinzugefügt.

Risikomanagement

  • Initialer Stop-Loss / Take-Profit: Jeder neue Einstieg setzt Preisziele basierend auf StopLossPips und TakeProfitPips. Pip-Abstände werden in Preiseinheiten über den PriceStep des Instruments umgerechnet. Instrumente mit fraktionalen Schritten (z. B. fünfstellige Forex-Symbole) erhalten einen zusätzlichen Faktor von 10, genau wie der ursprüngliche Experte.
  • Trailing-Stop: Wenn der Gewinn TrailingStopPips + TrailingStepPips überschreitet, wird der Stop bewegt, um einen Abstand von TrailingStopPips zu halten. Weitere Bewegungen erfordern eine weitere TrailingStepPips-Preisverbesserung, was das schrittweise Trailing-Verhalten aus dem MQL-Code reproduziert.
  • Positionslimit: Der MaxPositions-Parameter begrenzt die maximale Anzahl von Volumeneinheiten. Signale, die dieses Limit überschreiten würden, werden ignoriert.

Parameter

Parameter Beschreibung Standard
MaPeriod Länge des geglätteten gleitenden Durchschnitts angewendet auf den Median-Preis. 67
ShiftBar, ShiftBar1, ShiftBar2, ShiftBar3 Offsets (in Bars) zum Zugriff auf historische SMMA-Proben für den Trendformfilter. 1, 1, 2, 3
RsiPeriod Rückblickperiode für den RSI-Indikator. 57
RsiBuyLevel RSI-Schwellenwert, der bullische Setups bestätigt. 60
RsiSellLevel RSI-Schwellenwert, der bärische Setups bestätigt. 36
TradeVolume Volumen angewendet auf jeden Einstieg oder jede Ergänzung. 0.1
StopLossPips Abstand für den initialen Stop-Loss in Pips (0 deaktiviert ihn). 300
TakeProfitPips Abstand für den initialen Take-Profit in Pips (0 deaktiviert ihn). 300
TrailingStopPips Abstand zwischen Preis und Trailing-Stop nach Aktivierung (0 deaktiviert Trailing). 12
TrailingStepPips Zusätzlicher Fortschritt erforderlich, bevor der Trailing-Stop erneut bewegt wird. 5
MaxPositions Maximale Anzahl von Volumeneinheiten (TradeVolume-Vielfachen), die aktiv sein können. 7
CandleType Kerzendatenserie für Indikatoraktualisierungen. 1-Stunden-Zeitrahmen

Hinweise

  • Alle Berechnungen werden nur auf abgeschlossenen Kerzen durchgeführt; unfertige Kerzen werden ignoriert, um rauschige Signale zu vermeiden.
  • Der Positionsstatus wird intern verfolgt, damit Stop-Loss-, Take-Profit- und Trailing-Ausstiege gehandhabt werden, auch wenn keine Schutzorders an der Börse platziert sind.
  • Die Konvertierung behält das ursprüngliche Verhalten für Pip-Konvertierung und Trailing-Schritt-Logik bei, während die StockSharp-High-Level-API für Abonnements und Orderausführung genutzt wird.
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>
/// GreenTrade strategy converted from the MQL implementation.
/// Combines a smoothed moving average slope filter with RSI momentum confirmation.
/// </summary>
public class GreenTradeStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _shiftBar;
	private readonly StrategyParam<int> _shiftBar1;
	private readonly StrategyParam<int> _shiftBar2;
	private readonly StrategyParam<int> _shiftBar3;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiBuyLevel;
	private readonly StrategyParam<decimal> _rsiSellLevel;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<int> _maxPositions;
	private readonly StrategyParam<DataType> _candleType;

	private SmoothedMovingAverage _smma;
	private RelativeStrengthIndex _rsi;

	private readonly List<decimal> _maHistory = new();
	private readonly List<decimal> _rsiHistory = new();

	private decimal _pipSize = 1m;
	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;

	/// <summary>
	/// Period for the smoothed moving average.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Shift (in bars) used for the most recent MA/RSI sample.
	/// </summary>
	public int ShiftBar
	{
		get => _shiftBar.Value;
		set => _shiftBar.Value = value;
	}

	/// <summary>
	/// Additional shift between the first and second MA comparison.
	/// </summary>
	public int ShiftBar1
	{
		get => _shiftBar1.Value;
		set => _shiftBar1.Value = value;
	}

	/// <summary>
	/// Additional shift between the second and third MA comparison.
	/// </summary>
	public int ShiftBar2
	{
		get => _shiftBar2.Value;
		set => _shiftBar2.Value = value;
	}

	/// <summary>
	/// Additional shift between the third and fourth MA comparison.
	/// </summary>
	public int ShiftBar3
	{
		get => _shiftBar3.Value;
		set => _shiftBar3.Value = value;
	}

	/// <summary>
	/// RSI lookback period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// RSI threshold to confirm bullish entries.
	/// </summary>
	public decimal RsiBuyLevel
	{
		get => _rsiBuyLevel.Value;
		set => _rsiBuyLevel.Value = value;
	}

	/// <summary>
	/// RSI threshold to confirm bearish entries.
	/// </summary>
	public decimal RsiSellLevel
	{
		get => _rsiSellLevel.Value;
		set => _rsiSellLevel.Value = value;
	}

	/// <summary>
	/// Trade volume for each new position add-on.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Minimum price improvement (in pips) before trailing stop is moved again.
	/// </summary>
	public decimal TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Maximum number of position units (in TradeVolume steps) allowed.
	/// </summary>
	public int MaxPositions
	{
		get => _maxPositions.Value;
		set => _maxPositions.Value = value;
	}

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

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public GreenTradeStrategy()
	{
		_maPeriod = Param(nameof(MaPeriod), 67)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Length of the smoothed moving average", "Indicators");

		_shiftBar = Param(nameof(ShiftBar), 1)
			.SetGreaterThanZero()
			.SetDisplay("Shift #0", "Index of the most recent evaluated bar", "Signals");

		_shiftBar1 = Param(nameof(ShiftBar1), 1)
			.SetGreaterThanZero()
			.SetDisplay("Shift #1", "Offset from bar #0 to bar #1", "Signals");

		_shiftBar2 = Param(nameof(ShiftBar2), 2)
			.SetGreaterThanZero()
			.SetDisplay("Shift #2", "Offset from bar #1 to bar #2", "Signals");

		_shiftBar3 = Param(nameof(ShiftBar3), 3)
			.SetGreaterThanZero()
			.SetDisplay("Shift #3", "Offset from bar #2 to bar #3", "Signals");

		_rsiPeriod = Param(nameof(RsiPeriod), 57)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "Length of the RSI indicator", "Indicators");

		_rsiBuyLevel = Param(nameof(RsiBuyLevel), 60m)
			.SetDisplay("RSI Buy Level", "RSI threshold for bullish entries", "Signals");

		_rsiSellLevel = Param(nameof(RsiSellLevel), 36m)
			.SetDisplay("RSI Sell Level", "RSI threshold for bearish entries", "Signals");

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Volume used for each new order", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 300m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Initial stop-loss distance in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 300m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Initial take-profit distance in pips", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 12m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop", "Trailing stop distance in pips", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
			.SetNotNegative()
			.SetDisplay("Trailing Step", "Required progress before trailing adjusts", "Risk");

		_maxPositions = Param(nameof(MaxPositions), 7)
			.SetGreaterThanZero()
			.SetDisplay("Max Positions", "Maximum number of volume units allowed", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle subscription", "Data");
	}

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

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

		_smma = null;
		_rsi = null;
		_maHistory.Clear();
		_rsiHistory.Clear();
		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
	}

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

		_smma = new SmoothedMovingAverage { Length = MaPeriod };
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		_pipSize = CalculatePipSize();

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

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

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

		if (_smma == null || _rsi == null)
			return;

		var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;
		var maResult = _smma.Process(new DecimalIndicatorValue(_smma, medianPrice, candle.OpenTime) { IsFinal = true });
		var rsiResult = _rsi.Process(new DecimalIndicatorValue(_rsi, candle.ClosePrice, candle.OpenTime) { IsFinal = true });

		if (!_smma.IsFormed || !_rsi.IsFormed)
		{
			_maHistory.Add(0m);
			_rsiHistory.Add(0m);
			TrimHistory();
			return;
		}

		var maValue = maResult.ToDecimal();
		var rsiValue = rsiResult.ToDecimal();

		_maHistory.Add(maValue);
		_rsiHistory.Add(rsiValue);
		TrimHistory();

		// Indicators checked above.

		var shift0 = ShiftBar;
		var shift1 = shift0 + ShiftBar1;
		var shift2 = shift1 + ShiftBar2;
		var shift3 = shift2 + ShiftBar3;

		var ma0 = GetHistoryValue(_maHistory, shift0);
		var ma1 = GetHistoryValue(_maHistory, shift1);
		var ma2 = GetHistoryValue(_maHistory, shift2);
		var ma3 = GetHistoryValue(_maHistory, shift3);
		var rsiSample = GetHistoryValue(_rsiHistory, ShiftBar);

		if (ma0 is null || ma1 is null || ma2 is null || ma3 is null || rsiSample is null)
			return;

		var buySignal = ma0 > ma1 && ma1 > ma2 && ma2 > ma3 && rsiSample > RsiBuyLevel;
		var sellSignal = ma0 < ma1 && ma1 < ma2 && ma2 < ma3 && rsiSample < RsiSellLevel;

		if (buySignal && CanIncreasePosition(true))
			OpenPosition(true, candle);
		else if (sellSignal && CanIncreasePosition(false))
			OpenPosition(false, candle);

		UpdateTrailing(candle);
		ManageExits(candle);
	}

	private void OpenPosition(bool isLong, ICandleMessage candle)
	{
		var additionalVolume = TradeVolume;
		var currentPosition = Position;

		if (isLong && currentPosition < 0)
			additionalVolume += Math.Abs(currentPosition);
		else if (!isLong && currentPosition > 0)
			additionalVolume += currentPosition;

		if (additionalVolume <= 0)
			return;

		if (isLong)
			BuyMarket();
		else
			SellMarket();

		if (isLong)
		{
			if (currentPosition > 0)
			{
				var total = currentPosition + TradeVolume;
				_entryPrice = total > 0 ? ((currentPosition * _entryPrice) + (TradeVolume * candle.ClosePrice)) / total : candle.ClosePrice;
			}
			else
			{
				_entryPrice = candle.ClosePrice;
			}
		}
		else
		{
			if (currentPosition < 0)
			{
				var total = Math.Abs(currentPosition) + TradeVolume;
				_entryPrice = total > 0 ? ((Math.Abs(currentPosition) * _entryPrice) + (TradeVolume * candle.ClosePrice)) / total : candle.ClosePrice;
			}
			else
			{
				_entryPrice = candle.ClosePrice;
			}
		}

		var stopDistance = StopLossPips * _pipSize;
		var takeDistance = TakeProfitPips * _pipSize;

		_stopPrice = stopDistance > 0 ? (isLong ? _entryPrice - stopDistance : _entryPrice + stopDistance) : null;
		_takePrice = takeDistance > 0 ? (isLong ? _entryPrice + takeDistance : _entryPrice - takeDistance) : null;
	}

	private void UpdateTrailing(ICandleMessage candle)
	{
		if (TrailingStopPips <= 0)
			return;

		var trailingDistance = TrailingStopPips * _pipSize;
		var stepDistance = TrailingStepPips * _pipSize;

		if (Position > 0 && _entryPrice > 0)
		{
			var profit = candle.ClosePrice - _entryPrice;
			if (profit > trailingDistance + stepDistance)
			{
				var threshold = candle.ClosePrice - (trailingDistance + stepDistance);
				if (!_stopPrice.HasValue || _stopPrice.Value < threshold)
					_stopPrice = candle.ClosePrice - trailingDistance;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			var profit = _entryPrice - candle.ClosePrice;
			if (profit > trailingDistance + stepDistance)
			{
				var threshold = candle.ClosePrice + trailingDistance + stepDistance;
				if (!_stopPrice.HasValue || _stopPrice.Value > threshold)
					_stopPrice = candle.ClosePrice + trailingDistance;
			}
		}
	}

	private void ManageExits(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				SellMarket();
				ResetPositionState();
				return;
			}

			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				SellMarket();
				ResetPositionState();
				return;
			}
		}
		else if (Position < 0)
		{
			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				BuyMarket();
				ResetPositionState();
				return;
			}

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

	private bool CanIncreasePosition(bool isLong)
	{
		if (TradeVolume <= 0)
			return false;

		if (MaxPositions <= 0)
			return true;

		var maxVolume = MaxPositions * TradeVolume;
		var absolutePosition = Math.Abs(Position);

		if (isLong && Position < 0)
			return true;

		if (!isLong && Position > 0)
			return true;

		var tolerance = Security?.VolumeStep ?? 0.0000001m;
		return absolutePosition + TradeVolume <= maxVolume + tolerance;
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 1m;
		if (step < 0.01m)
			step *= 10m;
		return step;
	}

	private static decimal? GetHistoryValue(List<decimal> values, int shift)
	{
		if (shift <= 0)
			return null;

		var index = values.Count - shift;
		if (index < 0)
			return null;

		return values[index];
	}

	private void TrimHistory()
	{
		var maxShift = ShiftBar + ShiftBar1 + ShiftBar2 + ShiftBar3;
		var maxCount = Math.Max(maxShift + 5, 10);

		if (_maHistory.Count > maxCount)
			_maHistory.RemoveRange(0, _maHistory.Count - maxCount);

		if (_rsiHistory.Count > maxCount)
			_rsiHistory.RemoveRange(0, _rsiHistory.Count - maxCount);
	}

	private void ResetPositionState()
	{
		if (Math.Abs(Position) < (Security?.VolumeStep ?? 0.0000001m))
		{
			_entryPrice = 0m;
			_stopPrice = null;
			_takePrice = null;
		}
	}
}