GitHub で見る

平均回帰モメンタム戦略

概要

平均回帰戦略は、MetaTrader エキスパート アドバイザー Mean reversion.mq4 の直接移植です。 StockSharp バージョンでは、元の取引アイデアが維持されています。つまり、終値で下落が続いた後に買い、同様の強気相場が続いた後に売るというものです。エントリーは、2 つの線形加重移動平均、より高い時間枠での勢いの強さ、および月次の MACD フィルターを使用したトレンドの調整によって確認されます。

この戦略は、適切な位置に設定されると、MQL バージョンの資金管理ルールを再作成します。設定可能なストップロスとテイクプロフィット (ピップ単位)、オプションの損益分岐点の再配置、市場が取引に有利に動くときに利益を確定するトレーリング ストップなどです。

取引ロジック

  1. シグナル時間枠 – 戦略は選択されたローソク足シリーズ (デフォルトは 15 分) で動作します。
  2. 枯渇検出 – 最後の BarsToCount 成約を収集します。長いセットアップでは、直近の終値が以前の各終値よりも低く、売りのシグナルとなる必要があります。短いセットアップには逆の条件が必要です。
  3. トレンド フィルター – 高速 LWMA (長さ FastMaLength) は、ロングの場合は低速 LWMA (SlowMaLength) より上、ショートの場合は下である必要があります。
  4. モメンタム フィルター – モメンタム インジケーター (期間 MomentumLength) は、MetaTrader スタイルのより高い時間枠 (M15 → H1、H1 → D1 など) で計算されます。最後の 3 つの運動量測定値のうち少なくとも 1 つは、100 から MomentumThreshold を超えて逸脱している必要があります。
  5. MACD の確認 – 月次の MACD (12/26/9) では、メイン ラインがロングの場合はシグナル ラインより上、ショートの場合は下にある必要があります。

すべての条件が満たされると、ストラテジーは OrderVolume を使用してポジションをオープンします。反対のトレードは、反転する前に現在のポジションをフラットにします。

ポジション管理

  • ストップロスとテイクプロフィットStopLossPips および TakeProfitPips を介してピップ単位で設定されます。
  • 損益分岐点 – 有効にすると、価格が BreakEvenTriggerPips 進んだ後、ストップはエントリー価格に BreakEvenOffsetPips を加えた値に移動します。
  • トレーリングストップEnableTrailing が true で含み益が TrailingStopPips を超える場合、ストップはステップ TrailingStepPips で価格を追跡します。

すべての価格変換では、MetaTrader の動作に一致させるために商品のピップ サイズが使用されます。

パラメーター

名前 説明 デフォルト
OrderVolume 市場エントリーに使用される注文サイズ。 1
CandleType シグナルに使用される主なキャンドル シリーズ。 M15
BarsToCount 枯渇をチェックした以前の終値の数。 10
FastMaLength 高速 LWMA 期間。 6
SlowMaLength LWMA 期間が遅い。 85
MomentumLength より高い時間枠でのモメンタム期間。 14
MomentumThreshold 勢いを確認するための 100 からの最小絶対偏差。 0.3
StopLossPips ピップス単位のストップロス距離。 20
TakeProfitPips 利益確定距離 (pips)。 50
UseBreakEven 損益分岐点までのストップの再配置を有効にします。 false
BreakEvenTriggerPips ストップを移動する前に必要なピップ単位の利益。 30
BreakEvenOffsetPips 損益分岐点に移行すると追加のピップが追加されます。 30
EnableTrailing トレーリングストップ管理を有効にします。 true
TrailingStopPips トレーリングを開始するために必要な利益 (pips)。 40
TrailingStepPips トレーリングストップによって維持される距離。 40

注意事項

  • 勢いのより高い時間枠は MetaTrader ステップに従います: M1→M15、M5→M30、M15→H1、M30→H4、H1→D1、H4→W1、D1→MN1、W1→MN1。
  • MACD の確認では常に月次の期間 (MN1) が使用されます。
  • この戦略では、タイムフレームベースのローソク足タイプを想定しています。ティックまたはレンジ キャンドルはサポートされていません。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "Mean Reversion" MetaTrader expert.
/// Buys after multi-bar sell-off when RSI is oversold, sells after multi-bar rally when RSI is overbought.
/// Uses consecutive bar count for exhaustion detection with RSI confirmation.
/// </summary>
public class MeanReversionMomentumStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _barsToCount;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiOverbought;
	private readonly StrategyParam<decimal> _rsiOversold;

	private RelativeStrengthIndex _rsi;
	private readonly List<decimal> _closeHistory = new();

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int BarsToCount
	{
		get => _barsToCount.Value;
		set => _barsToCount.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public decimal RsiOverbought
	{
		get => _rsiOverbought.Value;
		set => _rsiOverbought.Value = value;
	}

	public decimal RsiOversold
	{
		get => _rsiOversold.Value;
		set => _rsiOversold.Value = value;
	}

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

		_barsToCount = Param(nameof(BarsToCount), 5)
			.SetGreaterThanZero()
			.SetDisplay("Bars To Count", "Number of consecutive bars for exhaustion detection", "Signal");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period for confirmation", "Indicators");

		_rsiOverbought = Param(nameof(RsiOverbought), 70m)
			.SetDisplay("RSI Overbought", "RSI level for sell signal", "Signals");

		_rsiOversold = Param(nameof(RsiOversold), 30m)
			.SetDisplay("RSI Oversold", "RSI level for buy signal", "Signals");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_closeHistory.Clear();
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

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

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

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

		_closeHistory.Add(candle.ClosePrice);
		if (_closeHistory.Count > BarsToCount + 1)
			_closeHistory.RemoveAt(0);

		if (!_rsi.IsFormed || _closeHistory.Count < BarsToCount + 1)
			return;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Count consecutive down bars
		var downCount = 0;
		for (int i = _closeHistory.Count - 1; i >= 1; i--)
		{
			if (_closeHistory[i] < _closeHistory[i - 1])
				downCount++;
			else
				break;
		}

		// Count consecutive up bars
		var upCount = 0;
		for (int i = _closeHistory.Count - 1; i >= 1; i--)
		{
			if (_closeHistory[i] > _closeHistory[i - 1])
				upCount++;
			else
				break;
		}

		// Multi-bar sell-off + RSI oversold -> mean reversion buy
		if (downCount >= BarsToCount && rsiValue < RsiOversold)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));

			if (Position <= 0)
				BuyMarket(volume);
		}
		// Multi-bar rally + RSI overbought -> mean reversion sell
		else if (upCount >= BarsToCount && rsiValue > RsiOverbought)
		{
			if (Position > 0)
				SellMarket(Position);

			if (Position >= 0)
				SellMarket(volume);
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_closeHistory.Clear();
		_rsi = null;

		base.OnReseted();
	}
}