GitHub で見る

トレンドフォロワーレインボー戦略

概要

トレンド フォロワー レインボー戦略は、MetaTrader 4 エキスパート アドバイザー「TrendFolwerRainbowMethodkyast773」の C# ポートです。この戦略は、いくつかの確認レイヤーを組み合わせて、レンジ内にある期間を除外しながら強いトレンドの方向に取引します。これは、指数移動平均の虹の配置、MACD の運動量、ラゲール オシレーターのしきい値、マネー フロー インデックスの読み取り値、およびポジションをトリガーする高速/低速の EMA クロスオーバーに依存します。

取引ロジック

  1. 取引ウィンドウ – シグナルは、現在のローソク足の終了時間が厳密に設定可能な開始時間と終了時間の間にある場合にのみ評価されます。これは、セッションの最初と最後の取引時間を回避する元の EA の時間フィルターを模倣しています。
  2. EMA クロスオーバー トリガー – 長いセットアップでは、高速の EMA (デフォルトの長さ 4) が低速の EMA (デフォルトの長さ 8) の上をクロスする必要があります。短いセットアップには反対のクロスオーバーが必要です。
  3. MACD の確認 – モメンタムの一致を確認するには、MACD ラインとシグナル ライン (デフォルト 5/35/5) の両方がロングトレードの場合はゼロより上、ショートトレードの場合はゼロ未満である必要があります。
  4. ラゲール フィルター – ラゲール フィルター値は、ロング取引の場合は 0.15 を超え、ショート取引の場合は 0.75 を下回る必要があり、カスタム指標で実行された元のしきい値チェックを再現します。
  5. レインボー アラインメント – レインボー構造を確認するには、指数移動平均の 5 つのバンドル (バンドルあたり 4 つの EMA) を単調に並べ替える必要があります。バンドルは、強気シナリオでは非増加順序で評価され、弱気シナリオでは非減少順序で評価されます。
  6. Money Flow Index Filter – The Money Flow Index (default period 14) must be below 40 for long entries and above 60 for short entries to avoid trading against volume-driven flow.
  7. ポジション管理 – 成行注文が使用されます。反対のシグナルが現れると、既存のエクスポージャーがクローズされ、反対方向に新しいポジションがオープンされます。

リスク管理

この戦略は、StockSharp の StartProtection ヘルパーを通じて組み込みの保護をサポートしています。

  • テイクプロフィットストップロスの距離は、EAのポイントベースの構成を反映するために価格ステップで表されます。
  • トレーリング ストップ距離も価格ステップを使用し、保護ブロックが開始されるとアクティブになります。

パラメーター

パラメータ 説明 デフォルト
OrderVolume 基本市場注文量。 1
TakeProfitPoints 価格ステップでの利益確定距離。 17
StopLossPoints 価格ステップのストップロス距離。 30
TrailingStopPoints 価格ステップでのトレーリングストップ距離。 45
TradingStartHour シグナルを評価する前にスキップされる最初の 1 時間 (両端を含む)。 1
TradingEndHour シグナルの評価後にスキップされる最後の 1 時間 (両端を含む)。 23
FastEmaLength クロスオーバー トリガーで使用される高速な EMA の長さ。 4
SlowEmaLength クロスオーバー トリガーで使用されるスロー EMA の長さ。 8
MacdFastLength MACD 高速の EMA 長さ。 5
MacdSlowLength MACD 遅い EMA 長さ。 35
MacdSignalLength MACD 信号の長さは EMA です。 5
LaguerreGamma ラゲール フィルターの平滑化係数。 0.7
LaguerreBuyThreshold ロングトレードでラゲールの閾値を上向きに超えた。 0.15
LaguerreSellThreshold ショートトレードでラゲールの閾値を下方に超えた。 0.75
MfiPeriod マネーフローインデックスの計算期間。 14
MfiBuyLevel 長いエントリを許可する最大 MFI レベル。 40
MfiSellLevel ショートエントリーを許可する最小MFIレベル。 60
RainbowGroup{1..5}Base 各レインボー バンドルの基本 EMA の長さ。オフセット (0、2、4、6) を追加することにより、各基本値から 4 つの連続する EMA が作成されます。 5 / 13 / 21 / 34 / 55
CandleType 戦略で使用されるプライマリ キャンドル シリーズ。デフォルトは 5 分足のローソク足です。 5分の時間枠

グラフ化

