GitHub で見る

Martingale ブレイクアウト戦略

概要

Martingale ブレイクアウト戦略は、MetaTrader エキスパート アドバイザー MartinGaleBreakout.mq5 の StockSharp 移植です。システム 異常に大きなブレイクアウトローソク足を待ち、ブレイクアウト方向に単一の成行注文を出します。オリジナルの EA 「マジックナンバー」を追跡してその位置を管理する場合、StockSharp の実装は戦略コンテキストに依存するため、動作は 戦略が単独で実行される場合も事実上同じです。

このアルゴリズムは、次の 2 つの中心的なアイデアに焦点を当てています。

  1. ブレイクアウト検出 – この戦略は、完成した各ローソク足のサイズを調べ、それをローソク足の平均範囲と比較します。 前の10本のキャンドル。現在のレンジが平均の 3 倍で、ローソク足が強い終値を迎えるとき。 ブレイクアウトの方向に応じて、取引シグナルが生成されます。
  2. Martingale スタイルの回復 – この戦略は変動損益を追跡します。未実現損益が 設定された損失しきい値により、すべてのオープンポジションが即座にクローズされ、次の利益目標が増加するため、次の取引が可能になります。 損失を取り戻そうとします。増加した目標が達成されると、しきい値は元の値にリセットされます。

ポートは、マージン用に予約された残高パーセンテージを含む、MQL5 コードからのすべての資金管理パラメータを保持します。 パーセンテージベースの損益目標と、回復段階でのテイクプロフィット距離を拡大する乗数。

取引ロジック

  1. 設定されたキャンドル シリーズを購読し、完成したキャンドルを待ちます。
  2. ローソク足の範囲 (High - Low) を計算し、前の 10 個の範囲を含む固定サイズのバッファーを維持して、 ブレイクアウト検出に使用される基準平均。
  3. ロングサイドとショートサイドの平均エントリー価格を追跡して、変動損益を計算します。未実現損益が 利益目標を達成するか、ストップロスのしきい値に違反した場合は、直ちにすべてのポジションを決済し、リカバリ状態をリセットします。 オリジナルの専門アドバイザー。
  4. ストラテジーがすでにポジションを保持している場合、または接続状態によって取引が許可されていない場合は、注文の発注をスキップします。
  5. 強気のブレイクアウトローソク足が現れたら、期待される利益が現在の目標と一致するように注文のサイズを調整します。テイクプロフィット 価格ステップの距離は、EA の TP_Points_Multiplier パラメータとまったく同様に、回復中に乗算されます。
  6. 計算された容量を機器の制限 (最小、最大、ステップ) に対して検証し、必要なマージンが確保されていることを確認します。 構成された残高割り当てまたは利用可能な無料資金を超えないでください。制約が尊重される場合は、 市場の買い注文。
  7. 弱気ブレイクアウトの場合も同じプロセスを繰り返し、代わりに市場売り注文を送信します。

これらのルールを組み合わせると、元の MetaTrader システムの動作が再現されます。これには、移行と終了も含まれます。 ストップロスイベント後のリカバリモード。

パラメーター

パラメータ 説明 デフォルト
TakeProfitPoints 価格ステップで表されたエントリー価格とテイクプロフィット価格の間の距離。 50
BalancePercentAvailable 1 回の取引で証拠金として確保できる口座残高の最大割合。 50
TakeProfitPercentOfBalance 現在の残高に対するパーセンテージで表される目標利益。 0.1
StopLossPercentOfBalance 現在の残高に対するパーセンテージで表されるストップロスのサイズ。 10
RecoveryStartFraction リカバリモードに切り替える前に使用されるストップロスの割合。 0.1
RecoveryPointsMultiplier 回復中にテイクプロフィットディスタンスに適用される乗数。 1
CandleType 戦略で使用されるローソク足データ ソース (タイムフレーム、ティック ローソク足など)。 15-minute time frame

追加の注意事項

  • 体積計算は、MetaTrader ヘルパー CalcLotWithTP を複製します。現在の水準に達するために必要なロットサイズを導き出します。 特定の価格変動に対する利益目標を計算し、その結果を商品の出来高ステップに正規化します。
  • 証拠金チェックは、CheckVolumeValue と同じ精神で実行され、MQL で使用される残高パーセント フィルターも実行されます。 バージョン。必要な証拠金が残高の許容シェアまたは報告された自由資金を超える場合、注文は拒否されます。 ポートフォリオ。
  • この戦略はポジションをフラット化する前にすべてのアクティブな注文をキャンセルするため、動作は CloseAllOrders ヘルパーと一致します。 元の専門アドバイザー。
  • 内部範囲バッファには 10 個の値のみが保存され、ソース EA の iHigh/iLow を反復するのと同等です。いいえ 過去 10 本のローソク足を超える履歴データが必要です。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Breakout strategy that detects abnormally large candles and enters in the breakout direction.
