GitHub で見る

Improve MA & RSI ヘッジ戦略

この戦略は、高レベル API を使用してオリジナルの MetaTrader エキスパート「Improve」を StockSharp に移植したものです。戦略に選択されたメイン銘柄とヘッジ銘柄の2つの銘柄を同時に取引します。取引方向は、メイン銘柄における2つの平滑化移動平均と相対力指数(RSI)の関係によって定義されます。ヘッジの脚はメインの脚の方向を反映し、同期したモメンタムの動きから利益を得ながら単一銘柄のリスクを制限しようとするペアのエクスポージャーを作成します。

戦略ロジック

  • 設定可能な高速および低速の期間でプライマリ銘柄に2つの平滑化移動平均(SMMA)を計算します。
  • 同じロウソク足で RSI を計算し、売られすぎ/買われすぎのしきい値を監視します。
  • 低速 SMMA が高速 SMMA を上回り、RSI が売られすぎのしきい値以下のとき、両方の銘柄でロングに入ります。
  • 低速 SMMA が高速 SMMA を下回り、RSI が買われすぎのしきい値以上のとき、両方の銘柄でショートに入ります。
  • 両方の脚の合計オープン利益が設定された金額目標を超えるまでポジションは開いたまま保持され、その時点で戦略は両側を清算します。

アルゴリズムは各銘柄の最新の終値を追跡します。合計利益は、現在の終値と各脚の保存されたエントリー価格の差から推定されます。ストップロスが適用されないため、価格が利益目標に達しない場合、ポジションは長期間開いたままになる可能性があります。

パラメーター

パラメーター 説明
Volume プライマリとヘッジの両方の銘柄に対する注文数量。
Profit Target 両方の脚で共有される金額目標;達成されると戦略はすべてのオープンポジションを閉じます。
Hedge Security プライマリ銘柄と並行して取引されるセカンダリ銘柄。
Fast MA 高速平滑化移動平均の期間(デフォルト 8)。
Slow MA 低速平滑化移動平均の期間(デフォルト 21)。高速 MA 期間より大きくなければなりません。
RSI Period RSI の計算に使用される長さ(デフォルト 21)。
Oversold MA 条件と共にロングエントリーをトリガーする RSI レベル(デフォルト 30)。
Overbought MA 条件と共にショートエントリーをトリガーする RSI レベル(デフォルト 70)。
Candle Type 計算の時間軸;デフォルトで1時間ロウソク足ですが調整可能です。

インジケーター

  • 平滑化移動平均(SMMA) – 高速と低速のトレンドコンポーネントを定義するために2回使用されます。
  • 相対力指数(RSI) – 確認のために売られすぎ/買われすぎ状態を決定します。

エントリーと決済ルール

  1. ロングエントリー
    • プライマリ銘柄で低速 SMMA > 高速 SMMA。
    • RSI ≤ 売られすぎ。
    • 両方の脚が同じ方向の成行注文で開かれます(買い/買い)。
  2. ショートエントリー
    • プライマリ銘柄で低速 SMMA < 高速 SMMA。
    • RSI ≥ 買われすぎ。
    • 両方の脚が同じ方向の成行注文で開かれます(売り/売り)。
  3. 決済
    • (プライマリ利益 + ヘッジ利益) ≥ Profit Target のとき、戦略は成行注文を使用して両方のポジションを閉じます。
    • 追加のストップロスまたはトレーリングロジックは適用されません;必要な場合はリスク管理を外部で追加する必要があります。

使用上の注意

  • 戦略を開始する前に、プライマリ銘柄とヘッジ銘柄の両方が割り当てられていることを確認してください;そうでなければ例外がスローされます。
  • 合計利益の推定はロウソク足の終値に依存します。2つの脚間のスリッページと実行の違いが実際の実現利益に影響を与える可能性があります。
  • 戦略は両方の脚を同時に開くため、同期した動きが期待される相関した銘柄(例えば通貨ペアや関連先物)に適しています。
  • ライブ取引時にはポートフォリオレベルのリスク管理を追加することを検討してください。オリジナルのアルゴリズムは決済に仮想的な利益目標のみを使用しているためです。
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Dual smoothed moving average and RSI hedge strategy converted from Improve.mq5.
/// </summary>
public class ImproveMaRsiHedgeStrategy : Strategy
{
	private readonly StrategyParam<decimal> _profitTarget;
	private readonly StrategyParam<Security> _hedgeSecurity;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _oversoldLevel;
	private readonly StrategyParam<decimal> _overboughtLevel;
	private readonly StrategyParam<DataType> _candleType;

	private SmoothedMovingAverage _fastMa = null!;
	private SmoothedMovingAverage _slowMa = null!;
	private RelativeStrengthIndex _rsi = null!;

	private decimal _baseLastClose;
	private decimal _hedgeLastClose;
	private decimal _baseEntryPrice;
	private decimal _hedgeEntryPrice;
	private bool _hasBaseClose;
	private bool _hasHedgeClose;
	private int _pairDirection;


	/// <summary>
	/// Profit target across both legs expressed in money.
	/// </summary>
	public decimal ProfitTarget
	{
		get => _profitTarget.Value;
		set => _profitTarget.Value = value;
	}

	/// <summary>
	/// Second instrument traded alongside the primary security.
	/// </summary>
	public Security HedgeSecurity
	{
		get => _hedgeSecurity.Value;
		set => _hedgeSecurity.Value = value;
	}

	/// <summary>
	/// Smoothed moving average period for the fast line.
	/// </summary>
	public int FastMaPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Smoothed moving average period for the slow line.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// RSI calculation length.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// RSI oversold threshold.
	/// </summary>
	public decimal OversoldLevel
	{
		get => _oversoldLevel.Value;
		set => _oversoldLevel.Value = value;
	}