戦略は自動的に以下を描画します。

  • 定期購読シリーズの価格キャンドル。
  • クロスオーバーを視覚的に確認するための高速EMAと低速EMA。
  • しきい値の交差を観察するためのラゲール フィルター値。
  • 自身の取引がチャート領域にプロットされます。

注意事項

  • レインボー ロジックは、構成可能な EMA バンドルを構築することで、元の RainbowMMA カスタム インジケーターに近似します。必要に応じて、特定の虹テンプレートに一致するように塩基の長さを調整します。
  • すべてのコード コメント、ログ、ドキュメントは、必要に応じて英語で提供されます。
  • この戦略は C# の実装のみに焦点を当てています。このタスクでは Python ポートは生成されません。
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 that combines EMA crossover, MACD confirmation,
/// Laguerre filter thresholds, rainbow moving average structure and MFI filter.
/// </summary>
public class TrendFollowerRainbowStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<int> _tradingStartHour;
	private readonly StrategyParam<int> _tradingEndHour;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _macdFastLength;
	private readonly StrategyParam<int> _macdSlowLength;
	private readonly StrategyParam<int> _macdSignalLength;
	private readonly StrategyParam<decimal> _laguerreGamma;
	private readonly StrategyParam<decimal> _laguerreBuyThreshold;
	private readonly StrategyParam<decimal> _laguerreSellThreshold;
	private readonly StrategyParam<int> _mfiPeriod;
	private readonly StrategyParam<decimal> _mfiBuyLevel;
	private readonly StrategyParam<decimal> _mfiSellLevel;
	private readonly StrategyParam<int> _rainbowGroup1Base;
	private readonly StrategyParam<int> _rainbowGroup2Base;
	private readonly StrategyParam<int> _rainbowGroup3Base;
	private readonly StrategyParam<int> _rainbowGroup4Base;
	private readonly StrategyParam<int> _rainbowGroup5Base;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _emaFast = null!;
	private ExponentialMovingAverage _emaSlow = null!;
	private MovingAverageConvergenceDivergenceSignal _macd = null!;
	private AdaptiveLaguerreFilter _laguerre = null!;
	private MoneyFlowIndex _mfi = null!;
	private ExponentialMovingAverage[][] _rainbowGroups = [];

	private decimal? _previousFastEma;
	private decimal? _previousSlowEma;
	private decimal? _previousLaguerre;
	private decimal _pointValue;

	/// <summary>
	/// Initializes a new instance of the <see cref="TrendFollowerRainbowStrategy"/> class.
	/// </summary>
	public TrendFollowerRainbowStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 1m)
		.SetDisplay("Order Volume", "Base order volume", "Trading")
		;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 17m)
		.SetDisplay("Take Profit (pts)", "Distance in price steps for take profit", "Risk Management")
		;

		_stopLossPoints = Param(nameof(StopLossPoints), 30m)
		.SetDisplay("Stop Loss (pts)", "Distance in price steps for stop loss", "Risk Management")
		;

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 45m)
		.SetDisplay("Trailing Stop (pts)", "Distance in price steps for trailing stop", "Risk Management")
		;

		_tradingStartHour = Param(nameof(TradingStartHour), 1)
		.SetDisplay("Start Hour", "Hour (0-23) when trading window opens", "Trading Schedule")
		;

		_tradingEndHour = Param(nameof(TradingEndHour), 23)
		.SetDisplay("End Hour", "Hour (0-23) when trading window closes", "Trading Schedule")
		;

		_fastEmaLength = Param(nameof(FastEmaLength), 4)
		.SetRange(2, 20)
		.SetDisplay("Fast EMA", "Length of the fast EMA", "Indicators")
		;

		_slowEmaLength = Param(nameof(SlowEmaLength), 8)
		.SetRange(3, 50)
		.SetDisplay("Slow EMA", "Length of the slow EMA", "Indicators")
		;

		_macdFastLength = Param(nameof(MacdFastLength), 5)
		.SetDisplay("MACD Fast", "Fast EMA length for MACD", "Indicators")
		;

		_macdSlowLength = Param(nameof(MacdSlowLength), 35)
		.SetDisplay("MACD Slow", "Slow EMA length for MACD", "Indicators")
		;

		_macdSignalLength = Param(nameof(MacdSignalLength), 5)
		.SetDisplay("MACD Signal", "Signal EMA length for MACD", "Indicators")
		;

		_laguerreGamma = Param(nameof(LaguerreGamma), 0.7m)
		.SetRange(0.1m, 0.9m)
		.SetDisplay("Laguerre Gamma", "Smoothing factor for Laguerre filter", "Indicators")
		;

		_laguerreBuyThreshold = Param(nameof(LaguerreBuyThreshold), 0.15m)
		.SetDisplay("Laguerre Buy", "Threshold crossed upward for long signals", "Indicators")
		;

		_laguerreSellThreshold = Param(nameof(LaguerreSellThreshold), 0.75m)
		.SetDisplay("Laguerre Sell", "Threshold crossed downward for short signals", "Indicators")
		;

		_mfiPeriod = Param(nameof(MfiPeriod), 14)
		.SetDisplay("MFI Period", "Money Flow Index calculation period", "Indicators")
		;

		_mfiBuyLevel = Param(nameof(MfiBuyLevel), 40m)
		.SetDisplay("MFI Buy", "Upper bound for oversold check", "Indicators")
		;

		_mfiSellLevel = Param(nameof(MfiSellLevel), 60m)
		.SetDisplay("MFI Sell", "Lower bound for overbought check", "Indicators")
		;

		_rainbowGroup1Base = Param(nameof(RainbowGroup1Base), 5)
		.SetDisplay("Rainbow Group 1", "Base length for the fastest rainbow bundle", "Rainbow")
		;

		_rainbowGroup2Base = Param(nameof(RainbowGroup2Base), 13)
		.SetDisplay("Rainbow Group 2", "Base length for the second rainbow bundle", "Rainbow")
		;

		_rainbowGroup3Base = Param(nameof(RainbowGroup3Base), 21)
		.SetDisplay("Rainbow Group 3", "Base length for the middle rainbow bundle", "Rainbow")
		;

		_rainbowGroup4Base = Param(nameof(RainbowGroup4Base), 34)
		.SetDisplay("Rainbow Group 4", "Base length for the fourth rainbow bundle", "Rainbow")
		;

		_rainbowGroup5Base = Param(nameof(RainbowGroup5Base), 55)
		.SetDisplay("Rainbow Group 5", "Base length for the slowest rainbow bundle", "Rainbow")
		;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
		.SetDisplay("Candle Type", "Primary candle series", "General");
	}

	/// <summary>
	/// Base order volume.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

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

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

	/// <summary>
	/// Trailing stop distance expressed in price steps.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// First hour (0-23) when the strategy can evaluate entries.
	/// </summary>
	public int TradingStartHour
	{
		get => _tradingStartHour.Value;
		set => _tradingStartHour.Value = value;
	}

	/// <summary>
	/// Last hour (0-23) when the strategy can evaluate entries.
	/// </summary>
	public int TradingEndHour
	{
		get => _tradingEndHour.Value;
		set => _tradingEndHour.Value = value;
	}

	/// <summary>
	/// Fast EMA length used for the crossover signal.
	/// </summary>
	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	/// <summary>
	/// Slow EMA length used for the crossover signal.
	/// </summary>
	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	/// <summary>
	/// MACD fast EMA length.
	/// </summary>
	public int MacdFastLength
	{
		get => _macdFastLength.Value;
		set => _macdFastLength.Value = value;
	}

	/// <summary>
	/// MACD slow EMA length.
	/// </summary>
	public int MacdSlowLength
	{
		get => _macdSlowLength.Value;
		set => _macdSlowLength.Value = value;
	}

	/// <summary>
	/// MACD signal EMA length.
	/// </summary>
	public int MacdSignalLength
	{
		get => _macdSignalLength.Value;
		set => _macdSignalLength.Value = value;
	}

	/// <summary>
	/// Laguerre filter smoothing factor.
	/// </summary>
	public decimal LaguerreGamma
	{
		get => _laguerreGamma.Value;
		set => _laguerreGamma.Value = value;
	}

	/// <summary>
	/// Laguerre threshold that needs to be crossed upward to allow long signals.
	/// </summary>
	public decimal LaguerreBuyThreshold
	{
		get => _laguerreBuyThreshold.Value;
		set => _laguerreBuyThreshold.Value = value;
	}

	/// <summary>
	/// Laguerre threshold that needs to be crossed downward to allow short signals.
	/// </summary>
	public decimal LaguerreSellThreshold
	{
		get => _laguerreSellThreshold.Value;
		set => _laguerreSellThreshold.Value = value;
	}

	/// <summary>
	/// Money Flow Index period.
	/// </summary>
	public int MfiPeriod
	{
		get => _mfiPeriod.Value;
		set => _mfiPeriod.Value = value;
	}

	/// <summary>
	/// Maximum MFI level that still allows long entries.
	/// </summary>
	public decimal MfiBuyLevel
	{
		get => _mfiBuyLevel.Value;
		set => _mfiBuyLevel.Value = value;
	}

	/// <summary>
	/// Minimum MFI level that still allows short entries.
	/// </summary>
	public decimal MfiSellLevel
	{
		get => _mfiSellLevel.Value;
		set => _mfiSellLevel.Value = value;
	}

	/// <summary>
	/// Base period for the fastest rainbow bundle.
	/// </summary>
	public int RainbowGroup1Base
	{
		get => _rainbowGroup1Base.Value;
		set => _rainbowGroup1Base.Value = value;
	}

	/// <summary>
	/// Base period for the second rainbow bundle.
	/// </summary>
	public int RainbowGroup2Base
	{
		get => _rainbowGroup2Base.Value;
		set => _rainbowGroup2Base.Value = value;
	}

	/// <summary>
	/// Base period for the third rainbow bundle.
	/// </summary>
	public int RainbowGroup3Base
	{
		get => _rainbowGroup3Base.Value;
		set => _rainbowGroup3Base.Value = value;
	}

	/// <summary>
	/// Base period for the fourth rainbow bundle.
	/// </summary>
	public int RainbowGroup4Base
	{
		get => _rainbowGroup4Base.Value;
		set => _rainbowGroup4Base.Value = value;
	}

	/// <summary>
	/// Base period for the fifth rainbow bundle.
	/// </summary>
	public int RainbowGroup5Base
	{
		get => _rainbowGroup5Base.Value;
		set => _rainbowGroup5Base.Value = value;
	}

	/// <summary>
	/// Candle type used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

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

		_previousFastEma = null;
		_previousSlowEma = null;
		_previousLaguerre = null;
		_pointValue = 0m;
		_rainbowGroups = [];
	}

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

		_pointValue = Security?.PriceStep ?? 0m;
		Volume = OrderVolume;

		var takeProfit = ToAbsoluteUnit(TakeProfitPoints);
		var stopLoss = ToAbsoluteUnit(StopLossPoints);

		if (takeProfit != null || stopLoss != null)
		{
			StartProtection(
			takeProfit: takeProfit,
			stopLoss: stopLoss,
			isStopTrailing: TrailingStopPoints > 0m,
			useMarketOrders: true);
		}

		_emaFast = new EMA { Length = FastEmaLength };
		_emaSlow = new EMA { Length = SlowEmaLength };
		_macd = new MovingAverageConvergenceDivergenceSignal();
		_macd.Macd.ShortMa.Length = MacdFastLength;
		_macd.Macd.LongMa.Length = MacdSlowLength;
		_macd.SignalMa.Length = MacdSignalLength;
		_laguerre = new AdaptiveLaguerreFilter { Gamma = LaguerreGamma };
		_mfi = new MoneyFlowIndex { Length = MfiPeriod };
		_rainbowGroups = BuildRainbowGroups();

		var indicators = new List<IIndicator>
		{
			_emaFast,
			_emaSlow,
			_macd,
			_laguerre,
			_mfi
		};

		foreach (var group in _rainbowGroups)
		{
			indicators.AddRange(group);
		}

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(indicators.ToArray(), ProcessCandle)
		.Start();

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

	private ExponentialMovingAverage[][] BuildRainbowGroups()
	{
		var offsets = new[] { 0, 2, 4, 6 };

		return new[]
		{
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup1Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup2Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup3Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup4Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup5Base + o) }).ToArray()
		};
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
	{
		if (candle.State != CandleStates.Finished)
		return;

		var hour = candle.CloseTime.Hour;
		if (hour <= TradingStartHour || hour >= TradingEndHour)
		{
			UpdatePreviousValues(values);
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			UpdatePreviousValues(values);
			return;
		}

		var index = 0;

		var hasFast = TryGetDecimal(values[index++], out var fastEma);
		var hasSlow = TryGetDecimal(values[index++], out var slowEma);
		if (!hasFast || !hasSlow)
		{
			UpdatePreviousValues(values, hasFast ? fastEma : null, hasSlow ? slowEma : null);
			return;
		}

		var macdValue = values[index++];
		if (!macdValue.IsFinal || macdValue is not MovingAverageConvergenceDivergenceSignalValue macdData ||
		macdData.Macd is not decimal macdMain || macdData.Signal is not decimal macdSignal)
		{
			UpdatePreviousValues(values, fastEma, slowEma);
			return;
		}

		if (!TryGetDecimal(values[index++], out var laguerre))
		{
			UpdatePreviousValues(values, fastEma, slowEma);
			return;
		}

		if (!TryGetDecimal(values[index++], out var mfi))
		{
			UpdatePreviousValues(values, fastEma, slowEma, laguerre);
			return;
		}

		var rainbowValues = new List<decimal[]>(_rainbowGroups.Length);
		for (var groupIndex = 0; groupIndex < _rainbowGroups.Length; groupIndex++)
		{
			var group = _rainbowGroups[groupIndex];
			var decimals = new decimal[group.Length];

			for (var i = 0; i < group.Length; i++)
			{
				if (!TryGetDecimal(values[index++], out var rainbow))
				{
					UpdatePreviousValues(values, fastEma, slowEma, laguerre);
					return;
				}

				decimals[i] = rainbow;
			}

			rainbowValues.Add(decimals);
		}

		var rainbowBullish = rainbowValues.All(bundle => IsMonotonic(bundle, descending: true));
		var rainbowBearish = rainbowValues.All(bundle => IsMonotonic(bundle, descending: false));

		var emaCrossUp = _previousFastEma is decimal prevFast && _previousSlowEma is decimal prevSlow &&
		prevFast < prevSlow && fastEma > slowEma;

		var emaCrossDown = _previousFastEma is decimal prevFastDown && _previousSlowEma is decimal prevSlowDown &&
		prevFastDown > prevSlowDown && fastEma < slowEma;

		var laguerreBullish = _previousLaguerre is decimal prevLagBull &&
		prevLagBull <= LaguerreBuyThreshold && laguerre > LaguerreBuyThreshold;

		var laguerreBearish = _previousLaguerre is decimal prevLagBear &&
		prevLagBear >= LaguerreSellThreshold && laguerre < LaguerreSellThreshold;

		var macdBullish = macdMain > 0m && macdSignal > 0m;
		var macdBearish = macdMain < 0m && macdSignal < 0m;

		var mfiBullish = mfi < MfiBuyLevel;
		var mfiBearish = mfi > MfiSellLevel;

		if (emaCrossUp && macdBullish && Position <= 0)
		{
			var volume = Volume + Math.Abs(Position);
			BuyMarket(volume);
		}
		else if (emaCrossDown && macdBearish && Position >= 0)
		{
			var volume = Volume + Math.Abs(Position);
			SellMarket(volume);
		}

		_previousFastEma = fastEma;
		_previousSlowEma = slowEma;
		_previousLaguerre = laguerre;
	}

	private void UpdatePreviousValues(IIndicatorValue[] values, decimal? fastEma = null, decimal? slowEma = null, decimal? laguerre = null)
	{
		var index = 0;

		fastEma ??= TryGetDecimal(values[index++], out var fast) ? fast : null;
		slowEma ??= TryGetDecimal(values[index++], out var slow) ? slow : null;
		index++;
		laguerre ??= TryGetDecimal(values[index++], out var lag) ? lag : null;

		_previousFastEma = fastEma ?? _previousFastEma;
		_previousSlowEma = slowEma ?? _previousSlowEma;
		_previousLaguerre = laguerre ?? _previousLaguerre;
	}

	private bool IsMonotonic(decimal[] values, bool descending)
	{
		for (var i = 0; i < values.Length - 1; i++)
		{
			if (descending)
			{
				if (values[i] < values[i + 1])
				return false;
			}
			else
			{
				if (values[i] > values[i + 1])
				return false;
			}
		}

		return true;
	}

	private static bool TryGetDecimal(IIndicatorValue value, out decimal result)
	{
		if (!value.IsFinal)
		{
			result = default;
			return false;
		}

		result = value.ToDecimal();
		return true;
	}

	private Unit ToAbsoluteUnit(decimal points)
	{
		if (points <= 0m || _pointValue <= 0m)
		return null;

		return new Unit(points * _pointValue, UnitTypes.Absolute);
	}
}