Auf GitHub ansehen

CCI MA v1.5 Strategie

Diese Strategie erstellt den Expertenberater MetaTrader „CCI_MA v1.5“ innerhalb des StockSharp-High-Level-API neu. Der ursprüngliche Roboter wartet darauf, dass der Commodity Channel Index (CCI) einen einfachen gleitenden Durchschnitt überschreitet, der auf den CCI-Werten selbst berechnet wird, und verwendet einen sekundären CCI, um Exits um die ±100-Schwellenwerte herum zu überwachen. Der StockSharp-Port behält die gleiche Signalreihenfolge, das optionale Geldmanagement und die punktbasierten Stopp-/Zielentfernungen bei und passt alles an Kerzenabonnements und Indikatorbindungen an.

Wie es funktioniert

  • Datenquelle – Eine benutzerdefinierte Kerzenserie (standardmäßig 15-Minuten-Kerzen) speist beide CCIs. Die Indikatoren lesen den Schlusskurs der Kerze, um die PRICE_CLOSE-Einstellung von MetaTrader widerzuspiegeln.
  • Kernindikatoren – Der primäre CommodityChannelIndex (Parameter CciPeriod) liefert den Momentumwert. Ein SimpleMovingAverage mit dem Zeitraum MaPeriod wird auf den Strom von CCI-Werten angewendet, um die Triggerlinie zu bilden. Ein sekundärer CCI (SignalCciPeriod) überwacht überkaufte und überverkaufte Umkehrungen um ±100.
  • Einstiegslogik – Ein Long-Trade wird auf dem Balken nach einem Aufwärts-Crossover eröffnet: Die zuvor abgeschlossene Kerze (prevCci) muss über dem gleitenden Durchschnitt CCI liegen, während die Kerze davor (prev2Cci) darunter lag. Ein kurzes Signal ist die symmetrische Kreuzung nach unten. Bestehende gegensätzliche Positionen werden geschlossen und umgedreht, indem der Absolutwert der aktuellen Position zur neuen Ordergröße addiert wird, was dem Verhalten der MQL-Version entspricht.
  • Ausstiegslogik – Long-Positionen werden liquidiert, wenn der aufsichtsrechtliche CCI von über +100 auf unter +100 fällt oder wenn der primäre CCI wieder unter seinen gleitenden Durchschnitt fällt (wiederum anhand der beiden zuvor abgeschlossenen Kerzen bewertet). Shorts treten unter den umgekehrten Bedingungen auf. Schutzstopps emulieren die punktbasierten Abstände von MetaTrader: Die Strategie leitet eine Pip-Größe aus dem Instrument PriceStep ab (Multiplikation mit 10 für drei- oder fünfstellige Kurse) und vergleicht die Kerzenextreme mit entry ± distance bei jeder abgeschlossenen Kerze.
  • PositionsgrößeLotVolume definiert die Basis-Ordergröße. Wenn UseMoneyManagement aktiviert ist, multipliziert die Strategie es mit einem ganzzahligen Faktor gleich floor(balance / DepositPerLot), begrenzt durch MaxMultiplier, wodurch die Einzahlungsleiter des Fachberaters reproduziert wird. Das Bestellvolumen wird vor der Übermittlung an die Instrumentenbeschränkungen VolumeStep, MinVolume und MaxVolume angepasst.

Parameter

  • Kerzentyp – Kerzendatentyp, der allen Indikatorberechnungen zugrunde liegt.
  • CCI-Periode – Länge des primären CCI-Oszillators.
  • Exit-CCI-Zeitraum – Länge des Überwachungszeitraums CCI, der für Schwellenwert-Exits verwendet wird.
  • CCI MA-Periode – Periode des einfachen gleitenden Durchschnitts, der auf den primären CCI angewendet wird.
  • Lotvolumen – Basishandelsvolumen vor der Skalierung des Geldmanagements.
  • Geldverwaltung aktivieren – Aktiviert die einzahlungsbasierte Skalierung des Lotvolumens.
  • Einzahlung pro Lot – Erforderliche Guthabenerhöhung, um den Lot-Multiplikator um eins zu erhöhen (wird nur verwendet, wenn die Geldverwaltung aktiv ist).
  • Max. Multiplikator – Maximaler Multiplikator, den das Geldmanagement erreichen kann.
  • Stop-Loss (Pips) – Abstand in Pips für den Schutzstopp; Zum Deaktivieren auf Null setzen.
  • Take Profit (Pips) – Abstand in Pips für das Gewinnziel; Zum Deaktivieren auf Null setzen.

