GitHub で見る

Color Ma RSI Trigger Duplex 戦略

この戦略は、エキスパートアドバイザー Exp_ColorMaRsi-Trigger_Duplex.mq5 をStockSharpのハイレベルAPIにポーティングしたものです。 2つの独立したMaRsi-Triggerデテクターを駆動します:ロングブロックはロングポジションをいつ開閉すべきかを決定し、ショートブロックはショートポジションに対して同じ役割を果たします。各デテクターは、カスタムインジケーターが強気(+1)、中立(0)、または弱気(-1)の市場圧力を報告しているかを評価します。2つの完了バーを待ってから反応する遅延確認、および方向ごとの独立した資金管理設定を含む、元のMetaTraderロジックが保持されています。

トレードアイデア

  1. 各ブロックに対して、選択可能なローソク足シリーズで2つの指数移動平均(高速と低速)と2つのRSIオシレーター(高速と低速)を計算します。
  2. 確定したローソク足ごとにインジケーターは +1 を返します(両方の高速研究がその低速対応物を上回る場合)、-1(両方が弱い場合)、そうでない場合は 0。生値はMT5インジケーターと同様に [-1, 1] の範囲にクランプされます。
  3. 戦略はインジケーター値のローリング履歴を保存します。設定された SignalBar オフセットに対して、SignalBar + 1 期間前のバーの値(older と呼ばれる)と SignalBar 期間前のバーの値(recent と呼ばれる)を比較します。
  4. ロングロジック:
    • older < 0 の場合、ロングブロックはアクティブなロングポジションを閉じます(ロングエグジットが有効な場合)。
    • older > 0 かつ recent <= 0 の場合、ロングブロックは新しいロングエントリーを準備します(ロングエントリーが有効な場合)。
  5. ショートロジックはロングブロックを反映します:
    • older > 0 の場合、ショートブロックは既存のショートポジションを終了します(ショートエグジットが有効な場合)。
    • older < 0 かつ recent >= 0 の場合、ブロックは新しいショートポジションを開きます(ショートエントリーが有効な場合)。
  6. 銘柄価格ステップで表されたオプションのストップロスとテイクプロフィットレベルが、価格が設定されたレベルをクロスするとポジションを決済します。

2つのブロックは異なるローソク足の時間軸と価格ソースをサブスクライブできるため、ユーザーは元のデュアル時間軸動作を複製したり、代替の組み合わせを試したりすることができます。

パラメーター

パラメーター 説明
LongCandleType, ShortCandleType ロングブロックとショートブロックが使用するローソク足データシリーズ。デフォルトは4時間足。
LongVolume, ShortVolume 対応するブロックが新しいポジションを開くときに取引される市場ボリューム。
LongAllowOpen, ShortAllowOpen 各ブロックの新しいポジション開始を有効または無効にします。
LongAllowClose, ShortAllowClose 各ブロックのクローズシグナルを有効または無効にします。
LongStopLossPoints, ShortStopLossPoints 価格ステップで測定したストップロス距離。0 に設定すると無効。
LongTakeProfitPoints, ShortTakeProfitPoints 価格ステップで測定したテイクプロフィット距離。0 に設定すると無効。
LongSignalBar, ShortSignalBar 現在のローソク足と決定ロジックに使用されるローソク足との間の完了バー数。
LongRsiPeriod, LongRsiLongPeriod, ShortRsiPeriod, ShortRsiLongPeriod 高速・低速RSIオシレーターの長さ。
LongMaPeriod, LongMaLongPeriod, ShortMaPeriod, ShortMaLongPeriod 高速・低速移動平均の長さ。
LongRsiPrice, ShortRsiPrice 高速RSIに入力する価格ソース(終値、始値、高値、安値、中央値、典型値、加重)。
LongRsiLongPrice, ShortRsiLongPrice 低速RSIに入力する価格ソース。
LongMaPrice, ShortMaPrice 高速移動平均に入力する価格ソース。
LongMaLongPrice, ShortMaLongPrice 低速移動平均に入力する価格ソース。
LongMaType, ShortMaType 高速ラインの移動平均法(単純、指数、平滑化、または加重)。
LongMaLongType, ShortMaLongType 低速ラインの移動平均法。

トレードルール

  1. 選択したローソク足シリーズが完了バーを生成し、すべてのインジケーターが完全にウォームアップされるまで待機します。
  2. 各ブロックにつきMaRsi-Trigger値を計算し、履歴バッファを更新します。
  3. 履歴に少なくとも SignalBar + 2 エントリが含まれると、トレードアイデアセクションで説明したロングおよびショートの条件を評価します。
  4. ポジションを開く前に、戦略は反対のエクスポージャーを中立化します(対応するクローズフラグが有効な場合)。例えば、新しいロングエントリーはショートポジションを閉じるのに十分なボリュームを買い、その後にのみロングボリュームを追加します。
  5. ポジションが開かれた後、オプションのストップロスとテイクプロフィットレベルが確定したローソク足ごとに監視されます。
  6. 開始と終了の注文は、ハイレベルヘルパー BuyMarketSellMarket を通じて市場注文として送信されます。

リスク管理

  • ストップとターゲットは Security.PriceStep を使用して測定されます。銘柄が価格ステップを公開していない場合、デフォルト値 1 が仮定され、このリポジトリの多くの既存戦略の動作と一致します。
  • ロングブロックとショートブロックは独立したストップとテイクの設定を維持します。
  • 戦略は追加の保護注文(トレーリングストップなど)を配置しません;インジケーターが発火したとき、またはハードストップ/ターゲットに達したときにのみトレードを閉じるMT5エキスパートの動作を反映します。

