GitHub で見る

ColorMetroDuplexStrategy 戦略

概要

ColorMetroDuplexStrategy は、MetaTrader 5のエキスパート Exp_ColorMETRO_Duplex をC#に変換したものです。元のロボットはColorMETROインジケーターの2つの独立したインスタンスを使用して、ロングとショートのトレーディングモジュールを管理します。各モジュールは独自のローソク足サブスクリプションで動作し、ColorMETROインジケーターが生成する2つのステップ式RSIエンベロープを評価し、高速と低速のエンベロープがクロスしたときにオプションでポジションを開閉します。

StockSharpバージョンは両モジュールを維持し、同じシグナル評価ルールを再現しながら、ローソク足サブスクリプション、注文管理、インジケーターバインディングにはハイレベルAPIを使用します。MT5のiCustom実装を模倣するカスタム ColorMetroIndicator が含まれており、ColorMETROの高速・低速バンドと内部RSI値を公開します。

動作原理

  1. 2つの SignalModule インスタンスが作成されます — LongShort — それぞれ独自のローソク足シリーズ、ColorMETRO設定、トレード管理オプションを持ちます。
  2. 戦略が開始されると、各モジュールは設定された時間軸をサブスクライブし、SubscribeCandles(...).BindEx(...) を通じて ColorMetroIndicator をバインドします。
  3. 確定したローソク足ごとにインジケーターが生成するもの:
    • 高速ColorMETROバンド(高速RSIエンベロープ)。
    • 低速ColorMETROバンド(低速RSIエンベロープ)。
    • 基礎となるRSI値(参照のみに使用)。
  4. モジュールはインジケーターの履歴を保存し、設定された SignalBar シフトを使用して最後の2つの値を評価します(MT5の CopyBuffer ロジックに対応)。
  5. トレードルール:
    • ロングモジュール
      • 開く: 前のバーで高速バンドが低速バンドより上にあり、現在は以下または等しい。
      • 閉じる: 前のバーで低速バンドが高速バンドより上にあった。
    • ショートモジュール
      • 開く: 前のバーで高速バンドが低速バンドより下にあり、現在は以上または等しい。
      • 閉じる: 前のバーで低速バンドが高速バンドより下にあった。
  6. 注文は BuyMarket / SellMarket 経由でルーティングされます。現在のネットポジションが考慮されます — 反対のトレードは新しいポジションを開く前に既存のエクスポージャーを解消します。

パラメーター

各モジュールは専用のパラメーターグループを公開します。デフォルト値はMT5エキスパートを反映します。

共有市場パラメーター

  • Long_VolumeShort_Volume — 新規エントリーに使用するトレードサイズ(ロット)。
  • Long_OpenAllowedShort_OpenAllowed — モジュールのトレード開始を有効または無効にします。
  • Long_CloseAllowedShort_CloseAllowed — 自動エグジットを有効または無効にします。
  • Long_MarginModeShort_MarginMode — 互換性のために保持された資金管理モード(このポートでは効果なし)。
  • Long_StopLossLong_TakeProfitLong_DeviationShort_StopLossShort_TakeProfitShort_Deviation — ドキュメント用として予約済み;ストップとスリッページ制御はこのバージョンでは自動化されていません。
  • Long_MagicShort_Magic — 参照用として保存された元のMT5マジックナンバー。

インジケーターパラメーター

  • Long_CandleTypeShort_CandleType — 各ColorMETROモジュールの時間軸。
  • Long_PeriodRSIShort_PeriodRSI — ColorMETROアルゴリズム内で使用されるRSI長。
  • Long_StepSizeFastShort_StepSizeFast — 高速エンベロープのステップ(RSIポイント単位)。
  • Long_StepSizeSlowShort_StepSizeSlow — 低速エンベロープのステップ。
  • Long_SignalBarShort_SignalBar — インジケーターバッファ読み取り時のバーシフト(MT5の SignalBar 入力と同一)。
  • Long_AppliedPriceShort_AppliedPrice — RSI計算の価格ソース(デフォルトは終値)。

MT5との違い

  • ポジションモデル — StockSharp戦略はネットポジションで動作します。元のエキスパートはマジックナンバーで個別ポジションを保管していましたが、ポートは反対側を開く前に現在のエクスポージャーを解消します。
  • 資金管理 — マージンモードと偏差設定はパラメーターとして保持されますが、自動的には適用されません。サイズを制御するには Volume 入力を使用してください。
  • ストップロス / テイクプロフィット — MT5エキスパートは各注文に保護ストップを配置していました。StockSharpバージョンは距離をパラメーターとして参照用に保持しますが、実際のストップ注文が必要な場合は別途実装する必要があります。
  • 時間レベル制御 — MT5コードはグローバル変数を使用して、シグナル時間ごとに1つのトレードのみを保証していました。StockSharpでは確定したローソク足ごとに一度処理し、重複エントリーを防ぐためにネットポジションチェックに依存します。