	/// <summary>
	/// RSI overbought threshold.
	/// </summary>
	public decimal OverboughtLevel
	{
		get => _overboughtLevel.Value;
		set => _overboughtLevel.Value = value;
	}

	/// <summary>
	/// Type of candles used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ImproveMaRsiHedgeStrategy"/> class.
	/// </summary>
	public ImproveMaRsiHedgeStrategy()
	{

		_profitTarget = Param(nameof(ProfitTarget), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Profit Target", "Combined profit target across both legs", "Risk")
			;

		_hedgeSecurity = Param<Security>(nameof(HedgeSecurity))
			.SetDisplay("Hedge Security", "Secondary instrument to trade", "General");

		_fastPeriod = Param(nameof(FastMaPeriod), 8)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA", "Fast smoothed MA period", "Indicators")
			;

		_slowPeriod = Param(nameof(SlowMaPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA", "Slow smoothed MA period", "Indicators")
			;

		_rsiPeriod = Param(nameof(RsiPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "Length of the RSI", "Indicators")
			;

		_oversoldLevel = Param(nameof(OversoldLevel), 30m)
			.SetDisplay("Oversold", "RSI oversold threshold", "Indicators")
			;

		_overboughtLevel = Param(nameof(OverboughtLevel), 70m)
			.SetDisplay("Overbought", "RSI overbought threshold", "Indicators")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for calculations", "Data");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		if (Security != null)
			yield return (Security, CandleType);

		if (HedgeSecurity != null)
			yield return (HedgeSecurity, CandleType);
	}

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

		_fastMa = null!;
		_slowMa = null!;
		_rsi = null!;
		_baseLastClose = 0m;
		_hedgeLastClose = 0m;
		_baseEntryPrice = 0m;
		_hedgeEntryPrice = 0m;
		_hasBaseClose = false;
		_hasHedgeClose = false;
		_pairDirection = 0;
	}

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

		if (Security == null)
			throw new InvalidOperationException("Primary security must be specified.");

		if (HedgeSecurity == null)
			throw new InvalidOperationException("Hedge security must be specified.");

		if (FastMaPeriod >= SlowMaPeriod)
			throw new InvalidOperationException("Fast MA period must be less than slow MA period.");

		_fastMa = new SmoothedMovingAverage { Length = FastMaPeriod };
		_slowMa = new SmoothedMovingAverage { Length = SlowMaPeriod };
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		var baseSubscription = SubscribeCandles(CandleType);
		baseSubscription
			.Bind(_fastMa, _slowMa, _rsi, ProcessBaseCandle)
			.Start();

		var hedgeSubscription = SubscribeCandles(CandleType, false, HedgeSecurity);
		hedgeSubscription
			.Bind(ProcessHedgeCandle)
			.Start();
	}

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

		_baseLastClose = candle.ClosePrice;
		_hasBaseClose = true;

		CheckProfitTarget();

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (!_fastMa.IsFormed || !_slowMa.IsFormed || !_rsi.IsFormed)
			return;

		if (_pairDirection != 0)
			return;

		if (!_hasHedgeClose)
			return;

		if (slowValue > fastValue && rsiValue <= OversoldLevel)
		{
			OpenPair(1);
		}
		else if (slowValue < fastValue && rsiValue >= OverboughtLevel)
		{
			OpenPair(-1);
		}
	}

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

		_hedgeLastClose = candle.ClosePrice;
		_hasHedgeClose = true;

		CheckProfitTarget();
	}

	private void OpenPair(int direction)
	{
		if (direction == 0)
			return;

		var basePos = GetPositionValue(Security, Portfolio) ?? 0m;
		var hedgePos = GetPositionValue(HedgeSecurity, Portfolio) ?? 0m;

		if (basePos != 0m || hedgePos != 0m)
			return;

		var volume = Volume;

		if (direction > 0)
		{
			BuyMarket(volume, Security);
			BuyMarket(volume, HedgeSecurity);
		}
		else
		{
			SellMarket(volume, Security);
			SellMarket(volume, HedgeSecurity);
		}

		_pairDirection = direction;
		_baseEntryPrice = _baseLastClose;
		_hedgeEntryPrice = _hedgeLastClose;
	}

	private void CheckProfitTarget()
	{
		if (_pairDirection == 0 || !_hasBaseClose || !_hasHedgeClose)
			return;

		var baseProfit = _pairDirection > 0
			? (_baseLastClose - _baseEntryPrice) * Volume
			: (_baseEntryPrice - _baseLastClose) * Volume;

		var hedgeProfit = _pairDirection > 0
			? (_hedgeLastClose - _hedgeEntryPrice) * Volume
			: (_hedgeEntryPrice - _hedgeLastClose) * Volume;

		var totalProfit = baseProfit + hedgeProfit;

		if (totalProfit >= ProfitTarget)
		{
			ClosePair();
		}
	}

	private void ClosePair()
	{
		var basePos = GetPositionValue(Security, Portfolio) ?? 0m;
		if (basePos > 0)
		{
			SellMarket(basePos, Security);
		}
		else if (basePos < 0)
		{
			BuyMarket(-basePos, Security);
		}

		var hedgePos = GetPositionValue(HedgeSecurity, Portfolio) ?? 0m;
		if (hedgePos > 0)
		{
			SellMarket(hedgePos, HedgeSecurity);
		}
		else if (hedgePos < 0)
		{
			BuyMarket(-hedgePos, HedgeSecurity);
		}

		_pairDirection = 0;
		_baseEntryPrice = 0m;
		_hedgeEntryPrice = 0m;
	}
}