GitHub で見る

Two MA RSI 戦略

概要

Two MA RSI 戦略は、オリジナルの MetaTrader エキスパートアドバイザー「2MA_RSI」を変換したものです。相対力指数(RSI)フィルターで確認された、高速と低速の指数移動平均(EMA)クロスオーバーを使用します。注文は、損失後に次の注文ボリュームを増やすマーチンゲールスタイルの資金管理ブロックでサイズ設定されます。StockSharp バージョンは完成したロウソク足のみで動作し、価格ポイントでのオリジナルのテイクプロフィットとストップロスの動作を再現します。

データとインジケーター

  • 戦略は CandleType で定義された単一のロウソク足シリーズをサブスクライブします(デフォルトで5分ロウソク足)。
  • 各完成バーで3つのインジケーターが計算されます:
    • FastLength EMA(ロウソク足の終値に適用)。
    • SlowLength EMA。
    • RsiLength の長さを持つ RSI。
  • EMA クロスオーバーを検出するためにインジケーターバッファからデータを引き出さずに、過去のインジケーター値が内部に保存されます。

エントリーロジック

  1. イントラバーの再評価を避けるために、前のロウソク足が完成している必要があります。
  2. アクティブなポジションは許可されていません(Position == 0)。
  3. ロングエントリー:
    • 高速 EMA が低速 EMA を上回る(現在のバーの高速 EMA が低速 EMA より大きく、前のバーで高速 EMA < 低速 EMA だった)。
    • RSI 値が RsiOversold を下回り、売られすぎの市場を確認する。
  4. ショートエントリー:
    • 高速 EMA が類似の条件で低速 EMA を下回る(高速 EMA が現在低速 EMA より低く、以前は上にあった)。
    • RSI が RsiOverbought を上回り、買われすぎの市場を示す。
  5. すべての条件が満たされると、戦略はマーチンゲールモジュールに従ってサイズ設定された成行注文を送信します。

決済ロジック

  • 各エントリーの直後に保護的なストップロスとテイクプロフィットが計算されます。距離は「ポイント」で定義され、銘柄の PriceStep を通して変換されます:
    • ロング:
      • ストップロス = エントリー価格 - StopLossPoints * PriceStep
      • テイクプロフィット = エントリー価格 + TakeProfitPoints * PriceStep
    • ショート:
      • ストップロス = エントリー価格 + StopLossPoints * PriceStep
      • テイクプロフィット = エントリー価格 - TakeProfitPoints * PriceStep
  • これらの保護レベルのみが取引を閉じます。戦略は次のロウソク足を待って安値/高値が目標またはストップに触れたかどうかを確認し、それに応じて ClosePosition() 成行注文を送信します。
  • 決済の優先度はオリジナルロボットの保守的な動作と一致します:両方のレベルが同じロウソク足の範囲内に入る場合、ストップロスがテイクプロフィットより先に評価されます。

ポジションサイジングとマーチンゲール

  1. 基本ボリュームは各エントリーで floor(balance / BalanceDivider) * VolumeStep として計算されます。値は常に1ボリュームステップ以上を維持し、ポートフォリオの CurrentValue を使用します(必要な場合は BeginValue にフォールバック)。
  2. 各損失決済後、マーチンゲールステージは MaxDoublings まで1ずつ増加します。次の注文ボリュームは 2^stage で掛け算されます。
  3. 勝ちトレードまたは最大倍数に達すると、ステージはゼロにリセットされ、基本ボリュームに戻ります。
  4. MaxDoublings がゼロまたは負の場合、サイズは増加せず基本ボリュームに等しくなります。

追加の動作

  • 戦略は前の EMA 値を内部で追跡し、インジケーターバッファから過去の値を要求しません。
  • 注文は、戦略がオンラインで、インジケーターが形成されており、取引が許可されている場合のみ実行されます。
  • チャート出力は価格ロウソク足、自分の取引、および視覚的分析のための3つのインジケーターを描画します。

パラメーター

パラメーター 説明 デフォルト
FastLength 高速 EMA の長さ。 5
SlowLength 低速 EMA の長さ。 20
RsiLength RSI 計算で使用されるバーの数。 14
RsiOverbought 新規ロングをブロックしショートを許可する RSI レベル。 70
RsiOversold ロングを許可する RSI レベル。 30
StopLossPoints 価格ステップで表されたストップロス距離。 500
TakeProfitPoints 価格ステップでのテイクプロフィット距離。 1500
BalanceDivider 基本注文サイズを取得するためにポートフォリオ値を除算。 1000
MaxDoublings 連続損失後のマーチンゲール倍数の最大数。 1
CandleType 戦略が使用するロウソク足シリーズ。 5分時間軸