Die Strategie wartet auf zwei vollständig geschlossene Kerzen, bevor sie die erste Order erteilt, sodass die Zwei-Balken-Crossover-Vergleiche genau mit der verzögerten Ausführung des MQL-Experten übereinstimmen. Stop-Loss- und Take-Profit-Prüfungen werden für fertige Kerzen unter Verwendung ihrer Hoch-/Tief-Extremwerte ausgeführt, was den serverseitigen Schutzaufträgen von MetaTrader nahe kommt und gleichzeitig innerhalb des hohen Niveaus StockSharp API bleibt.

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;

/// <summary>
/// Commodity Channel Index strategy converted from the MetaTrader "CCI_MA v1.5" expert advisor.
/// Uses a primary CCI with a manually computed SMA of CCI values as a signal line.
/// A secondary CCI provides overbought/oversold exit confirmation.
/// </summary>
public class CciMaV15Strategy : Strategy
{
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<int> _signalCciPeriod;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private CommodityChannelIndex _cci;
	private CommodityChannelIndex _signalCci;

	private readonly List<decimal> _cciHistory = new();
	private decimal? _prevCciMa;
	private decimal? _prevCci;
	private decimal? _prevSignalCci;

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

	/// <summary>
	/// Secondary CCI period for exit signals.
	/// </summary>
	public int SignalCciPeriod
	{
		get => _signalCciPeriod.Value;
		set => _signalCciPeriod.Value = value;
	}

	/// <summary>
	/// SMA period applied to the primary CCI values.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Stop loss distance in absolute points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance in absolute points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

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

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public CciMaV15Strategy()
	{
		_cciPeriod = Param(nameof(CciPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "Length of the primary CCI", "CCI")
			.SetOptimize(7, 35, 7);

		_signalCciPeriod = Param(nameof(SignalCciPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Exit CCI Period", "Length of the secondary CCI", "CCI")
			.SetOptimize(7, 35, 7);

		_maPeriod = Param(nameof(MaPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("CCI MA Period", "SMA length applied to the CCI", "CCI")
			.SetOptimize(3, 21, 3);

		_stopLossPoints = Param(nameof(StopLossPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Protective stop distance in absolute points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Profit target distance in absolute points", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Market data series", "General");

		Volume = 1;
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_cci = null;
		_signalCci = null;
		_cciHistory.Clear();
		_prevCciMa = null;
		_prevCci = null;
		_prevSignalCci = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		_cci = new CommodityChannelIndex { Length = CciPeriod };
		_signalCci = new CommodityChannelIndex { Length = SignalCciPeriod };

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

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

			var indArea = CreateChartArea();
			if (indArea != null)
			{
				DrawIndicator(indArea, _cci);
				DrawIndicator(indArea, _signalCci);
			}
		}

		var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
		var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
		if (tp != null || sl != null)
			StartProtection(tp, sl);

		base.OnStarted2(time);
	}

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

		// Maintain CCI history for manual SMA calculation
		_cciHistory.Add(cciValue);
		if (_cciHistory.Count > MaPeriod)
			_cciHistory.RemoveAt(0);

		// Compute SMA of CCI
		decimal? cciMa = null;
		if (_cciHistory.Count >= MaPeriod)
		{
			decimal sum = 0;
			for (int i = 0; i < _cciHistory.Count; i++)
				sum += _cciHistory[i];
			cciMa = sum / _cciHistory.Count;
		}

		if (cciMa == null || _prevCci == null || _prevCciMa == null || _prevSignalCci == null)
		{
			_prevCci = cciValue;
			_prevCciMa = cciMa;
			_prevSignalCci = signalCciValue;
			return;
		}

		// Exit logic: secondary CCI overbought/oversold reversal
		if (Position > 0 && _prevSignalCci > 100 && signalCciValue <= 100)
		{
			SellMarket(Position);
		}
		else if (Position < 0 && _prevSignalCci < -100 && signalCciValue >= -100)
		{
			BuyMarket(Math.Abs(Position));
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevCci = cciValue;
			_prevCciMa = cciMa;
			_prevSignalCci = signalCciValue;
			return;
		}

		// Entry: CCI crosses above its MA (buy) or below (sell)
		if (_prevCci < _prevCciMa && cciValue > cciMa.Value && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
		}
		else if (_prevCci > _prevCciMa && cciValue < cciMa.Value && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Position);
			SellMarket(Volume);
		}

		_prevCci = cciValue;
		_prevCciMa = cciMa;
		_prevSignalCci = signalCciValue;
	}
}