Auf GitHub ansehen

CrossoverMA-Strategie

Überblick

Diese Strategie ist ein StockSharp-Port des MetaTrader 5-Expertenberaters CrossoverMA.mq5. Der ursprüngliche Roboter wartet darauf, dass eine Kerze einen gleitenden Durchschnitt kreuzt, und öffnet eine Position nur, wenn der Durchschnitt in die gleiche Richtung wie der Ausbruch geneigt ist. Die StockSharp-Version behält dasselbe Verhalten bei und nutzt die High-Level-API für Kerzenabonnements, Indikatorverwaltung und automatisches Chart-Rendering.

Handelslogik

  1. Abonnieren der konfigurierten Kerzenserie und Berechnung eines einfachen gleitenden Durchschnitts (SMA) über den Kerzenschlusskurs.
  2. Wenn eine abgeschlossene Kerze empfangen wird, messen:
    • Die Kerzeneröffnungs- und -schlussabstände vom SMA.
    • Die Steigung des SMA durch Vergleich des aktuellen Wertes mit dem vorherigen.
  3. Signale generieren:
    • Bullischer Ausbruch – die Kerze öffnet unter dem SMA, schließt über ihm, und der SMA steigt. Die Strategie schließt jede Short-Exposition und öffnet/erweitert eine Long-Position.
    • Bärischer Ausbruch – die Kerze öffnet über dem SMA, schließt unter ihm, und der SMA fällt. Die Strategie schließt jede Long-Exposition und öffnet/erweitert eine Short-Position.
  4. Doppelte Signale ignorieren, die die aktuelle Positionsseite nicht ändern.

Der Port behält die MetaTrader-Regel bei, dass nur abgeschlossene Kerzen verarbeitet werden und eine zusätzliche Kerze vor dem ersten Trade benötigt wird (um die SMA-Steigung zu messen).

Parameter

Name Beschreibung Standard Hinweise
Candle Type Zeitrahmen für den Kerzenaufbau. 1-Minuten-Zeitrahmen Es kann jeder von StockSharp unterstützte Kerzendatentyp ausgewählt werden.
MA Length Anzahl abgeschlossener Kerzen im SMA. 12 Entspricht der Standardperiode des MetaTrader-Experten.
Trade Volume Market-Order-Volumen für Einstiege. 1 Die Strategie schließt die entgegengesetzte Exposition vor dem Öffnen einer neuen Position.

Alle Parameter stehen für die Optimierung in StockSharp Designer oder Runner zur Verfügung.

Implementierungshinweise

  • Die Strategie basiert auf SubscribeCandles und Bind, sodass Indikatorwerte direkt in die Verarbeitungsmethode gestreamt werden, ohne manuelle Historienverwaltung.
  • Der SMA wird in einem privaten Feld gespeichert, um ihn im Diagrammbereich zu zeichnen, wenn einer verfügbar ist.
  • Signale werden nur verarbeitet, wenn IsFormedAndOnlineAndAllowTrading() true zurückgibt, sodass die Strategie den globalen Handelszustand respektiert.
  • Positions-Umkehrungen folgen der MetaTrader-Vorlage: zuerst die aktuelle Exposition schließen, dann die neue Seite mit dem konfigurierten Trade-Volumen öffnen.

Dateien

  • CS/CrossoverMaStrategy.cs – C#-Implementierung der konvertierten Strategie.
  • README.md – englische Dokumentation.
  • README_zh.md – chinesische Dokumentation.
  • README_ru.md – russische Dokumentation.

Portierungsunterschiede

  • Geldmanagement-, Trailing-Stop- und andere MetaTrader-Framework-Klassen werden weggelassen, da StockSharp Positionsgrößen und Risiken extern verwaltet. Der Parameter Trade Volume ersetzt die festen Lot-Einstellungen des ursprünglichen Experten.
  • MetaTrader verwendete separate Datenserien für Kerzen-Öffnungs- und -Schlusspreise. StockSharp-Kerzen enthalten bereits beide Preise, sodass keine zusätzlichen Indikatoren erforderlich sind.
  • Indikatorinitialisierung, -validierung und Lebenszyklusverwaltung werden automatisch von StockSharp gehandhabt, wodurch der umfangreiche Boilerplate-Code der MQL-Version entfällt.
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;

public class CrossoverMaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public CrossoverMaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}