使用上の注意

  • 点ベースのリスク管理とポジションサイジングが一貫して機能するように、有効な PriceStepVolumeStep メタデータを持つポートフォリオと銘柄を提供してください。
  • 決済に成行注文が使用されるため、MetaTrader バージョンの指値注文と比較してスリッページとスプレッドが発生する可能性がありますが、ストップ/テイクの評価ロジックは保持されます。
  • 戦略は Python バージョンを作成しません;要求に応じて C# 実装のみが提供されます。
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>
/// Moving average crossover strategy with RSI confirmation and martingale sizing.
/// </summary>
public class TwoMaRsiStrategy : Strategy
{
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<decimal> _rsiOverbought;
	private readonly StrategyParam<decimal> _rsiOversold;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _balanceDivider;
	private readonly StrategyParam<int> _maxDoublings;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;
	private RelativeStrengthIndex _rsi;

	private decimal? _previousFast;
	private decimal? _previousSlow;
	private decimal _entryPrice;
	private decimal _stopPrice;
	private decimal _takeProfitPrice;
	private int _martingaleStage;
	private bool _isClosing;

	/// <summary>
	/// Initializes a new instance of the <see cref="TwoMaRsiStrategy"/> class.
	/// </summary>
	public TwoMaRsiStrategy()
	{
		_fastLength = Param(nameof(FastLength), 5)
			.SetDisplay("Fast EMA Length", "Length of the fast exponential moving average", "Indicators")
			
			.SetOptimize(2, 20, 1);

		_slowLength = Param(nameof(SlowLength), 20)
			.SetDisplay("Slow EMA Length", "Length of the slow exponential moving average", "Indicators")
			
			.SetOptimize(10, 60, 5);

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "Number of bars for the RSI calculation", "Indicators")
			
			.SetOptimize(5, 30, 1);

		_rsiOverbought = Param(nameof(RsiOverbought), 50m)
			.SetDisplay("RSI Overbought", "Upper RSI threshold for short entries", "Signals");

		_rsiOversold = Param(nameof(RsiOversold), 50m)
			.SetDisplay("RSI Oversold", "Lower RSI threshold for long entries", "Signals");

		_stopLossPoints = Param(nameof(StopLossPoints), 500m)
			.SetDisplay("Stop Loss (points)", "Stop loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 1500m)
			.SetDisplay("Take Profit (points)", "Take profit distance in price steps", "Risk");

		_balanceDivider = Param(nameof(BalanceDivider), 1000m)
			.SetDisplay("Balance Divider", "Divides portfolio value to estimate base order volume", "Money Management");

		_maxDoublings = Param(nameof(MaxDoublings), 1)
			.SetDisplay("Max Doublings", "Maximum number of martingale doublings", "Money Management");

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

	/// <summary>
	/// Fast EMA length.
	/// </summary>
	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	/// <summary>
	/// Slow EMA length.
	/// </summary>
	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	/// <summary>
	/// RSI period.
	/// </summary>
	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	/// <summary>
	/// Overbought threshold for RSI.
	/// </summary>
	public decimal RsiOverbought
	{
		get => _rsiOverbought.Value;
		set => _rsiOverbought.Value = value;
	}

	/// <summary>
	/// Oversold threshold for RSI.
	/// </summary>
	public decimal RsiOversold
	{
		get => _rsiOversold.Value;
		set => _rsiOversold.Value = value;
	}

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

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

	/// <summary>
	/// Divider applied to the portfolio value to calculate the base order volume.
	/// </summary>
	public decimal BalanceDivider
	{
		get => _balanceDivider.Value;
		set => _balanceDivider.Value = value;
	}

	/// <summary>
	/// Maximum number of martingale doublings.
	/// </summary>
	public int MaxDoublings
	{
		get => _maxDoublings.Value;
		set => _maxDoublings.Value = value;
	}

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

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

		_fastEma = null;
		_slowEma = null;
		_rsi = null;
		_previousFast = null;
		_previousSlow = null;
		_entryPrice = default;
		_stopPrice = default;
		_takeProfitPrice = default;
		_martingaleStage = 0;
		_isClosing = false;
	}

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

		_fastEma = new ExponentialMovingAverage
		{
			Length = FastLength
		};

		_slowEma = new ExponentialMovingAverage
		{
			Length = SlowLength
		};

		_rsi = new RelativeStrengthIndex
		{
			Length = RsiLength
		};

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

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

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

