Auf GitHub ansehen

FiveMinuteRsiCci-Strategie

FiveMinuteRsiCciStrategy ist ein StockSharp-Port des MetaTrader 4-Expertenberaters 5Mins Rsi Cci EA.mq4. Das ursprüngliche Skript handelt Fünf-Minuten-Kerzen, indem es einen RSI-Schwellenwertdurchschnitt mit einem geglätteten/EMA-Gleitenden-Durchschnitts-Filter und der Polarität von zwei CCI-Indikatoren kombiniert. Die C#-Version behält die gleiche Entscheidungslogik bei, während sie das übergeordnete API von StockSharp für Datenabonnements, Indikatorbindung und Risikomanagement verwendet.

Handelslogik

  1. Abonnieren Sie den konfigurierten Kerzentyp (standardmäßig fünfminütiger Zeitrahmen) und aktualisieren Sie fünf Indikatoren in Echtzeit: RSI, ein geglätteter MA des Eröffnungspreises, ein EMA des Eröffnungspreises sowie schnelle und langsame CCIs, die aus typischen Preisen berechnet werden.
  2. Jede fertige Kerze wird nur dann ausgewertet, wenn keine Position offen ist und die aktuelle Geld-/Briefspanne unter MaxSpreadPoints liegt (umgerechnet in Preiseinheiten).
  3. Ein langes Signal erfordert:
    • der geglättete MA über dem EMA,
    • die RSI-Kreuzung nach oben durch BullishRsiLevel zwischen der vorherigen und der aktuellen Kerze,
    • beide CCI-Werte über Null.
  4. Ein kurzes Signal erfordert die umgekehrten Bedingungen (geglätteter MA unter EMA, RSI kreuzt nach unten durch BearishRsiLevel, beide CCIs unter Null).
  5. Das Ordervolumen reproduziert die dynamische Positionsgröße des EA: LotCoefficient × sqrt(Equity / EquityDivisor) wird auf den Volumenschritt des Instruments gerundet und durch VolumeMin/VolumeMax eingeschränkt.
  6. Die Schutzlogik wird von StartProtection verwaltet, das Stop-Loss-, Take-Profit- und Trailing-Stop-Distanzen hinzufügt, die aus MetaTrader Punkten in absolute Preisversätze umgewandelt werden.

Parameter

Parameter Standard Beschreibung
CandleType TimeSpan.FromMinutes(5).TimeFrame() Zeitrahmen für Indikatoraktualisierungen und Signalauswertung.
RsiPeriod 14 Anzahl der Kerzen, die in der RSI-Berechnung verwendet werden.
FastSmmaPeriod 2 Zeitraum des schnell geglätteten gleitenden Durchschnitts, der auf die Eröffnungspreise angewendet wird.
SlowEmaPeriod 6 Zeitraum der langsamen EMA, angewendet auf die Eröffnungspreise.
FastCciPeriod 34 Zeitraum des Fastens CCI, berechnet aus dem typischen Preis (H+L+C)/3.
SlowCciPeriod 175 Zeitraum des langsamen CCI, berechnet aus dem typischen Preis.
BullishRsiLevel 55 RSI Schwellenwert, der nach oben überschritten werden muss, um einen langen Eintrag zu aktivieren.
BearishRsiLevel 45 RSI Schwellenwert, der nach unten überschritten werden muss, um einen kurzen Eintrag zu aktivieren.
StopLossPoints 60 Stop-Loss-Distanz in MetaTrader Punkten (umgerechnet in absoluten Preis). Zum Deaktivieren auf 0 setzen.
TakeProfitPoints 0 Take-Profit-Distanz in MetaTrader Punkten. Zero behält das ursprüngliche EA-Verhalten bei (kein TP).
TrailingStopPoints 20 Trailing-Stop-Distanz in MetaTrader Punkten. Null deaktiviert das Nachziehen.
LotCoefficient 0.01 Basiskoeffizient, der in der dynamischen Positionsgrößenformel verwendet wird.
EquityDivisor 10 Teiler innerhalb der Quadratwurzel für die auf Eigenkapital basierende Dimensionierung (sqrt(Equity / EquityDivisor)).
MaxSpreadPoints 18 Maximal zulässiger Spread (in MetaTrader Punkten). Aufträge werden übersprungen, bis sich der Spread verringert.

