Auf GitHub ansehen

VR ZVER-Strategie

Übersicht

Die VR ZVER-Strategie ist ein Trendfolge-System, das drei Bestätigungsschichten kombiniert: ein schnelles/langsames/sehr langsames EMA-Stack, den Stochastischen Oszillator und den Relative Strength Index (RSI). Alle aktiven Filter müssen übereinstimmen, bevor eine Position eröffnet wird, was dabei hilft, Trades während turbulenter und widersprüchlicher Marktphasen zu vermeiden. Die Konvertierung behält die ursprüngliche Break-Even- und Schutzlogik bei, während die High-Level-API von StockSharp verwendet wird.

Marktregime-Erkennung

  1. EMA-Struktur – Die Standardkonfiguration verwendet exponentielle gleitende Durchschnitte mit den Perioden 3, 5 und 7. Ein Long-Bias erfordert, dass der schnelle EMA über dem langsamen EMA liegt und der langsame EMA über dem sehr langsamen EMA bleibt. Ein Short-Bias kehrt diese Beziehung um.
  2. Stochastischer Oszillator – Das %K/%D-Paar wird sowohl auf Richtung als auch auf Niveau geprüft. Long-Trades erfordern, dass %K unter dem unteren Band und über %D liegt, was einen überverkauften Abprall signalisiert. Short-Trades erfordern, dass %K über dem oberen Band und unter %D liegt, was auf eine überkaufte Umkehr hindeutet.
  3. RSI-Filter – Der RSI muss unter dem unteren Schwellenwert liegen, um Long-Einstiege zu erlauben, oder über dem oberen Schwellenwert, um Short-Trades zu ermöglichen.

Nur wenn jeder aktivierte Filter übereinstimmt, sendet die Strategie eine Marktorder mit dem konfigurierten Volumen.

Risikomanagement

  • Stop Loss – Jeder Einstieg projiziert einen preisbasierten Stop mit der StopLossPips-Einstellung multipliziert mit der Pip-Größe des Instruments. Long-Positionen gehen aus, wenn das Kerzentief den Stop durchsticht, während Short-Positionen schließen, wenn das Kerzenhoch ihren Stop erreicht.
  • Take Profit – Ein symmetrisches Take-Profit-Niveau wird angewendet. Wenn die aktuelle Kerze das Ziel zugunsten des Trades erreicht, wird die Position sofort geschlossen.
  • Breakeven-Schutz – Nachdem der Preis die BreakevenPips-Distanz vorangeschritten ist, wird ein Breakeven-Modus aktiviert. Jede Rückbewegung zum Einstiegspreis wird die Position abflachen, um Kapital zu erhalten.
  • Order-Bereinigung – Alle aktiven Orders werden vor dem Eröffnen eines neuen Trades storniert, um unbeabsichtigtes Stapeln zu vermeiden.

Parameter

Parameter Beschreibung
CandleType Für Berechnungen verwendete Kerzenserie.
UseMovingAverage Aktiviert oder deaktiviert den EMA-Trendfilter.
FastMaPeriod, SlowMaPeriod, VerySlowMaPeriod Perioden für schnelle, langsame und sehr langsame EMAs.
UseStochastic Schaltet die stochastische Bestätigungsschicht um.
StochasticKPeriod, StochasticDPeriod, StochasticSlowing Periodeneinstellungen für den Stochastischen Oszillator.
StochasticUpperLevel, StochasticLowerLevel Überkauft- und Überverkauft-Schwellenwerte für %K.
UseRsi Aktiviert oder deaktiviert die RSI-Bestätigungsschicht.
RsiPeriod RSI-Mittelungsperiode.
RsiUpperLevel, RsiLowerLevel RSI-Schwellenwerte, die Überkauft-/Überverkauft-Bereiche definieren.
StopLossPips, TakeProfitPips Distanzen (in Pips) für Stop-Loss- und Take-Profit-Platzierung.
BreakevenPips Preisfortschritt, der vor dem Aktivieren des Breakeven-Schutzes erforderlich ist.
Volume Menge für jede Marktorder.

