Auf GitHub ansehen

Testinator-Strategie

Überblick

Die Strategie ist eine C#-Portierung des MetaTrader Expert Advisors Testinator v1.30a. Es eröffnet nur Long-Positionen und verwaltet diese als Korb. Jeder neue Kauf ist nur zulässig, wenn ein konfigurierbarer Satz technischer Filter „wahr“ zurückgibt und der Preis um eine Mindestanzahl von Pips gestiegen ist. Die Ausgangslogik spiegelt die Eingangslogik wider, indem sie eine andere Filtermaske verwendet. Der ursprüngliche EA stützte sich auch auf tägliche ATR-Messungen für das Risikomanagement, daher abonniert der Hafen zusätzlich zum primären Zeitrahmen auch tägliche Kerzen.

Handelslogik

Eingabefiltermaske (Parameter BuySequence)

Die Maske verwendet die unteren neun Bits. Ein gesetztes Bit muss den entsprechenden Test der zuvor fertigen Kerze bestehen.

Etwas Zustand
1 EMA(12) liegt über SMA(14).
2 EMA(50) bleibt unter den Tiefstständen der letzten drei Kerzen.
4 Das vorherige Tief liegt unter dem unteren Bollinger-Band (20, 2).
8 ADX(14) liegt über dem -DI und +DI ist stärker als -DI.
16 Stochastic (16, 4, 8) hat %K über %D und %D über 80.
32 Williams %R(14) ist größer als -20.
64 Die Leitung MACD(12, 26, 9) liegt über der Signalleitung.
128 Ichimoku zeigt Senkou Span A über Span B, Tenkan über Kijun und das vorherige Tief über Span A.
256 RSI (Zeitraum RsiEntryPeriod) liegt über RsiEntryLevel und steigt relativ zum vorherigen Wert.

Filtermaske verlassen (Parameter CloseBuySequence)

Etwas Zustand
1 SMA(14) liegt über EMA(12).
2 EMA(50) liegt über den Höchstständen der letzten drei Kerzen.
4 Das vorherige Hoch liegt über dem oberen Ausstiegsband Bollinger (BollingerCloseLength, BollingerCloseDeviation).
8 -DI liegt über +DI.
16 Stochastic %D liegt unter 80.
32 Williams %R(14) ist kleiner als -80.
64 Die Linie MACD liegt unterhalb der Signallinie.
128 Ichimoku Senkou Span B liegt über Senkou Span A.
256 RSI (Zeitraum RsiClosePeriod) liegt unter RsiCloseLevel.

Ein Korb wird nur dann erweitert, wenn alle aktiven Eintragsbits „true“ zurückgeben, die Anzahl der Käufe unter MaxBuys liegt und der letzte Füllpreis mindestens StepPips entfernt ist. Der Korb wird immer dann abgeflacht, wenn die Austrittsmaske passiert oder wenn Schutzstufen ausgelöst werden.

Sitzungskontrolle und Risikomanagement

  • Der Handel findet nur zwischen TradeStartHour und TradeStartHour + TradeDurationHours - 1 (Osteuropäische Zeit) statt. Wenn das Fenster geschlossen ist und der Korb im Gewinn ist, werden alle Käufe geschlossen.
  • Die Schutzstopp- und Take-Profit-Distanzen werden in Pips ausgedrückt. Wenn Sie einen Wert auf -1 setzen, wird dieser deaktiviert, während 0 den ATR-Multiplikator aktiviert (StopRatio, TakeRatio).
  • Der Trailing Stop verwendet die gleiche ATR-Logik bis StartTrailPips, TrailStepPips, StartTrailRatio und TrailStepRatio.
  • Die Strategie berechnet tägliche ATR(15)-Werte für D1-Kerzen, um das Verhalten mit dem von EA identisch zu halten.

Parameter

  • TradeVolume – Losgröße (Volumen) für jeden Marktkauf.
  • BuySequence / CloseBuySequence – Bitmasken, die individuelle Indikatorfilter ermöglichen.
  • MaxBuys – maximale Anzahl offener Käufe, die als Warenkorb behandelt werden.
  • StepPips – Mindestpreisfortschritt (Pips) vor dem Hinzufügen zum Warenkorb.
  • TradeStartHour, TradeDurationHours – definiert das tägliche Handelsfenster.
  • TakeProfitPips, StopLossPips – feste Schutzstufen (Negativ deaktiviert, Null schaltet zu ATR-Verhältnissen).
  • StartTrailPips, TrailStepPips – Trailing-Startdistanz und Schritt (negativ deaktiviert, Null verwendet ATR-Verhältnisse).
  • TakeRatio, StopRatio, StartTrailRatio, TrailStepRatio – ATR Multiplikatoren, die verwendet werden, wenn der feste Wert gleich Null ist.
  • RsiEntryLevel, RsiEntryPeriod – RSI Schwellenwert und Zeitraum für die Eingabemaske.
  • RsiCloseLevel, RsiClosePeriod – RSI Schwellenwert und Zeitraum für die Exit-Maske.
  • BollingerCloseLength, BollingerCloseDeviation – Parameter der Ausgangsbänder Bollinger.
  • CandleType – Zeitrahmen der Arbeitskerzen (Tageskerzen werden automatisch für ATR abonniert).

Notizen

  • Der Port behält das Korbabrechnungsmodell des ursprünglichen EA bei: Alle Aufträge sind Käufe und es werden nur Marktaufträge verwendet.
  • Die Logik speichert absichtlich frühere Indikatorwerte, um die „bar[1]“-Prüfungen von MetaTrader nachzuahmen.
  • Die Strategie ignoriert die nicht verwendeten Eingaben von EA (TakeAsBasket, StopAsBasket usw.), da sie keinen Einfluss auf die Logik von MQL hatten.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Testinator strategy: RSI-based entry with EMA trend filter.
/// Buys when RSI rises above threshold and price is above EMA.
/// Sells when RSI drops below threshold and price is below EMA.
/// </summary>
public class TestinatorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<decimal> _rsiBuyLevel;
	private readonly StrategyParam<decimal> _rsiSellLevel;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public decimal RsiBuyLevel { get => _rsiBuyLevel.Value; set => _rsiBuyLevel.Value = value; }
	public decimal RsiSellLevel { get => _rsiSellLevel.Value; set => _rsiSellLevel.Value = value; }

	public TestinatorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");
		_emaPeriod = Param(nameof(EmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend filter period", "Indicators");
		_rsiBuyLevel = Param(nameof(RsiBuyLevel), 55m)
			.SetDisplay("RSI Buy Level", "RSI threshold for buy signal", "Signals");
		_rsiSellLevel = Param(nameof(RsiSellLevel), 45m)
			.SetDisplay("RSI Sell Level", "RSI threshold for sell signal", "Signals");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };

		decimal? prevClose = null;
		decimal? prevEma = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, ema, (candle, rsiVal, emaVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var close = candle.ClosePrice;

				if (prevClose.HasValue && prevEma.HasValue)
				{
					var crossUp = prevClose.Value <= prevEma.Value && close > emaVal;
					var crossDown = prevClose.Value >= prevEma.Value && close < emaVal;

					if (crossUp && rsiVal > RsiBuyLevel && Position <= 0)
						BuyMarket();
					else if (crossDown && rsiVal < RsiSellLevel && Position >= 0)
						SellMarket();
				}

				prevClose = close;
				prevEma = emaVal;
			})
			.Start();

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