Notizen

  • Der Spread-Filter basiert auf Daten der Ebene 1; Wenn die besten Geld-/Briefkurse nicht verfügbar sind, wartet die Strategie, bevor neue Positionen eröffnet werden.
  • Die Punkt-zu-Preis-Umrechnung wird automatisch um PriceStep und die Instrumentengenauigkeit skaliert (5/3-Dezimalinstrumente multiplizieren den Schritt mit 10), um den Point-Wert von MetaTrader widerzuspiegeln.
  • Stops und Trailing werden über die integrierte Schutz-Engine von StockSharp mit Marktausstiegen verwaltet, was der Verwendung von Marktaufträgen für Trailing-Stop-Updates durch EA entspricht.
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

/// <summary>
/// Finished-bar RSI threshold crossings confirmed by open-price SMMA/EMA and two typical-price CCIs.
/// </summary>
public class FiveMinuteRsiCciStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _fastSmmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _fastCciPeriod;
	private readonly StrategyParam<int> _slowCciPeriod;
	private readonly StrategyParam<decimal> _bullishRsiLevel;
	private readonly StrategyParam<decimal> _bearishRsiLevel;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<decimal> _lotCoefficient;
	private readonly StrategyParam<decimal> _equityDivisor;
	private readonly StrategyParam<decimal> _maxSpreadPoints;

	private RelativeStrengthIndex _rsi;
	private SmoothedMovingAverage _smma;
	private ExponentialMovingAverage _ema;
	private CommodityChannelIndex _fastCci;
	private CommodityChannelIndex _slowCci;
	private decimal? _previousRsi;
	private decimal? _bestBid;
	private decimal? _bestAsk;
	private decimal? _entryPrice;
	private decimal? _trailingStop;
	private bool _exitRequested;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int FastSmmaPeriod { get => _fastSmmaPeriod.Value; set => _fastSmmaPeriod.Value = value; }
	public int SlowEmaPeriod { get => _slowEmaPeriod.Value; set => _slowEmaPeriod.Value = value; }
	public int FastCciPeriod { get => _fastCciPeriod.Value; set => _fastCciPeriod.Value = value; }
	public int SlowCciPeriod { get => _slowCciPeriod.Value; set => _slowCciPeriod.Value = value; }
	public decimal BullishRsiLevel { get => _bullishRsiLevel.Value; set => _bullishRsiLevel.Value = value; }
	public decimal BearishRsiLevel { get => _bearishRsiLevel.Value; set => _bearishRsiLevel.Value = value; }
	public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
	public decimal TrailingStopPoints { get => _trailingStopPoints.Value; set => _trailingStopPoints.Value = value; }
	public decimal LotCoefficient { get => _lotCoefficient.Value; set => _lotCoefficient.Value = value; }
	public decimal EquityDivisor { get => _equityDivisor.Value; set => _equityDivisor.Value = value; }
	public decimal MaxSpreadPoints { get => _maxSpreadPoints.Value; set => _maxSpreadPoints.Value = value; }

	public FiveMinuteRsiCciStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame());
		_rsiPeriod = Param(nameof(RsiPeriod), 14).SetGreaterThanZero();
		_fastSmmaPeriod = Param(nameof(FastSmmaPeriod), 2).SetGreaterThanZero();
		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 6).SetGreaterThanZero();
		_fastCciPeriod = Param(nameof(FastCciPeriod), 34).SetGreaterThanZero();
		_slowCciPeriod = Param(nameof(SlowCciPeriod), 175).SetGreaterThanZero();
		_bullishRsiLevel = Param(nameof(BullishRsiLevel), 55m);
		_bearishRsiLevel = Param(nameof(BearishRsiLevel), 45m);
		_stopLossPoints = Param(nameof(StopLossPoints), 60m).SetNotNegative();
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 0m).SetNotNegative();
		_trailingStopPoints = Param(nameof(TrailingStopPoints), 20m).SetNotNegative();
		_lotCoefficient = Param(nameof(LotCoefficient), 0.01m).SetGreaterThanZero();
		_equityDivisor = Param(nameof(EquityDivisor), 10m).SetGreaterThanZero();
		_maxSpreadPoints = Param(nameof(MaxSpreadPoints), 18m).SetNotNegative();
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType), (Security, DataType.Level1)];

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

	private void ResetRuntime()
	{
		_previousRsi = null;
		_bestBid = null;
		_bestAsk = null;
		_entryPrice = null;
		_trailingStop = null;
		_exitRequested = false;
	}

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

		_rsi = new() { Length = RsiPeriod };
		_smma = new() { Length = FastSmmaPeriod };
		_ema = new() { Length = SlowEmaPeriod };
		_fastCci = new() { Length = FastCciPeriod };
		_slowCci = new() { Length = SlowCciPeriod };

		var point = GetPoint();
		var takeProfit = TakeProfitPoints > 0m ? new Unit(TakeProfitPoints * point, UnitTypes.Absolute) : new Unit();
		var stopLoss = StopLossPoints > 0m ? new Unit(StopLossPoints * point, UnitTypes.Absolute) : new Unit();
		StartProtection(takeProfit, stopLoss, useMarketOrders: true, isLocalStop: true);

		foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
		{
			var quotes = new Subscription(DataType.Level1, Security);
			quotes.MarketData.BuildField = field;
			SubscribeLevel1(quotes).Bind(ProcessLevel1).Start();
		}
		SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
	}

	private decimal GetPoint()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			throw new InvalidOperationException("Positive Security.PriceStep is required.");
		return Security.Decimals is 3 or 5 ? step * 10m : step;
	}

	private void ProcessLevel1(Level1ChangeMessage message)
	{
		if (message.TryGetDecimal(Level1Fields.BestBidPrice) is decimal bid && bid > 0m)
			_bestBid = bid;
		if (message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask && ask > 0m)
			_bestAsk = ask;

		if (Position == 0m || _entryPrice is null || TrailingStopPoints <= 0m || _exitRequested)
			return;

		var gap = TrailingStopPoints * GetPoint();
		var price = Position > 0m ? _bestBid : _bestAsk;
		if (price is null)
			return;

		if (Position > 0m)
		{
			if (price.Value >= _entryPrice.Value + gap)
			{
				var candidate = price.Value - gap;
				if (_trailingStop is null || candidate > _trailingStop.Value)
					_trailingStop = candidate;
			}
			if (_trailingStop is decimal stop && price.Value <= stop)
				RequestExit();
		}
		else
		{
			if (price.Value <= _entryPrice.Value - gap)
			{
				var candidate = price.Value + gap;
				if (_trailingStop is null || candidate < _trailingStop.Value)
					_trailingStop = candidate;
			}
			if (_trailingStop is decimal stop && price.Value >= stop)
				RequestExit();
		}
	}

	private void RequestExit()
	{
		if (_exitRequested || Position == 0m)
			return;
		_exitRequested = true;
		ClosePosition();
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);
		if (Position == 0m)
		{
			_entryPrice = null;
			_trailingStop = null;
			_exitRequested = false;
		}
		else if (_entryPrice is null)
		{
			_entryPrice = trade.Trade.Price;
		}
	}

	private bool IsSpreadAllowed()
		=> _bestBid is decimal bid && _bestAsk is decimal ask
			&& ask >= bid && ask - bid < MaxSpreadPoints * GetPoint();

	private decimal GetVolume()
	{
		var equity = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
		if (equity <= 0m || EquityDivisor <= 0m)
			return 0m;

		var raw = LotCoefficient * (decimal)Math.Sqrt((double)(equity / EquityDivisor));
		var minimum = Security?.MinVolume ?? 0m;
		var maximum = Security?.MaxVolume ?? decimal.MaxValue;
		var step = Security?.VolumeStep ?? 0m;
		if (maximum < minimum)
			return 0m;

		if (step > 0m)
		{
			var first = Math.Ceiling(minimum / step) * step;
			var last = Math.Floor(maximum / step) * step;
			if (first > last)
				return 0m;
			return Math.Clamp(Math.Floor(raw / step) * step, first, last);
		}

		return Math.Clamp(raw, minimum, maximum);
	}

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

		var rsi = _rsi.Process(candle.ClosePrice, candle.OpenTime, true);
		var smma = _smma.Process(candle.OpenPrice, candle.OpenTime, true);
		var ema = _ema.Process(candle.OpenPrice, candle.OpenTime, true);
		var fastCci = _fastCci.Process(candle);
		var slowCci = _slowCci.Process(candle);

		if (!rsi.IsFormed || !smma.IsFormed || !ema.IsFormed || !fastCci.IsFormed || !slowCci.IsFormed)
			return;

		var currentRsi = rsi.ToDecimal();
		if (_previousRsi is decimal previous && Position == 0m && !_exitRequested && IsSpreadAllowed())
		{
			var volume = GetVolume();
			if (volume > 0m)
			{
				if (previous <= BullishRsiLevel && currentRsi > BullishRsiLevel &&
					smma.ToDecimal() > ema.ToDecimal() &&
					fastCci.ToDecimal() > 0m && slowCci.ToDecimal() > 0m)
					BuyMarket(volume);
				else if (previous >= BearishRsiLevel && currentRsi < BearishRsiLevel &&
					smma.ToDecimal() < ema.ToDecimal() &&
					fastCci.ToDecimal() < 0m && slowCci.ToDecimal() < 0m)
					SellMarket(volume);
			}
		}

		_previousRsi = currentRsi;
	}
}