Implementierungshinweise

  • Die Pip-Größe wird aus dem Preisschritt des Instruments und der Anzahl der Dezimalstellen abgeleitet. Instrumente mit 3 oder 5 Dezimalstellen wenden automatisch die Standard-10x-Anpassung an, die in der ursprünglichen MQL-Version verwendet wird.
  • Alle Indikatordaten werden über BindEx abgerufen, wodurch sichergestellt wird, dass die Strategie nur auf abgeschlossene Kerzen mit finalisierten Indikatorwerten reagiert.
  • Die Strategie ist standardmäßig flat; Positionen werden niemals umgekehrt, ohne die bestehende Exposure zuerst zu schließen.
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;

/// <summary>
/// Multi-indicator strategy that combines EMA, Stochastic, and RSI confirmations.
/// </summary>
public class VrZverStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<bool> _useMovingAverage;
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _verySlowMaPeriod;
	private readonly StrategyParam<bool> _useStochastic;
	private readonly StrategyParam<int> _stochasticKPeriod;
	private readonly StrategyParam<int> _stochasticDPeriod;
	private readonly StrategyParam<int> _stochasticSlowing;
	private readonly StrategyParam<int> _stochasticUpperLevel;
	private readonly StrategyParam<int> _stochasticLowerLevel;
	private readonly StrategyParam<bool> _useRsi;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _rsiUpperLevel;
	private readonly StrategyParam<int> _rsiLowerLevel;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _breakevenPips;

	private ExponentialMovingAverage _fastMa = null!;
	private ExponentialMovingAverage _slowMa = null!;
	private ExponentialMovingAverage _verySlowMa = null!;
	private StochasticOscillator _stochastic = null!;
	private RelativeStrengthIndex _rsi = null!;

	private decimal _pipSize;
	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;
	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;
	private decimal? _longTakePrice;
	private decimal? _shortTakePrice;
	private decimal? _longBreakevenTrigger;
	private decimal? _shortBreakevenTrigger;
	private bool _longBreakevenArmed;
	private bool _shortBreakevenArmed;

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

	/// <summary>
	/// Enable moving average confirmation.
	/// </summary>
	public bool UseMovingAverage
	{
		get => _useMovingAverage.Value;
		set => _useMovingAverage.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>
	/// Very slow EMA period.
	/// </summary>
	public int VerySlowMaPeriod
	{
		get => _verySlowMaPeriod.Value;
		set => _verySlowMaPeriod.Value = value;
	}

	/// <summary>
	/// Enable Stochastic confirmation.
	/// </summary>
	public bool UseStochastic
	{
		get => _useStochastic.Value;
		set => _useStochastic.Value = value;
	}

	/// <summary>
	/// Stochastic oscillator %K period.
	/// </summary>
	public int StochasticKPeriod
	{
		get => _stochasticKPeriod.Value;
		set => _stochasticKPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %D smoothing period.
	/// </summary>
	public int StochasticDPeriod
	{
		get => _stochasticDPeriod.Value;
		set => _stochasticDPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %K smoothing period.
	/// </summary>
	public int StochasticSlowing
	{
		get => _stochasticSlowing.Value;
		set => _stochasticSlowing.Value = value;
	}

	/// <summary>
	/// Upper threshold for Stochastic %K.
	/// </summary>
	public int StochasticUpperLevel
	{
		get => _stochasticUpperLevel.Value;
		set => _stochasticUpperLevel.Value = value;
	}

	/// <summary>
	/// Lower threshold for Stochastic %K.
	/// </summary>
	public int StochasticLowerLevel
	{
		get => _stochasticLowerLevel.Value;
		set => _stochasticLowerLevel.Value = value;
	}

	/// <summary>
	/// Enable RSI confirmation.
	/// </summary>
	public bool UseRsi
	{
		get => _useRsi.Value;
		set => _useRsi.Value = value;
	}

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

	/// <summary>
	/// RSI upper threshold.
	/// </summary>
	public int RsiUpperLevel
	{
		get => _rsiUpperLevel.Value;
		set => _rsiUpperLevel.Value = value;
	}

	/// <summary>
	/// RSI lower threshold.
	/// </summary>
	public int RsiLowerLevel
	{
		get => _rsiLowerLevel.Value;
		set => _rsiLowerLevel.Value = value;
	}

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

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

	/// <summary>
	/// Breakeven activation distance in pips.
	/// </summary>
	public int BreakevenPips
	{
		get => _breakevenPips.Value;
		set => _breakevenPips.Value = value;
	}


	/// <summary>
	/// Initializes a new instance of <see cref="VrZverStrategy"/>.
	/// </summary>
	public VrZverStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles", "General");

		_useMovingAverage = Param(nameof(UseMovingAverage), true)
		.SetDisplay("Use EMA", "Enable EMA trend filter", "Signals");

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

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

		_verySlowMaPeriod = Param(nameof(VerySlowMaPeriod), 7)
		.SetGreaterThanZero()
		.SetDisplay("Very Slow EMA", "Very slow EMA period", "Signals");

		_useStochastic = Param(nameof(UseStochastic), true)
		.SetDisplay("Use Stochastic", "Enable stochastic confirmation", "Signals");

		_stochasticKPeriod = Param(nameof(StochasticKPeriod), 42)
		.SetGreaterThanZero()
		.SetDisplay("Stoch %K", "Stochastic %K period", "Signals");

		_stochasticDPeriod = Param(nameof(StochasticDPeriod), 5)
		.SetGreaterThanZero()
		.SetDisplay("Stoch %D", "Stochastic %D period", "Signals");

		_stochasticSlowing = Param(nameof(StochasticSlowing), 7)
		.SetGreaterThanZero()
		.SetDisplay("Stoch Smoothing", "Stochastic %K smoothing", "Signals");

		_stochasticUpperLevel = Param(nameof(StochasticUpperLevel), 55)
		.SetDisplay("Stoch Upper", "Upper stochastic threshold", "Signals");

		_stochasticLowerLevel = Param(nameof(StochasticLowerLevel), 50)
		.SetDisplay("Stoch Lower", "Lower stochastic threshold", "Signals");

		_useRsi = Param(nameof(UseRsi), true)
		.SetDisplay("Use RSI", "Enable RSI confirmation", "Signals");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetGreaterThanZero()
		.SetDisplay("RSI Period", "RSI averaging period", "Signals");

		_rsiUpperLevel = Param(nameof(RsiUpperLevel), 55)
		.SetDisplay("RSI Upper", "Upper RSI threshold", "Signals");

		_rsiLowerLevel = Param(nameof(RsiLowerLevel), 50)
		.SetDisplay("RSI Lower", "Lower RSI threshold", "Signals");

		_stopLossPips = Param(nameof(StopLossPips), 50)
		.SetNotNegative()
		.SetDisplay("Stop Loss", "Stop loss distance in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 70)
		.SetNotNegative()
		.SetDisplay("Take Profit", "Take profit distance in pips", "Risk");

		_breakevenPips = Param(nameof(BreakevenPips), 20)
		.SetNotNegative()
		.SetDisplay("Breakeven", "Breakeven activation distance", "Risk");

	}

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

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

		_pipSize = 0m;
		ResetPositionState();
	}

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

		_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
		_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };
		_verySlowMa = new ExponentialMovingAverage { Length = VerySlowMaPeriod };

		_stochastic = new StochasticOscillator
		{
			K = { Length = StochasticKPeriod },
			D = { Length = StochasticDPeriod },
		};

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		_pipSize = CalculatePipSize();

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(_fastMa, _slowMa, _verySlowMa, _stochastic, _rsi, ProcessCandle)
		.Start();

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

	private void ProcessCandle(
	ICandleMessage candle,
	IIndicatorValue fastValue,
	IIndicatorValue slowValue,
	IIndicatorValue verySlowValue,
	IIndicatorValue stochasticValue,
	IIndicatorValue rsiValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		if (!fastValue.IsFinal || !slowValue.IsFinal || !verySlowValue.IsFinal || !stochasticValue.IsFinal || !rsiValue.IsFinal)
		return;

		// Extract indicator outputs for the completed candle.
		var fast = fastValue.GetValue<decimal>();
		var slow = slowValue.GetValue<decimal>();
		var verySlow = verySlowValue.GetValue<decimal>();

		var stochastic = (StochasticOscillatorValue)stochasticValue;
		if (stochastic.K is not decimal stochK || stochastic.D is not decimal stochD)
		return;

		var rsi = rsiValue.GetValue<decimal>();

		var longClosed = HandleLongPosition(candle);
		if (!longClosed && Position > 0)
		return;

		var shortClosed = HandleShortPosition(candle);
		if (!shortClosed && Position < 0)
		return;

		if (Position == 0)
		ResetPositionState();

		if (!IsFormedAndOnlineAndAllowTrading())
		return;

		if (Position != 0)
		return;

		if (!UseMovingAverage && !UseStochastic && !UseRsi)
		return;

		// All enabled filters must align to confirm the long scenario.
		var longSignal = (!UseMovingAverage || fast > slow && slow > verySlow)
		&& (!UseStochastic || stochD < stochK && stochK < StochasticLowerLevel)
		&& (!UseRsi || rsi < RsiLowerLevel);

		var shortSignal = (!UseMovingAverage || verySlow > slow && slow > fast)
		&& (!UseStochastic || stochD > stochK && stochK > StochasticUpperLevel)
		&& (!UseRsi || rsi > RsiUpperLevel);

		if (longSignal && Volume > 0)
		{
			EnterLong(candle.ClosePrice);
		}
		else if (shortSignal && Volume > 0)
		{
			EnterShort(candle.ClosePrice);
		}
	}

	private void EnterLong(decimal price)
	{
		BuyMarket();

		_longEntryPrice = price;
		_longStopPrice = StopLossPips > 0 ? price - StopLossPips * _pipSize : null;
		_longTakePrice = TakeProfitPips > 0 ? price + TakeProfitPips * _pipSize : null;
		_longBreakevenTrigger = BreakevenPips > 0 ? price + BreakevenPips * _pipSize : null;
		_longBreakevenArmed = false;
	}

	private void EnterShort(decimal price)
	{
		SellMarket();

		_shortEntryPrice = price;
		_shortStopPrice = StopLossPips > 0 ? price + StopLossPips * _pipSize : null;
		_shortTakePrice = TakeProfitPips > 0 ? price - TakeProfitPips * _pipSize : null;
		_shortBreakevenTrigger = BreakevenPips > 0 ? price - BreakevenPips * _pipSize : null;
		_shortBreakevenArmed = false;
	}

	private bool HandleLongPosition(ICandleMessage candle)
	{
		if (Position <= 0 || _longEntryPrice is null)
		return false;

		// Exit the long trade if the protective stop is touched within the candle range.
		if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
		{
			SellMarket();
			ResetPositionState();
			return true;
		}

		// Take profit when the upper target is reached.
		if (_longTakePrice is decimal take && candle.HighPrice >= take)
		{
			SellMarket();
			ResetPositionState();
			return true;
		}

		if (!_longBreakevenArmed && _longBreakevenTrigger is decimal trigger && candle.HighPrice >= trigger)
		_longBreakevenArmed = true;

		// Once breakeven is armed, protect the entry price if momentum fades.
		if (_longBreakevenArmed && candle.LowPrice <= _longEntryPrice.Value)
		{
			SellMarket();
			ResetPositionState();
			return true;
		}

		return false;
	}

	private bool HandleShortPosition(ICandleMessage candle)
	{
		if (Position >= 0 || _shortEntryPrice is null)
		return false;

		// Cover the short trade if the stop level is breached.
		if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
		{
			BuyMarket();
			ResetPositionState();
			return true;
		}

		// Lock in profits if the target is met on the downside move.
		if (_shortTakePrice is decimal take && candle.LowPrice <= take)
		{
			BuyMarket();
			ResetPositionState();
			return true;
		}

		if (!_shortBreakevenArmed && _shortBreakevenTrigger is decimal trigger && candle.LowPrice <= trigger)
		_shortBreakevenArmed = true;

		// Breakeven logic mirrors the long side to guard against reversals.
		if (_shortBreakevenArmed && candle.HighPrice >= _shortEntryPrice.Value)
		{
			BuyMarket();
			ResetPositionState();
			return true;
		}

		return false;
	}

	private void ResetPositionState()
	{
		_longEntryPrice = null;
		_shortEntryPrice = null;
		_longStopPrice = null;
		_shortStopPrice = null;
		_longTakePrice = null;
		_shortTakePrice = null;
		_longBreakevenTrigger = null;
		_shortBreakevenTrigger = null;
		_longBreakevenArmed = false;
		_shortBreakevenArmed = false;
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0.0001m;
		if (step <= 0)
		step = 0.0001m;

		var decimals = Security?.Decimals ?? 0;
		return decimals is 3 or 5 ? step * 10m : step;
	}
}