GitHub で見る

FiveMinuteRsiCci 戦略

FiveMinuteRsiCciStrategy は、MetaTrader 4 Expert Advisor 5Mins Rsi Cci EA.mq4 の StockSharp ポートです。元のスクリプトは、RSI のしきい値クロスと平滑化/EMA 移動平均フィルター、および 2 つの CCI インジケーターの極性を組み合わせることにより、5 分間のローソク足を取引します。 C# バージョンは、データ サブスクリプション、インジケーター バインディング、リスク管理に StockSharp の高レベルの API を使用しながら、同じ意思決定ロジックを維持します。

取引ロジック

  1. 設定されたローソク足タイプ (デフォルトでは 5 分の時間枠) をサブスクライブし、5 つのインジケーターをリアルタイムで更新します: RSI、始値の平滑化された MA、始値の EMA、さらに典型的な価格から計算された高速 CCI と低速 CCI。
  2. 完了した各ローソク足は、オープンなポジションがなく、現在の買値/売値スプレッドが MaxSpreadPoints (価格単位に換算) を下回っている場合にのみ評価されます。
  3. 長い信号には次のものが必要です。
    • EMA を超える平滑化された MA、
    • 以前のローソク足と現在のローソク足の間の BullishRsiLevel を通って上向きに交差する RSI、
    • 両方の CCI 値がゼロより大きい。
  4. 短い信号には逆の条件が必要です(EMA 未満の平滑化された MA、RSI が BearishRsiLevel を下向きに交差、両方の CCI がゼロ未満)。
  5. 注文量は、EA の動的なポジション サイズを再現します。LotCoefficient × sqrt(Equity / EquityDivisor) は商品の量ステップに四捨五入され、VolumeMin/VolumeMax によって制限されます。
  6. 保護ロジックは StartProtection によって処理され、MetaTrader ポイントから絶対価格オフセットに変換されたストップロス、テイクプロフィット、トレーリングストップの距離が関連付けられます。

パラメーター

パラメータ デフォルト 説明
CandleType TimeSpan.FromMinutes(5).TimeFrame() インジケーターの更新とシグナル評価に使用されるタイムフレーム。
RsiPeriod 14 RSI の計算で使用されるローソク足の数。
FastSmmaPeriod 2 始値に適用される高速平滑移動平均の期間。
SlowEmaPeriod 6 低速 EMA の期間がオープン価格に適用されます。
FastCciPeriod 34 通常価格 (H+L+C)/3 から計算された高速 CCI の期間。
SlowCciPeriod 175 通常の価格から計算された低速の期間 CCI。
BullishRsiLevel 55 ロングエントリーを準備するには、RSI のしきい値を上方に超える必要があります。
BearishRsiLevel 45 ショートエントリーを準備するには、RSI のしきい値を下方に超える必要があります。
StopLossPoints 60 MetaTrader ポイント単位のストップロス距離 (絶対価格に変換)。無効にするには、0 に設定します。
TakeProfitPoints 0 MetaTrader ポイント単位のテイクプロフィット距離。ゼロは、元の EA の動作を維持します (TP なし)。
TrailingStopPoints 20 トレーリングストップ距離 (MetaTrader ポイント)。ゼロは末尾を無効にします。
LotCoefficient 0.01 動的位置サイジング式で使用される基本係数。
EquityDivisor 10 株式ベースのサイジングの平方根内の除数 (sqrt(Equity / EquityDivisor))。
MaxSpreadPoints 18 最大許容スプレッド (MetaTrader ポイント単位)。スプレッドが狭くなるまで注文はスキップされます。

注意事項

  • 拡散フィルタはレベル 1 データに依存します。最良の買値/売値が利用できない場合、戦略は新しいポジションをオープンする前に待機します。
  • ポイントから価格への変換は、PriceStep と商品精度 (5/3 小数商品はステップに 10 を乗算) によって自動的にスケーリングされ、MetaTrader の Point 値を反映します。
  • ストップとトレーリングは、StockSharp の市場エグジットを備えた組み込み保護エンジンを通じて管理され、トレーリングストップ更新のための EA の成行注文の使用と一致します。
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;
	}
}