GitHub で見る

ストップロス・テイクプロフィット戦略

このポートは MetaTrader のエキスパートアドバイザー「Stop Loss Take Profit」を複製します。戦略は口座がフラットのときにコインを投げ、選択した方向に成行注文をオープンします。各ポジションはすぐに pip ベースのストップロスとテイクプロフィット注文を受け取ります。ストップにヒットした場合、次のトレードはそのサイズを倍増させます(有価証券のボリューム制限によって制限される)。テイクプロフィットはボリュームを初期量にリセットします。このふるまいは StockSharp の高レベル API を使用しながら、元のマーチンゲール スタイルのポジションサイジングを反映します。

取引ロジック

  • 市場データ: CandleType パラメーター(デフォルト:1 分足時間軸)を使用して意思決定ポイントを駆動します。
  • エントリールール:
    • Position == 0 でエントリー注文が保留中でない場合、戦略は疑似ランダムなブール値を生成します。
    • trueBuyMarket(volume) でロングポジションをオープンします;falseSellMarket(volume) でショートをオープンします。
  • エグジットルール:
    • 保護的なストップロスとテイクプロフィット注文は、エントリーのフィルが受け取られるとすぐに配置されます。
    • ストップ出口は次のトレードのサイズを倍増させ、テイクプロフィットはそれをリセットします。
    • ストップまたはテイクプロフィットの距離が 0 に設定されている場合、それぞれの保護注文はスキップされます。
  • マネー管理:
    • InitialVolume はベース注文サイズを定義します。
    • 損失トレードの後、サイズは倍増しますが、その値が利用可能な場合は Security.MaxVolume に切り取られます。
    • ボリュームは注文が有効なままになるよう商品の VolumeStepMinVolumeMaxVolume に正規化されます。
  • Pip 処理:
    • デフォルトでは、戦略は商品の PriceStepDecimals から pip を推測します(5 桁の FX シンボルは 0.0001 にマップされます)。
    • 自動 pip サイズ検出を上書きするには PipSize を正の値に設定します。

パラメーター

名前 デフォルト 説明
CandleType 1 分ローソク足 コイン投げとエントリーに使用される時間軸。
StopLossPips 1 pips で表現されたストップロス距離。0 でストップを無効化。
TakeProfitPips 1 pips で表現されたテイクプロフィット距離。0 でテイクプロフィットを無効化。
InitialVolume 0.01 開始取引ボリューム。ストップロスイベント後に倍増、勝利後にリセット。
PipSize 0 (自動) 絶対価格単位でのオプション pip サイズ上書き。

使用上の注意

  • ロングとショートの両サイドで機能し、意図的に方向中立です。
  • 保護注文はポジションがクローズされるたびにキャンセルされ、古い注文を避けます。
  • ランダムジェネレーターは Environment.TickCount でシードされます。つまり、各セッションは異なるトレードシーケンスを生成します。
  • 追加のリスクコントロールなしに本番取引のためではなく、リスクレイヤリングとマーチンゲール行動のデモンストレーションに適しています。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Random direction strategy with pip-based stop loss and take profit that doubles the volume after losses.
/// </summary>
public class StopLossTakeProfitStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLossDistance;
	private readonly StrategyParam<decimal> _takeProfitDistance;
	private readonly StrategyParam<decimal> _initialVolume;

	private decimal _currentVolume;
	private decimal _entryPrice;
	private int _tradeCount;

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

	public decimal StopLossDistance
	{
		get => _stopLossDistance.Value;
		set => _stopLossDistance.Value = value;
	}

	public decimal TakeProfitDistance
	{
		get => _takeProfitDistance.Value;
		set => _takeProfitDistance.Value = value;
	}

	public decimal InitialVolume
	{
		get => _initialVolume.Value;
		set => _initialVolume.Value = value;
	}

	public StopLossTakeProfitStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for evaluating entries", "General");

		_stopLossDistance = Param(nameof(StopLossDistance), 5m)
			.SetDisplay("Stop Loss Distance", "Stop loss distance in price units", "Risk");

		_takeProfitDistance = Param(nameof(TakeProfitDistance), 5m)
			.SetDisplay("Take Profit Distance", "Take profit distance in price units", "Risk");

		_initialVolume = Param(nameof(InitialVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Initial Volume", "Starting order volume", "Risk");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_currentVolume = 0;
		_entryPrice = 0;
		_tradeCount = 0;
	}

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

		_currentVolume = InitialVolume;
		_entryPrice = 0m;

		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;

		// Check SL/TP for open position
		if (Position != 0 && _entryPrice > 0)
		{
			if (Position > 0)
			{
				var hitStop = StopLossDistance > 0 && candle.LowPrice <= _entryPrice - StopLossDistance;
				var hitTake = TakeProfitDistance > 0 && candle.HighPrice >= _entryPrice + TakeProfitDistance;

				if (hitStop)
				{
					SellMarket();
					HandleStopLoss();
					return;
				}

				if (hitTake)
				{
					SellMarket();
					HandleTakeProfit();
					return;
				}
			}
			else if (Position < 0)
			{
				var hitStop = StopLossDistance > 0 && candle.HighPrice >= _entryPrice + StopLossDistance;
				var hitTake = TakeProfitDistance > 0 && candle.LowPrice <= _entryPrice - TakeProfitDistance;

				if (hitStop)
				{
					BuyMarket();
					HandleStopLoss();
					return;
				}

				if (hitTake)
				{
					BuyMarket();
					HandleTakeProfit();
					return;
				}
			}
		}

		// Enter new position when flat
		if (Position == 0)
		{
			_tradeCount++;

			// Use candle direction as a deterministic signal
			if (candle.ClosePrice < candle.OpenPrice)
			{
				SellMarket();
			}
			else
			{
				BuyMarket();
			}

			_entryPrice = candle.ClosePrice;
		}
	}

	private void HandleStopLoss()
	{
		// Double volume on loss (martingale)
		_currentVolume *= 2m;

		var maxVol = Security?.MaxVolume;
		if (maxVol.HasValue && maxVol.Value > 0 && _currentVolume > maxVol.Value)
			_currentVolume = maxVol.Value;

		Volume = _currentVolume;
		_entryPrice = 0m;
	}

	private void HandleTakeProfit()
	{
		// Reset volume on profit
		_currentVolume = InitialVolume;
		Volume = _currentVolume;
		_entryPrice = 0m;
	}
}