GitHub で見る

ProMart MACD マルチンゲール戦略

この戦略は歴史的なMQLエキスパート MartGreg_1 / ProMart のStockSharpポートです。2つのMACDコンフィギュレーションを制御されたマルチンゲールポジションサイジングモデルと組み合わせます。プライマリMACDはモメンタムのローカル低値と高値を探し、セカンダリMACDは最近の傾きの方向を確認します。各クローズドトレード後、戦略はインジケーターパターンを再度フォローするか(最後のトレードが利益だった場合)、または即座に方向を反転し(損失後)、次の注文サイズを倍増させる可能性があります。

トレードロジック

  • シグナル
    • 選択されたローソク足シリーズに2つのMACDインジケーターを構築する:
      • MACD1(高速=5、低速=20、シグナル=3)はパターンディテクターとして機能する。
      • MACD2(高速=10、低速=15、シグナル=3)は短期傾きを確認する。
    • 以前の3つのMACD1値と以前の2つのMACD2値を使用して完成したローソク足でのみシグナルを評価する(1バー前を見るMQLロジックを反映)。
    • ロングセットアップ:MACD1がローカル谷(MACD1[t-1] > MACD1[t-2] < MACD1[t-3])を形成し、MACD2が上昇(MACD2[t-2] > MACD2[t-1])している。
    • ショートセットアップ:MACD2が下落している間にMACD1がローカルピークを形成する。
    • 最後のクローズドトレードが利益だった場合、戦略は次の有効なセットアップを待つ。損失トレード後は現在のMACDの形状に関係なく即座に反対方向を開き、元のマルチンゲール反転を再現する。
  • ポジション管理
    • トレードは成行注文で開かれ、各完成ローソク足で監視される。
    • ストップロスとテイクプロフィットレベルはエントリー価格から価格ポイントで計算される。ローソク足の高値/安値がいずれかのレベルに達した場合、ポジションは成行で閉じられてトレード結果が記録される。
    • ポジションを閉じた同じローソク足に新しいトレードは開かれない;戦略は新しいバーの最初のティックで行動していたMQLエキスパートと同様に次のバーを待つ。
  • マルチンゲールサイジング
    • 基本ボリュームはポートフォリオ資産をBalanceDividerで割り、銘柄のボリュームステップに整合させることで導出される(必要に応じてVolumeプロパティまたは銘柄の最小ボリュームにフォールバック)。
    • 損失トレード後、次のポジションは最大MaxDoublingCount回連続して前の注文ボリュームを倍増させることができる。利益は倍増カウンターをリセットする。
    • ボリュームは過大サイジングを避けるために常に銘柄の最大ボリュームで上限が設定される。

パラメーター

パラメーター 説明 デフォルト値
BalanceDivider 基本注文ボリュームを計算するためにポートフォリオ資産に適用する除数。 1000
MaxDoublingCount 損失後の連続ボリューム倍増の最大回数。 1
StopLossPoints 価格ポイントで測定したストップロス距離(PriceStep * StopLossPoints)。 500
TakeProfitPoints 価格ポイントで測定したテイクプロフィット距離。 1500
Macd1Fast / Macd1Slow / Macd1Signal 谷/ピークを検出するプライマリMACDの期間。 5 / 20 / 3
Macd2Fast / Macd2Slow / Macd2Signal セカンダリMACD傾きフィルターの期間。 10 / 15 / 3
CandleType ローソク足シリーズのデータタイプ(デフォルト:1分足)。 TimeSpan.FromMinutes(1).TimeFrame()

注意事項

  • 実装はStockSharpの例が完成したローソク足で動作するため、ローソク足の高値と安値を使用してバー内ストップロスとテイクプロフィットの約定を近似します。
  • ポートフォリオデータが利用できない場合、ポジションボリュームは戦略のVolumeプロパティまたは銘柄の最小ボリュームにフォールバックします。
  • Pythonバージョンはまだ提供されていません;C#戦略のみが含まれています。
  • 実際のトレーディングを有効にする前に必ず設定を履歴データで検証してください。マルチンゲールコンポーネントはリスクを大幅に増加させます。
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>
/// MACD-based martingale strategy. Uses two MACD indicators to detect turning points.
/// Doubles volume after a loss up to MaxDoublingCount times.
/// </summary>
public class ProMartMacdMartingaleStrategy : Strategy
{
	private readonly StrategyParam<int> _maxDoublingCount;
	private readonly StrategyParam<int> _macd1Fast;
	private readonly StrategyParam<int> _macd1Slow;
	private readonly StrategyParam<int> _macd2Fast;
	private readonly StrategyParam<int> _macd2Slow;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _macd1History = new();
	private readonly List<decimal> _macd2History = new();

	private decimal _entryPrice;
	private bool _inPosition;
	private bool _isLong;
	private bool _lastTradeWasLoss;
	private int _martingaleCounter;
	private decimal _currentVolume;