注意事項

  • カスタム ColorMetroIndicator はMT5ロジックを再現し、ステップ式RSIエンベロープとトレンドメモリを含みます。チャートやデバッグ用に高速/低速バンドと内部RSIを公開します。
  • コード内のコメントは、ポーティングの決定を明確にし、さらなるカスタマイズを支援するために意図的に詳細に記述されています。
  • ストップロスまたはテイクプロフィットの自動化を有効にするには、StockSharpのリスクコントロールを使用して保護注文を配置するよう SignalModule.ProcessModule を拡張してください。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Conversion of the MT5 expert "Exp_ColorMETRO_Duplex".
/// Uses RSI with step-based envelopes (fast/slow) to generate long/short signals.
/// </summary>
public class ColorMetroDuplexStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _fastStep;
	private readonly StrategyParam<int> _slowStep;
	private readonly StrategyParam<int> _signalCooldownBars;

	// fast envelope state
	private decimal? _fastMin, _fastMax;
	private int _fastTrend;
	private decimal? _prevFastBand;

	// slow envelope state
	private decimal? _slowMin, _slowMax;
	private int _slowTrend;
	private decimal? _prevSlowBand;
	private int _cooldownRemaining;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int FastStep { get => _fastStep.Value; set => _fastStep.Value = value; }
	public int SlowStep { get => _slowStep.Value; set => _slowStep.Value = value; }
	public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }

	public ColorMetroDuplexStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_rsiPeriod = Param(nameof(RsiPeriod), 7)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI lookback", "Indicator");

		_fastStep = Param(nameof(FastStep), 8)
			.SetGreaterThanZero()
			.SetDisplay("Fast Step", "Step size for fast envelope", "Indicator");

		_slowStep = Param(nameof(SlowStep), 24)
			.SetGreaterThanZero()
			.SetDisplay("Slow Step", "Step size for slow envelope", "Indicator");

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

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

		_fastMin = _fastMax = null;
		_slowMin = _slowMax = null;
		_fastTrend = _slowTrend = 0;
		_prevFastBand = _prevSlowBand = null;
		_cooldownRemaining = 0;
	}

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

		_fastMin = _fastMax = null;
		_slowMin = _slowMax = null;
		_fastTrend = _slowTrend = 0;
		_prevFastBand = _prevSlowBand = null;
		_cooldownRemaining = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, ProcessCandle)
			.Start();

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent)
		);

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

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

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var fStep = (decimal)FastStep;
		var sStep = (decimal)SlowStep;

		// fast envelope
		var fastMinCand = rsiVal - 2m * fStep;
		var fastMaxCand = rsiVal + 2m * fStep;

		if (_fastMin == null || _fastMax == null)
		{
			_fastMin = fastMinCand;
			_fastMax = fastMaxCand;
			_fastTrend = 0;

			_slowMin = rsiVal - 2m * sStep;
			_slowMax = rsiVal + 2m * sStep;
			_slowTrend = 0;
			return;
		}

		// fast trend
		if (rsiVal > _fastMax) _fastTrend = 1;
		else if (rsiVal < _fastMin) _fastTrend = -1;

		if (_fastTrend > 0 && fastMinCand < _fastMin) fastMinCand = _fastMin.Value;
		else if (_fastTrend < 0 && fastMaxCand > _fastMax) fastMaxCand = _fastMax.Value;

		// slow envelope
		var slowMinCand = rsiVal - 2m * sStep;
		var slowMaxCand = rsiVal + 2m * sStep;

		if (rsiVal > _slowMax) _slowTrend = 1;
		else if (rsiVal < _slowMin) _slowTrend = -1;

		if (_slowTrend > 0 && slowMinCand < _slowMin) slowMinCand = _slowMin.Value;
		else if (_slowTrend < 0 && slowMaxCand > _slowMax) slowMaxCand = _slowMax.Value;

		// compute band values
		decimal? fastBand = null;
		if (_fastTrend > 0) fastBand = fastMinCand + fStep;
		else if (_fastTrend < 0) fastBand = fastMaxCand - fStep;

		decimal? slowBand = null;
		if (_slowTrend > 0) slowBand = slowMinCand + sStep;
		else if (_slowTrend < 0) slowBand = slowMaxCand - sStep;

		_fastMin = fastMinCand;
		_fastMax = fastMaxCand;
		_slowMin = slowMinCand;
		_slowMax = slowMaxCand;

		if (fastBand == null || slowBand == null)
		{
			_prevFastBand = fastBand;
			_prevSlowBand = slowBand;
			return;
		}

		if (_prevFastBand == null || _prevSlowBand == null)
		{
			_prevFastBand = fastBand;
			_prevSlowBand = slowBand;
			return;
		}

		var up = fastBand.Value;
		var down = slowBand.Value;
		var prevUp = _prevFastBand.Value;
		var prevDown = _prevSlowBand.Value;

		_prevFastBand = fastBand;
		_prevSlowBand = slowBand;

		// Long signal: fast crosses below slow (up crosses down downward)
		var longOpen = prevUp > prevDown && up <= down;
		// Short signal: fast crosses above slow (up crosses down upward)
		var shortOpen = prevUp < prevDown && up >= down;

		if (_cooldownRemaining == 0 && longOpen && Position == 0)
		{
			BuyMarket();
			_cooldownRemaining = SignalCooldownBars;
		}
		else if (_cooldownRemaining == 0 && shortOpen && Position == 0)
		{
			SellMarket();
			_cooldownRemaining = SignalCooldownBars;
		}
	}
}