Auf GitHub ansehen

Ma2Cci EMA-Strategie

Strategie mit doppeltem Kreuzungsystem zweier exponentieller gleitender Durchschnitte, bestätigt durch einen Zero-Line-Break des Commodity Channel Index (CCI). Positionsgröße und Stop-Platzierung werden aus der Average-True-Range (ATR)-Volatilität und einem konfigurierbaren Risikoprozentsatz abgeleitet.

Details

  • Daten: Zeitbasierte Kerzen (Standard 1 Stunde) vom gewählten Candle Type-Parameter.
  • Einstieg: Long gehen, wenn die schnelle EMA die langsame EMA nach oben kreuzt und der CCI auf derselben Kerze die Nulllinie nach oben durchbricht; Short bei der entgegengesetzten Kreuzung mit CCI-Bruch unter null.
  • Ausstieg: Longs schließen, wenn die schnelle EMA wieder unter die langsame EMA kreuzt oder der Preis den festen Stop berührt; Shorts schließen, wenn die schnelle EMA über die langsame EMA kreuzt oder der Preis den Short-Stop erreicht.
  • Risiko: Die Stop-Distanz entspricht dem Größeren aus ATR (Länge AtrPeriod) oder MinStopPoints multipliziert mit dem Kursschritt des Instruments. Die Handelsgröße ist der Portfoliowert mal RiskPercent, geteilt durch diese Stop-Distanz.
  • Instrumente: Trendfolgende Forex- oder Index-Symbole, die Hedging in der ursprünglichen MetaTrader-Version unterstützen; auch für andere Assets mit klaren Momentum-Schwingungen geeignet.
  • Umgebung: Entwickelt für Märkte mit Dauersitzungen, bei denen EMA/CCI-Signale mit ATR-basierten Risikokontrollen übereinstimmen.

Parameter

  • CandleType – Zeitrahmen und Datentyp für Berechnungen und Orderfluss.
  • FastMaPeriod – Periode der schnellen EMA (Standard 10).
  • SlowMaPeriod – Periode der langsamen EMA (Standard 37).
  • CciPeriod – Lookback des CCI-Oszillators zur Momentum-Bestätigung (Standard 39).
  • AtrPeriod – ATR-Länge zur Schätzung der aktuellen Volatilität für die Stop-Platzierung (Standard 3).
  • RiskPercent – Anteil des aktuellen Portfolio-Eigenkapitals, der pro Trade riskiert wird (Standard 2%).
  • MinStopPoints – Minimaler Stop-Abstand in Preisschritten zur Emulation des MetaTrader-Pip-Filters (Standard 15).

