GitHub で見る

ColorJjrsxTimePlus ストラテジー

MetaTrader5のエキスパートExp_ColorJJRSX_Tm_Plusから変換されました。ストラテジーはJurikで平滑化されたRSIオシレーターで検出されたトレンドの反転を取引し、元のマネー管理トグルを模倣したオプションの時間ベースのエグジットを含みます。

概要

  • アイデア: Color JJRSXオシレーター(ジュリク移動平均で平滑化されたRSIで近似)の傾きを追跡します。オシレーターが上昇すると、システムはショートを閉じてオプションでロングを開き、下降するとその逆を行います。
  • 市場: 接続されたSecurityで定義された単一のインスツルメント。
  • 時間軸: 設定可能;デフォルトは4時間ローソク足(元のEA入力と一致)。
  • 方向: ロングとショート。各方向は独立して無効化できます。
  • 注文タイプ: BuyMarket() / SellMarket()による成行注文。

インジケータースタック

  1. Relative Strength Index (RSI)RSI Lengthパラメーターを使用するベースのモメンタムオシレーター(JurXPeriodを反映)。
  2. Jurik Moving Average (JMA)Smoothing LengthでRSI出力を平滑化(JMAPeriodを反映)。MQLバージョンのJMAフェーズパラメーターはStockSharpで公開されていないため省略。
  3. Signal ShiftSignalBarパラメーターを再現。シグナルはSignal Shiftバー前の値と前の2つの値から生成され、傾きの変化を検出します。

トレードロジック

ロング管理

  • エントリー: Enable Long Entriesで有効化。平滑化オシレーターが2バー前に下降していた(previous > olderがfalse)、最後の完成バーで上向きに転換した(previous < older)、現在のバーでさらに高くなっている(current > previous)ことが必要。ポジションはフラットまたはショートでなければなりません。
  • エグジット: Exit Long on Downturnが有効でオシレーターが下向きに傾く(previous > older)場合、開いているロングが閉じられます。

ショート管理

  • エントリー: Enable Short Entriesで有効化。ストラテジーがフラットまたはロングの間、オシレーターが下向きに転換(previous > older)し、現在のバーで引き続き下落(current < previous)することが必要。
  • エグジット: Exit Short on Upturnが有効でオシレーターが上向きに傾く(previous < older)場合、開いているショートがカバーされます。

時間フィルター

  • Enable Time Exitはポジションの保有時間がHolding Minutesを超えると閉じます。これはnTime分後にポジションを清算する元のエキスパートのタイマーを反映しています。

リスク管理

  • Stop Loss (pts)Take Profit (pts)UnitTypes.PriceStepを使用してStartProtection経由でStockSharpの保護レベルに変換されます。

パラメーター

パラメーター 説明 デフォルト
Indicator Timeframe インジケーター計算のローソク足タイプ。 4時間ローソク足
RSI Length RSIのピリオド(JurXピリオドに類似)。 8
Smoothing Length Jurik MAの平滑化長(JMAピリオドに類似)。 3
Signal Shift 傾きを確認する前にスキップする完成バーの数(SignalBar)。 1
Enable Long Entries / Enable Short Entries 各方向でのトレードを開くことを許可。 true
Exit Long on Downturn / Exit Short on Upturn 既存ポジションのオシレーター駆動のエグジットを許可。 true
Enable Time Exit 保有時間ベースの清算を有効化。 true
Holding Minutes ポジションを開いたままにする最大時間(分)。 240
Stop Loss (pts) 保護ストップの価格ステップ単位の距離。 1000
Take Profit (pts) 利益目標の価格ステップ単位の距離。 2000