	public int MaxDoublingCount
	{
		get => _maxDoublingCount.Value;
		set => _maxDoublingCount.Value = value;
	}

	public int Macd1Fast
	{
		get => _macd1Fast.Value;
		set => _macd1Fast.Value = value;
	}

	public int Macd1Slow
	{
		get => _macd1Slow.Value;
		set => _macd1Slow.Value = value;
	}

	public int Macd2Fast
	{
		get => _macd2Fast.Value;
		set => _macd2Fast.Value = value;
	}

	public int Macd2Slow
	{
		get => _macd2Slow.Value;
		set => _macd2Slow.Value = value;
	}

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

	public ProMartMacdMartingaleStrategy()
	{
		_maxDoublingCount = Param(nameof(MaxDoublingCount), 2)
			.SetNotNegative()
			.SetDisplay("Max Doubling", "Maximum number of volume doublings after losses.", "Risk");

		_macd1Fast = Param(nameof(Macd1Fast), 5)
			.SetGreaterThanZero()
			.SetDisplay("MACD1 Fast", "Fast EMA period for the primary MACD.", "Signal");

		_macd1Slow = Param(nameof(Macd1Slow), 20)
			.SetGreaterThanZero()
			.SetDisplay("MACD1 Slow", "Slow EMA period for the primary MACD.", "Signal");

		_macd2Fast = Param(nameof(Macd2Fast), 10)
			.SetGreaterThanZero()
			.SetDisplay("MACD2 Fast", "Fast EMA period for the secondary MACD.", "Filter");

		_macd2Slow = Param(nameof(Macd2Slow), 15)
			.SetGreaterThanZero()
			.SetDisplay("MACD2 Slow", "Slow EMA period for the secondary MACD.", "Filter");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Data type used for signal generation.", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_macd1History.Clear();
		_macd2History.Clear();
		_entryPrice = 0;
		_inPosition = false;
		_isLong = false;
		_lastTradeWasLoss = false;
		_martingaleCounter = 0;
		_currentVolume = 0;
	}

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

		_macd1History.Clear();
		_macd2History.Clear();
		_inPosition = false;
		_isLong = false;
		_lastTradeWasLoss = false;
		_martingaleCounter = 0;
		_currentVolume = Volume;
		_entryPrice = 0;

		var macd1 = new MovingAverageConvergenceDivergence(
			new ExponentialMovingAverage { Length = Macd1Slow },
			new ExponentialMovingAverage { Length = Macd1Fast });

		var macd2 = new MovingAverageConvergenceDivergence(
			new ExponentialMovingAverage { Length = Macd2Slow },
			new ExponentialMovingAverage { Length = Macd2Fast });

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

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

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

		_macd1History.Add(macd1Value);
		_macd2History.Add(macd2Value);

		if (_macd1History.Count > 4)
			_macd1History.RemoveAt(0);
		if (_macd2History.Count > 3)
			_macd2History.RemoveAt(0);

		// Check exit for open position
		if (_inPosition)
		{
			var pnl = _isLong
				? candle.ClosePrice - _entryPrice
				: _entryPrice - candle.ClosePrice;

			// Detect reversal to exit
			var shouldExit = false;
			if (_macd1History.Count >= 3)
			{
				var m0 = _macd1History[^1];
				var m1 = _macd1History[^2];
				var m2 = _macd1History[^3];

				if (_isLong && m0 < m1 && m1 > m2)
					shouldExit = true;
				else if (!_isLong && m0 > m1 && m1 < m2)
					shouldExit = true;
			}

			if (shouldExit)
			{
				if (_isLong)
					SellMarket();
				else
					BuyMarket();

				_lastTradeWasLoss = pnl < 0;
				if (_lastTradeWasLoss && _martingaleCounter < MaxDoublingCount)
				{
					_currentVolume *= 2;
					_martingaleCounter++;
				}
				else
				{
					_currentVolume = Volume;
					_martingaleCounter = 0;
				}

				_inPosition = false;
				return;
			}
		}

		// Check entry
		if (!_inPosition && _macd1History.Count >= 3 && _macd2History.Count >= 2)
		{
			var m0 = _macd1History[^1];
			var m1 = _macd1History[^2];
			var m2 = _macd1History[^3];
			var f0 = _macd2History[^1];
			var f1 = _macd2History[^2];

			// MACD1 turns up from bottom + MACD2 confirms
			var buySignal = m0 > m1 && m1 < m2 && f1 > f0;
			var sellSignal = m0 < m1 && m1 > m2 && f1 < f0;

			if (buySignal && Position <= 0)
			{
				BuyMarket();
				_inPosition = true;
				_isLong = true;
				_entryPrice = candle.ClosePrice;
			}
			else if (sellSignal && Position >= 0)
			{
				SellMarket();
				_inPosition = true;
				_isLong = false;
				_entryPrice = candle.ClosePrice;
			}
		}
	}
}