Hinweise

  • Funktioniert am besten bei liquiden Paaren und Indizes, bei denen EMA/CCI-Kreuzungen zuverlässig sind; dünne Märkte können vorzeitige Ausstiege auslösen.
  • Da Stops nur beim Einstieg neu berechnet werden, hält die Strategie das Risikoprofil stabil und spiegelt die feste Stop-Loss-Logik des ursprünglichen MQL-Experten wider.
  • Die Portfolio-Bewertung muss vom verbundenen Konto bereitgestellt werden, damit die Positionsgrößenbestimmung funktioniert; andernfalls fällt die Engine auf das Volume der Strategie oder das Mindestvolumen des Instruments zurück.
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>
/// EMA crossover with CCI confirmation and ATR based stop distance.
/// </summary>
public class Ma2CciEmaStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<int> _minStopPoints;

	private ExponentialMovingAverage _fastMa = null!;
	private ExponentialMovingAverage _slowMa = null!;
	private CommodityChannelIndex _cci = null!;
	private AverageTrueRange _atr = null!;

	private decimal _previousFast;
	private decimal _previousSlow;
	private decimal _previousCci;
	private bool _hasPreviousValues;
	private decimal? _stopPrice;

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

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastMaPeriod { get => _fastMaPeriod.Value; set => _fastMaPeriod.Value = value; }

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowMaPeriod { get => _slowMaPeriod.Value; set => _slowMaPeriod.Value = value; }

	/// <summary>
	/// CCI calculation period.
	/// </summary>
	public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }

	/// <summary>
	/// ATR period for volatility based stops.
	/// </summary>
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }

	/// <summary>
	/// Percentage of portfolio equity risked per trade.
	/// </summary>
	public decimal RiskPercent { get => _riskPercent.Value; set => _riskPercent.Value = value; }

	/// <summary>
	/// Minimum stop distance expressed in price steps.
	/// </summary>
	public int MinStopPoints { get => _minStopPoints.Value; set => _minStopPoints.Value = value; }

	/// <summary>
	/// Initializes a new instance of the <see cref="Ma2CciEmaStrategy"/> class.
	/// </summary>
	public Ma2CciEmaStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles used for calculations", "General");

		_fastMaPeriod = Param(nameof(FastMaPeriod), 10)
		.SetGreaterThanZero()
		.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 37)
		.SetGreaterThanZero()
		.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");

		_cciPeriod = Param(nameof(CciPeriod), 39)
		.SetGreaterThanZero()
		.SetDisplay("CCI Period", "CCI length", "Indicators");

		_atrPeriod = Param(nameof(AtrPeriod), 3)
		.SetGreaterThanZero()
		.SetDisplay("ATR Period", "ATR length for stop calculation", "Risk Management");

		_riskPercent = Param(nameof(RiskPercent), 2m)
		.SetDisplay("Risk %", "Portfolio percentage risked per entry", "Risk Management");

		_minStopPoints = Param(nameof(MinStopPoints), 15)
		.SetGreaterThanZero()
		.SetDisplay("Min Stop Points", "Minimum stop distance in price steps", "Risk Management");
	}

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

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

		_previousFast = 0m;
		_previousSlow = 0m;
		_previousCci = 0m;
		_hasPreviousValues = false;
		_stopPrice = null;
	}

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

		_fastMa = new EMA { Length = FastMaPeriod };
		_slowMa = new EMA { Length = SlowMaPeriod };
		_cci = new CommodityChannelIndex { Length = CciPeriod };
		_atr = new AverageTrueRange { Length = AtrPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_fastMa, _slowMa, _cci, _atr, ProcessCandle)
		.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal cciValue, decimal atrValue)
	{
		// Process only finished candles to avoid intrabar noise.
		if (candle.State != CandleStates.Finished)
		return;

		// Wait until all indicators are fully initialized before trading.
		if (!_fastMa.IsFormed || !_slowMa.IsFormed || !_cci.IsFormed || !_atr.IsFormed)
		return;

		// removed IFOAAT for backtesting

		if (!_hasPreviousValues)
		{
			_previousFast = fastValue;
			_previousSlow = slowValue;
			_previousCci = cciValue;
			_hasPreviousValues = true;
			return;
		}

		var fastCrossUp = _previousFast <= _previousSlow && fastValue > slowValue;
		var fastCrossDown = _previousFast >= _previousSlow && fastValue < slowValue;
		var cciCrossUp = _previousCci <= 0m && cciValue > 0m;
		var cciCrossDown = _previousCci >= 0m && cciValue < 0m;

		var stopDistance = Math.Max(atrValue, GetMinStopDistance());

		if (Position != 0m)
		{
			var exitTriggered = false;

			if (Position > 0m)
			{
				// Close long positions on stop hit or bearish crossover.
				if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
				{
					SellMarket();
					exitTriggered = true;
				}
				else if (fastCrossDown)
				{
					SellMarket();
					exitTriggered = true;
				}
			}
			else if (Position < 0m)
			{
				// Close short positions on stop hit or bullish crossover.
				if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
				{
					BuyMarket();
					exitTriggered = true;
				}
				else if (fastCrossUp)
				{
					BuyMarket();
					exitTriggered = true;
				}
			}

			if (exitTriggered)
			{
				_stopPrice = null;
				_previousFast = fastValue;
				_previousSlow = slowValue;
				_previousCci = cciValue;
				return;
			}
		}
		else
		{
			// Enter long when EMA and CCI confirm bullish momentum.
			if (fastCrossUp && cciCrossUp)
			{
				var volume = CalculateVolume(stopDistance);
				if (volume > 0m)
				{
					BuyMarket();
					_stopPrice = NormalizePrice(candle.ClosePrice - stopDistance);
				}
			}
			// Enter short when EMA and CCI confirm bearish momentum.
			else if (fastCrossDown && cciCrossDown)
			{
				var volume = CalculateVolume(stopDistance);
				if (volume > 0m)
				{
					SellMarket();
					_stopPrice = NormalizePrice(candle.ClosePrice + stopDistance);
				}
			}
		}

		_previousFast = fastValue;
		_previousSlow = slowValue;
		_previousCci = cciValue;
	}

	private decimal CalculateVolume(decimal stopDistance)
	{
		if (stopDistance <= 0m)
		return 0m;

		var equity = Portfolio?.CurrentValue ?? 0m;
		var riskAmount = equity * (RiskPercent / 100m);

		if (riskAmount <= 0m)
		return NormalizeVolume(GetBaseVolume());

		var rawVolume = riskAmount / stopDistance;
		if (rawVolume <= 0m)
		return NormalizeVolume(GetBaseVolume());

		return NormalizeVolume(rawVolume);
	}

	private decimal GetBaseVolume()
	{
		var volume = Volume;
		if (volume > 0m)
		return volume;

		var step = Security?.VolumeStep ?? 1m;
		var min = Security?.MinVolume ?? step;
		return min > 0m ? min : step;
	}

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

		var normalized = Math.Round(volume / step, MidpointRounding.AwayFromZero) * step;
		var min = Security?.MinVolume ?? step;
		if (normalized < min)
		normalized = min;

		var max = Security?.MaxVolume;
		if (max.HasValue && max.Value > 0m && normalized > max.Value)
		normalized = max.Value;

		return Math.Max(normalized, 0m);
	}

	private decimal NormalizePrice(decimal price)
	{
		var step = Security?.PriceStep;
		if (!step.HasValue || step.Value <= 0m)
		return price;

		var rounded = Math.Round(price / step.Value, MidpointRounding.AwayFromZero) * step.Value;
		return rounded;
	}

	private decimal GetMinStopDistance()
	{
		var step = Security?.PriceStep ?? 0m;
		return step > 0m ? step * MinStopPoints : MinStopPoints;
	}
}