GitHub で見る

二重MA四段階戦略

この戦略はStockSharpの高レベルAPIを使用してMetaTraderのエキスパート「2MA_4Level」を再現します。中央値価格で計算された2つの平滑移動平均(SMMA)を使用して単一の銘柄を取引し、速い曲線と遅い曲線の間の5つの相対的なクロスゾーンを監視します。エントリーはポジションが開いていない場合にのみ許可され、すべての取引はpipベースのストップロスとテイクプロフィットオフセットで保護されます。

ロジック

  • 選択したローソク足シリーズで速いSMMAと遅いSMMAが計算されます(デフォルトは50と130期間)。
  • 完成したローソク足の前回と現在のSMMA値を評価してクロスオーバーを検出します。
  • クロスオーバーは遅いMAから構築された5つのしきい値に対してチェックされます:
    • 純粋な遅いMA(オフセットなし)、
    • 遅いMA + MostTopLevel pips、
    • 遅いMA + TopLevel pips、
    • 遅いMA - LowermostLevel pips、
    • 遅いMA - LowerLevel pips。
  • 速いMAがいずれかのしきい値を上回るとロングポジションが開かれます(フラット時)。いずれかのしきい値を下回るクロスはショートポジションを開きます。
  • ストップロスとテイクプロフィットレベルは、銘柄のpip値(Security.PriceStep)を使用してStartProtectionを通じて設定されます。

この戦略はポジションをピラミッド化しません:新しい取引はストップまたは目標によって前の取引が閉じられた後にのみ開くことができます。

パラメーター

パラメーター デフォルト値 説明
FastPeriod 50 速い平滑移動平均の長さ。SlowPeriodより小さくする必要があります。
SlowPeriod 130 遅い平滑移動平均の長さ。
MostTopLevel 500 最も広い強気/弱気確認のための上部オフセット(pips単位)。TopLevelより大きくする必要があります。
TopLevel 250 二次的な強気/弱気確認のための上部オフセット(pips単位)。
LowerLevel 250 二次的な弱気/強気確認のための下部オフセット(pips単位)。LowermostLevelより小さくする必要があります。
LowermostLevel 500 最も広い弱気/強気確認のための下部オフセット(pips単位)。
TakeProfitPips 55 エントリーからテイクプロフィットまでの距離(pips単位)。
StopLossPips 260 エントリーからストップロスまでの距離(pips単位)。
CandleType 15分時間軸 SMMAの計算とシグナル処理に使用するローソク足シリーズ。

実装の詳細

  • 中央値価格((High + Low) / 2)が両方のSMMAを供給し、PRICE_MEDIANを使用するMT5設定と一致します。
  • クロスオーバーテストは最後に完成したローソク足と前のローソク足を比較し、部分的に形成されたバーへの依存を排除します。
  • StartProtectionは起動時に一度だけストップロスとテイクプロフィットを設定するため、すべての注文は自動的に設定されたリスク制限を継承します。
  • 無効なパラメーターの組み合わせ(例:FastPeriod >= SlowPeriod)が指定された場合、戦略はOnStarted中に自己停止します。

使用上の注意

  1. PriceStepが定義された銘柄に戦略を設定してください。設定されていない場合、pip変換は1の値にフォールバックします。
  2. MT5のヘッジアカウントに適しています。StockSharpでは一度に一つのオープンポジションのみを確保することで同様に動作します。
  3. 最適化フック(SetCanOptimize)は両方のMA期間で有効になっており、StockSharpオプティマイザから直接パラメータースイープを実行できます。
  4. 戦略はストップロスとテイクプロフィットによる終了にのみ依存しているため、長時間のエクスポージャーを避けるために設定された距離が銘柄のボラティリティと一致していることを確認してください。