		if (Position == 0 && _isClosing)
		{
			_isClosing = false;
			_entryPrice = default;
			_stopPrice = default;
			_takeProfitPrice = default;
		}

		var fastResult = _fastEma.Process(candle);
		var slowResult = _slowEma.Process(candle);
		var rsiResult = _rsi.Process(candle);

		if (fastResult.IsEmpty || slowResult.IsEmpty || rsiResult.IsEmpty)
		{
			return;
		}

		if (!_fastEma.IsFormed || !_slowEma.IsFormed || !_rsi.IsFormed)
		{
			_previousFast = fastResult.GetValue<decimal>();
			_previousSlow = slowResult.GetValue<decimal>();
			return;
		}

		var fast = fastResult.GetValue<decimal>();
		var slow = slowResult.GetValue<decimal>();
		var rsi = rsiResult.GetValue<decimal>();
		var point = GetPoint();

		if (Position > 0)
		{
			var stopHit = candle.LowPrice <= _stopPrice;
			var takeHit = candle.HighPrice >= _takeProfitPrice;

			if (!_isClosing && stopHit)
			{
				_isClosing = true;
				ClosePosition();
				RegisterLoss();
			}
			else if (!_isClosing && takeHit)
			{
				_isClosing = true;
				ClosePosition();
				RegisterWin();
			}
		}
		else if (Position < 0)
		{
			var stopHit = candle.HighPrice >= _stopPrice;
			var takeHit = candle.LowPrice <= _takeProfitPrice;

			if (!_isClosing && stopHit)
			{
				_isClosing = true;
				ClosePosition();
				RegisterLoss();
			}
			else if (!_isClosing && takeHit)
			{
				_isClosing = true;
				ClosePosition();
				RegisterWin();
			}
		}
		else if (!_isClosing)
		{
			if (_previousFast is null || _previousSlow is null)
			{
				_previousFast = fast;
				_previousSlow = slow;
				return;
			}

			var prevFast = _previousFast.Value;
			var prevSlow = _previousSlow.Value;

			var crossUp = prevFast < prevSlow && fast > slow && rsi < RsiOversold;
			var crossDown = prevFast > prevSlow && fast < slow && rsi > RsiOverbought;

			if (crossUp)
			{
				var volume = CalculateOrderVolume();
				if (volume > 0m)
				{
					BuyMarket(volume);
					_entryPrice = candle.ClosePrice;
					_stopPrice = _entryPrice - StopLossPoints * point;
					_takeProfitPrice = _entryPrice + TakeProfitPoints * point;
				}
			}
			else if (crossDown)
			{
				var volume = CalculateOrderVolume();
				if (volume > 0m)
				{
					SellMarket(volume);
					_entryPrice = candle.ClosePrice;
					_stopPrice = _entryPrice + StopLossPoints * point;
					_takeProfitPrice = _entryPrice - TakeProfitPoints * point;
				}
			}
		}

		_previousFast = fast;
		_previousSlow = slow;
	}

	private decimal GetPoint()
	{
		var step = Security?.PriceStep ?? 1m;
		return step > 0m ? step : 1m;
	}

	private decimal CalculateOrderVolume()
	{
		var step = Security?.VolumeStep ?? 1m;
		if (step <= 0m)
			step = 1m;

		var baseVolume = step;
		var divider = BalanceDivider;
		var balance = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
		if (divider > 0m && balance > 0m)
		{
			var count = Math.Floor((double)(balance / divider));
			baseVolume = (decimal)count * step;
			if (baseVolume < step)
				baseVolume = step;
		}

		var multiplier = CalculateMartingaleMultiplier();
		var volume = baseVolume * multiplier;

		if (volume < step)
			volume = step;

		var ratio = volume / step;
		volume = Math.Ceiling(ratio) * step;

		return volume;
	}

	private decimal CalculateMartingaleMultiplier()
	{
		if (MaxDoublings <= 0 || _martingaleStage <= 0)
			return 1m;

		var stage = Math.Min(_martingaleStage, MaxDoublings);
		return (decimal)Math.Pow(2d, stage);
	}

	private void RegisterWin()
	{
		_martingaleStage = 0;
	}

	private void ClosePosition()
	{
		if (Position > 0)
			SellMarket(Position);
		else if (Position < 0)
			BuyMarket(-Position);
	}

	private void RegisterLoss()
	{
		if (MaxDoublings <= 0)
		{
			_martingaleStage = 0;
			return;
		}

		if (_martingaleStage < MaxDoublings)
		{
			_martingaleStage++;
		}
		else
		{
			_martingaleStage = 0;
		}
	}
}