変換に関するメモ

  • 元のインジケーターのJJRSXヒストグラムバッファはRSI + Jurik平滑化でエミュレートされます。傾き情報のみが使用されるため、数値スケールの違いは判断に影響しません。
  • マネー管理オプション(MMMMModeDeviation)はポートされていません。StockSharpでの注文サイジングはStrategy.Volumeプロパティまたは外部ポートフォリオ設定で処理する必要があります。
  • 注文のレート制限のためにMQLで使用されるグローバル変数は、ストラテジーが完成したローソク足にのみ反応するため、ここでは不要です。
  • すべてのコメントとドキュメントはリポジトリのガイドラインに従い英語で書かれています。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trend-following strategy inspired by the Color JJRSX TM Plus Expert Advisor.
/// Uses a smoothed RSI oscillator to detect slope reversals and optional time-based exits.
/// </summary>
public class ColorJjrsxTimePlusStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _smoothingLength;
	private readonly StrategyParam<int> _signalShift;
	private readonly StrategyParam<bool> _enableBuyEntries;
	private readonly StrategyParam<bool> _enableSellEntries;
	private readonly StrategyParam<bool> _enableBuyExit;
	private readonly StrategyParam<bool> _enableSellExit;
	private readonly StrategyParam<bool> _enableTimeExit;
	private readonly StrategyParam<int> _holdingMinutes;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private readonly Queue<decimal> _smoothedValues = new();

	private RelativeStrengthIndex _rsi;
	private JurikMovingAverage _smoother;
	private DateTimeOffset? _entryTime;

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

	/// <summary>
	/// RSI length before Jurik smoothing.
	/// </summary>
	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	/// <summary>
	/// Length of the Jurik moving average.
	/// </summary>
	public int SmoothingLength
	{
		get => _smoothingLength.Value;
		set => _smoothingLength.Value = value;
	}

	/// <summary>
	/// Number of completed candles to shift before calculating signals.
	/// </summary>
	public int SignalShift
	{
		get => _signalShift.Value;
		set => _signalShift.Value = value;
	}

	/// <summary>
	/// Enable or disable long entries.
	/// </summary>
	public bool EnableBuyEntries
	{
		get => _enableBuyEntries.Value;
		set => _enableBuyEntries.Value = value;
	}

	/// <summary>
	/// Enable or disable short entries.
	/// </summary>
	public bool EnableSellEntries
	{
		get => _enableSellEntries.Value;
		set => _enableSellEntries.Value = value;
	}

	/// <summary>
	/// Allow closing long positions on oscillator downturns.
	/// </summary>
	public bool EnableBuyExit
	{
		get => _enableBuyExit.Value;
		set => _enableBuyExit.Value = value;
	}

	/// <summary>
	/// Allow closing short positions on oscillator upturns.
	/// </summary>
	public bool EnableSellExit
	{
		get => _enableSellExit.Value;
		set => _enableSellExit.Value = value;
	}

	/// <summary>
	/// Enable the maximum holding time exit.
	/// </summary>
	public bool EnableTimeExit
	{
		get => _enableTimeExit.Value;
		set => _enableTimeExit.Value = value;
	}

	/// <summary>
	/// Maximum minutes to keep an open position.
	/// </summary>
	public int HoldingMinutes
	{
		get => _holdingMinutes.Value;
		set => _holdingMinutes.Value = value;
	}

	/// <summary>
	/// Stop loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes <see cref="ColorJjrsxTimePlusStrategy"/>.
	/// </summary>
	public ColorJjrsxTimePlusStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Indicator Timeframe", "Timeframe used for the JJRSX oscillator", "General");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Length", "Period for the RSI calculation", "Indicator")
			
			.SetOptimize(4, 20, 1);

		_smoothingLength = Param(nameof(SmoothingLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Smoothing Length", "Jurik moving average length", "Indicator")
			
			.SetOptimize(1, 10, 1);

		_signalShift = Param(nameof(SignalShift), 1)
			.SetDisplay("Signal Shift", "Completed candles to skip before evaluating signals", "Indicator");

		_enableBuyEntries = Param(nameof(EnableBuyEntries), true)
			.SetDisplay("Enable Long Entries", "Allow opening long positions", "Execution");

		_enableSellEntries = Param(nameof(EnableSellEntries), true)
			.SetDisplay("Enable Short Entries", "Allow opening short positions", "Execution");

		_enableBuyExit = Param(nameof(EnableBuyExit), true)
			.SetDisplay("Exit Long on Downturn", "Close longs when the oscillator turns down", "Execution");

		_enableSellExit = Param(nameof(EnableSellExit), true)
			.SetDisplay("Exit Short on Upturn", "Close shorts when the oscillator turns up", "Execution");

		_enableTimeExit = Param(nameof(EnableTimeExit), true)
			.SetDisplay("Enable Time Exit", "Close positions after the holding period expires", "Risk");

		_holdingMinutes = Param(nameof(HoldingMinutes), 480)
			.SetGreaterThanZero()
			.SetDisplay("Holding Minutes", "Maximum time in minutes to keep a position", "Risk")
			
			.SetOptimize(60, 720, 60);

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
			.SetDisplay("Stop Loss (pts)", "Stop loss distance expressed in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
			.SetDisplay("Take Profit (pts)", "Take profit distance expressed in price steps", "Risk");
	}

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

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

		_smoothedValues.Clear();
		_entryTime = null;
	}

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

		_rsi = new RelativeStrengthIndex
		{
			Length = RsiLength
		};

		_smoother = new JurikMovingAverage
		{
			Length = SmoothingLength
		};

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

		var priceStep = Security?.PriceStep ?? 0.01m;
		StartProtection(
			stopLoss: StopLossPoints > 0 ? new Unit(StopLossPoints * priceStep, UnitTypes.Absolute) : null,
			takeProfit: TakeProfitPoints > 0 ? new Unit(TakeProfitPoints * priceStep, UnitTypes.Absolute) : null);
	}

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

		if (_smoother is null)
			return;

		HandleTimeExit(candle.CloseTime);

		var smoothValue = _smoother.Process(new DecimalIndicatorValue(_smoother, rsiValue, candle.CloseTime) { IsFinal = true });

		if (!_smoother.IsFormed || smoothValue is not DecimalIndicatorValue smoothDecimal)
			return;

		_smoothedValues.Enqueue(smoothDecimal.Value);

		var required = SignalShift + 3;

		if (_smoothedValues.Count < required)
			return;

		while (_smoothedValues.Count > required)
		{
			_smoothedValues.Dequeue();
		}

		var values = _smoothedValues.ToArray();

		var currentIndex = values.Length - SignalShift - 1;
		var previousIndex = values.Length - SignalShift - 2;
		var olderIndex = values.Length - SignalShift - 3;

		if (currentIndex < 0 || previousIndex < 0 || olderIndex < 0)
			return;

		var current = values[currentIndex];
		var previous = values[previousIndex];
		var older = values[olderIndex];

		var slopeUp = previous < older;
		var slopeDown = previous > older;

		if (EnableSellExit && slopeUp && Position < 0)
		{
			BuyMarket();
			_entryTime = null;
		}

		if (EnableBuyExit && slopeDown && Position > 0)
		{
			SellMarket();
			_entryTime = null;
		}

		if (EnableBuyEntries && slopeUp && current > previous && Position <= 0)
		{
			BuyMarket();
			_entryTime = candle.CloseTime;
		}
		else if (EnableSellEntries && slopeDown && current < previous && Position >= 0)
		{
			SellMarket();
			_entryTime = candle.CloseTime;
		}
	}

	private void HandleTimeExit(DateTimeOffset candleTime)
	{
		if (!EnableTimeExit || Position == 0 || _entryTime is null)
			return;

		var minutesInPosition = (candleTime - _entryTime.Value).TotalMinutes;

		if (minutesInPosition < HoldingMinutes)
			return;

		if (Position > 0)
		{
			SellMarket();
		}
		else if (Position < 0)
		{
			BuyMarket();
		}

		_entryTime = null;
	}
}