ファイル

  • CS/TwoMaFourLevelStrategy.cs – トレーディングロジックのC#実装。
  • README_ru.md – ロシア語ドキュメント。
  • README_zh.md – 中国語ドキュメント。
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>
/// Two smoothed moving average crossover strategy with level offsets.
/// </summary>
public class TwoMaFourLevelStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _mostTopLevel;
	private readonly StrategyParam<int> _topLevel;
	private readonly StrategyParam<int> _lowerLevel;
	private readonly StrategyParam<int> _lowermostLevel;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<DataType> _candleType;

	private SmoothedMovingAverage _fastMa = null!;
	private SmoothedMovingAverage _slowMa = null!;
	private decimal? _prevFast;
	private decimal? _prevSlow;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int MostTopLevel { get => _mostTopLevel.Value; set => _mostTopLevel.Value = value; }
	public int TopLevel { get => _topLevel.Value; set => _topLevel.Value = value; }
	public int LowerLevel { get => _lowerLevel.Value; set => _lowerLevel.Value = value; }
	public int LowermostLevel { get => _lowermostLevel.Value; set => _lowermostLevel.Value = value; }
	public int TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
	public int StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public TwoMaFourLevelStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Period of the fast smoothed MA", "Moving Averages")
			.SetOptimize(20, 150, 5);

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Period of the slow smoothed MA", "Moving Averages")
			.SetOptimize(60, 300, 5);

		_mostTopLevel = Param(nameof(MostTopLevel), 2)
			.SetGreaterThanZero()
			.SetDisplay("Extreme Upper Level", "Highest positive offset in points", "Levels");

		_topLevel = Param(nameof(TopLevel), 1)
			.SetGreaterThanZero()
			.SetDisplay("Upper Level", "Second positive offset in points", "Levels");

		_lowerLevel = Param(nameof(LowerLevel), 1)
			.SetGreaterThanZero()
			.SetDisplay("Lower Level", "Second negative offset in points", "Levels");

		_lowermostLevel = Param(nameof(LowermostLevel), 2)
			.SetGreaterThanZero()
			.SetDisplay("Extreme Lower Level", "Largest negative offset in points", "Levels");

		_takeProfitPips = Param(nameof(TakeProfitPips), 500)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pips)", "Distance to take profit", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 1000)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Distance to stop loss", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for analysis", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

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

		if (FastPeriod >= SlowPeriod)
		{
			this.LogError("FastPeriod must be less than SlowPeriod.");
			Stop();
			return;
		}

		if (MostTopLevel <= TopLevel)
		{
			this.LogError("MostTopLevel must be greater than TopLevel.");
			Stop();
			return;
		}

		if (LowerLevel >= LowermostLevel)
		{
			this.LogError("LowerLevel must be less than LowermostLevel.");
			Stop();
			return;
		}

		_fastMa = new SmoothedMovingAverage { Length = FastPeriod };
		_slowMa = new SmoothedMovingAverage { Length = SlowPeriod };

		var pip = Security?.PriceStep ?? 1m;

		StartProtection(
			takeProfit: new Unit(TakeProfitPips * pip, UnitTypes.Absolute),
			stopLoss: new Unit(StopLossPips * pip, UnitTypes.Absolute));

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

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

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

		if (_prevFast is null || _prevSlow is null)
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		var pip = Security?.PriceStep ?? 0.05m;
		var signal = GetSignal(fast, slow, _prevFast.Value, _prevSlow.Value, pip);

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

		_prevFast = fast;
		_prevSlow = slow;
	}

	private int GetSignal(decimal fast, decimal slow, decimal prevFast, decimal prevSlow, decimal pip)
	{
		if (IsCrossUp(prevFast, fast, prevSlow, slow, 0m) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, MostTopLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, TopLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, -LowermostLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, -LowerLevel * pip))
		{
			return 1;
		}

		if (IsCrossDown(prevFast, fast, prevSlow, slow, 0m) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, MostTopLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, TopLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, -LowermostLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, -LowerLevel * pip))
		{
			return -1;
		}

		return 0;
	}

	private static bool IsCrossUp(decimal prevFast, decimal fast, decimal prevSlow, decimal slow, decimal offset)
	{
		var prevSlowShifted = prevSlow + offset;
		var slowShifted = slow + offset;
		return prevFast <= prevSlowShifted && fast > slowShifted;
	}

	private static bool IsCrossDown(decimal prevFast, decimal fast, decimal prevSlow, decimal slow, decimal offset)
	{
		var prevSlowShifted = prevSlow + offset;
		var slowShifted = slow + offset;
		return prevFast >= prevSlowShifted && fast < slowShifted;
	}
}