/// Uses a simple martingale recovery: after a losing trade, the next entry is taken more aggressively.
/// </summary>
public class MartingaleBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _lookback;
	private readonly StrategyParam<decimal> _breakoutMultiplier;
	private readonly StrategyParam<decimal> _takeProfitPct;
	private readonly StrategyParam<decimal> _stopLossPct;
	private readonly StrategyParam<DataType> _candleType;

	private readonly decimal[] _rangeBuffer = new decimal[10];
	private int _rangeBufferCount;
	private int _rangeBufferIndex;
	private decimal _rangeBufferSum;

	private decimal _entryPrice;
	private Sides? _entrySide;
	private bool _lastWasLoss;

	public int Lookback
	{
		get => _lookback.Value;
		set => _lookback.Value = value;
	}

	public decimal BreakoutMultiplier
	{
		get => _breakoutMultiplier.Value;
		set => _breakoutMultiplier.Value = value;
	}

	public decimal TakeProfitPct
	{
		get => _takeProfitPct.Value;
		set => _takeProfitPct.Value = value;
	}

	public decimal StopLossPct
	{
		get => _stopLossPct.Value;
		set => _stopLossPct.Value = value;
	}

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

	public MartingaleBreakoutStrategy()
	{
		_lookback = Param(nameof(Lookback), 10)
			.SetDisplay("Lookback", "Number of candles for average range", "General");

		_breakoutMultiplier = Param(nameof(BreakoutMultiplier), 3m)
			.SetDisplay("Breakout Mult", "Multiplier above avg range for breakout", "General");

		_takeProfitPct = Param(nameof(TakeProfitPct), 1m)
			.SetDisplay("Take Profit %", "Take profit as percentage of entry price", "Trading");

		_stopLossPct = Param(nameof(StopLossPct), 0.5m)
			.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle type", "General");
	}

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

		_rangeBufferCount = 0;
		_rangeBufferIndex = 0;
		_rangeBufferSum = 0m;
		_entryPrice = 0m;
		_entrySide = null;
		_lastWasLoss = false;

		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;

		var closePrice = candle.ClosePrice;

		// Check exit conditions first
		if (Position != 0 && _entryPrice > 0)
		{
			var tp = _lastWasLoss ? TakeProfitPct * 1.5m : TakeProfitPct;
			var sl = StopLossPct;

			if (_entrySide == Sides.Buy)
			{
				var pnlPct = (closePrice - _entryPrice) / _entryPrice * 100m;
				if (pnlPct >= tp || pnlPct <= -sl)
				{
					_lastWasLoss = pnlPct < 0;
					SellMarket();
					_entryPrice = 0;
					_entrySide = null;
					UpdateRangeStatistics(candle);
					return;
				}
			}
			else if (_entrySide == Sides.Sell)
			{
				var pnlPct = (_entryPrice - closePrice) / _entryPrice * 100m;
				if (pnlPct >= tp || pnlPct <= -sl)
				{
					_lastWasLoss = pnlPct < 0;
					BuyMarket();
					_entryPrice = 0;
					_entrySide = null;
					UpdateRangeStatistics(candle);
					return;
				}
			}
		}

		// Entry logic - only when flat
		if (Position == 0)
		{
			var range = candle.HighPrice - candle.LowPrice;

			if (_rangeBufferCount >= _rangeBuffer.Length)
			{
				var avgRange = _rangeBufferSum / _rangeBuffer.Length;

				if (range > avgRange * BreakoutMultiplier)
				{
					var body = candle.ClosePrice - candle.OpenPrice;

					if (body > 0 && body > range * 0.4m)
					{
						// Bullish breakout
						BuyMarket();
						_entryPrice = closePrice;
						_entrySide = Sides.Buy;
					}
					else if (body < 0 && Math.Abs(body) > range * 0.4m)
					{
						// Bearish breakout
						SellMarket();
						_entryPrice = closePrice;
						_entrySide = Sides.Sell;
					}
				}
			}
		}

		UpdateRangeStatistics(candle);
	}

	private void UpdateRangeStatistics(ICandleMessage candle)
	{
		var range = candle.HighPrice - candle.LowPrice;

		if (_rangeBufferCount < _rangeBuffer.Length)
		{
			_rangeBuffer[_rangeBufferIndex] = range;
			_rangeBufferSum += range;
			_rangeBufferCount++;
			_rangeBufferIndex = (_rangeBufferIndex + 1) % _rangeBuffer.Length;
			return;
		}

		_rangeBufferSum -= _rangeBuffer[_rangeBufferIndex];
		_rangeBuffer[_rangeBufferIndex] = range;
		_rangeBufferSum += range;
		_rangeBufferIndex = (_rangeBufferIndex + 1) % _rangeBuffer.Length;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		Array.Clear(_rangeBuffer);
		_rangeBufferCount = 0;
		_rangeBufferIndex = 0;
		_rangeBufferSum = 0m;
		_entryPrice = 0m;
		_entrySide = null;
		_lastWasLoss = false;

		base.OnReseted();
	}
}