注意事項

  • StockSharpポートは評価ローソク足が終了した直後に市場注文を発行します。MetaTraderではエキスパートがタイムスタンプオフセットで次のバーの開始時刻に注文をスケジュールしていました;StockSharpはローソク足が閉じるとすぐにシグナルを処理するため、両方の動作は実質的に一致します。
  • 元のEAは複数の資金管理モード(LOTBALANCEなど)を公開していました。StockSharp戦略は直接ボリューム値で動作するため、ポートはボリュームを単純なパラメーターとして保持します(LongVolume/ShortVolume)。
  • MT5ヘルパーライブラリのスリッページとマジックナンバー固有のロジックはStockSharpでは必要なく、省略されています。
  • インジケーター計算はStockSharpの組み込み移動平均とRSI実装を活用します;出力は元の ColorMaRsi-Trigger インジケーターに合わせて [-1, 1] にクランプされます。
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

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

/// <summary>
/// Recreates the Exp_ColorMaRsi-Trigger_Duplex expert advisor.
/// Combines fast/slow MA and fast/slow RSI comparisons into a color code (+1/0/-1).
/// Trades based on color code transitions.
/// </summary>
public class ColorMaRsiTriggerDuplexStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _fastRsiPeriod;
	private readonly StrategyParam<int> _slowRsiPeriod;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<int> _signalCooldownBars;

	private readonly List<decimal> _colorHistory = new();
	private int _cooldownRemaining;

	/// <summary>Candle type.</summary>
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	/// <summary>Fast MA period.</summary>
	public int FastMaPeriod { get => _fastMaPeriod.Value; set => _fastMaPeriod.Value = value; }
	/// <summary>Slow MA period.</summary>
	public int SlowMaPeriod { get => _slowMaPeriod.Value; set => _slowMaPeriod.Value = value; }
	/// <summary>Fast RSI period.</summary>
	public int FastRsiPeriod { get => _fastRsiPeriod.Value; set => _fastRsiPeriod.Value = value; }
	/// <summary>Slow RSI period.</summary>
	public int SlowRsiPeriod { get => _slowRsiPeriod.Value; set => _slowRsiPeriod.Value = value; }
	/// <summary>Signal bar shift.</summary>
	public int SignalBar { get => _signalBar.Value; set => _signalBar.Value = value; }
	/// <summary>Bars to wait between reversals.</summary>
	public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }

	public ColorMaRsiTriggerDuplexStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for candles", "General");

		_fastMaPeriod = Param(nameof(FastMaPeriod), 5)
			.SetDisplay("Fast MA Period", "Fast moving average length", "Indicators");

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 10)
			.SetDisplay("Slow MA Period", "Slow moving average length", "Indicators");

		_fastRsiPeriod = Param(nameof(FastRsiPeriod), 3)
			.SetDisplay("Fast RSI Period", "Fast RSI length", "Indicators");

		_slowRsiPeriod = Param(nameof(SlowRsiPeriod), 13)
			.SetDisplay("Slow RSI Period", "Slow RSI length", "Indicators");

		_signalBar = Param(nameof(SignalBar), 1)
			.SetDisplay("Signal Bar", "History shift for signal evaluation", "Strategy");

		_signalCooldownBars = Param(nameof(SignalCooldownBars), 8)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "Strategy");
	}

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

		lock (_colorHistory)
			_colorHistory.Clear();

		_cooldownRemaining = 0;
	}

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

		lock (_colorHistory)
			_colorHistory.Clear();

		_cooldownRemaining = 0;

		var fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
		var slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };
		var fastRsi = new RelativeStrengthIndex { Length = FastRsiPeriod };
		var slowRsi = new RelativeStrengthIndex { Length = SlowRsiPeriod };

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(fastMa, slowMa, fastRsi, slowRsi, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal fastMaVal, decimal slowMaVal, decimal fastRsiVal, decimal slowRsiVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		// Calculate color code from MA and RSI comparisons
		var score = 0m;

		if (fastMaVal > slowMaVal)
			score = 1m;
		else if (fastMaVal < slowMaVal)
			score = -1m;

		if (fastRsiVal > slowRsiVal)
			score += 1m;
		else if (fastRsiVal < slowRsiVal)
			score -= 1m;

		// Clamp to [-1, 1]
		if (score > 1m) score = 1m;
		else if (score < -1m) score = -1m;

		decimal recent;
		decimal older;

		lock (_colorHistory)
		{
			_colorHistory.Insert(0, score);
			var maxHistory = Math.Max(2, SignalBar + 2);
			while (_colorHistory.Count > maxHistory)
				_colorHistory.RemoveAt(_colorHistory.Count - 1);

			if (_colorHistory.Count <= SignalBar + 1)
				return;

			recent = _colorHistory[SignalBar];
			older = _colorHistory[SignalBar + 1];
		}

		var longOpen = older == -1m && recent == 1m;
		var shortOpen = older == 1m && recent == -1m;
		var longExit = Position > 0 && recent < 0m;
		var shortExit = Position < 0 && recent > 0m;

		if (longExit)
		{
			SellMarket(Position);
			_cooldownRemaining = SignalCooldownBars;
		}
		else if (shortExit)
		{
			BuyMarket(Math.Abs(Position));
			_cooldownRemaining = SignalCooldownBars;
		}
		else if (_cooldownRemaining == 0 && longOpen && Position <= 0)
		{
			BuyMarket(Volume + Math.Abs(Position));
			_cooldownRemaining = SignalCooldownBars;
		}
		else if (_cooldownRemaining == 0 && shortOpen && Position >= 0)
		{
			SellMarket(Volume + Math.Abs(Position));
			_cooldownRemaining = SignalCooldownBars;
		}
	}
}