GitHub で見る

Stopreversal トレーリング戦略

Stopreversal トレーリング戦略は、MT5エキスパートExp_Stopreversal.mq5を再現したものです。Stopreversal カスタムインジケーターを使用して、選択したローソク足価格の周辺にダイナミックなトレーリングストップラインを構築します。価格がこのトレーリングラインを上方向に突き抜けると、戦略はこれを強気のリバーサルとして扱い、オプションでショートのエクスポージャーを閉じ、新しいロングポジションを建てます。下方向への突き抜けは対称的な弱気アクションを生成します。シグナルは、元のエキスパートアドバイザーの動作に合わせるために、設定可能な数の閉じたバーだけ遅延させることができます。

詳細

  • エントリーロジック: 価格がアダプティブトレーリングストップを越えたときに生成されるStoprevesalインジケーターの矢印に反応します。
  • ロング/ショート: 両方向がサポートされており、ロングまたはショートエントリーを有効にする独立したトグルがあります。
  • エグジットロジック: 逆方向のStoprevesalシグナルが既存のポジションを閉じることができます; 保護的なストップロスとテイクプロフィットレベルも利用可能です。
  • ストップ: 価格ステップでの静的ストップロスとテイクプロフィット、プラスインジケーター駆動のリバーサル。
  • データソース: 任意の時間軸; デフォルトは4時間時間軸ローソク足を使用し、元のエキスパートのマルチタイムフレームコールを反映します。
  • シグナル遅延: SignalBarパラメーターは、指定した数の完成したバーだけ注文実行を遅延させます(デフォルト1バー)。
  • リスク管理: オプションのハードストップは証券価格ステップで表現されます; ポジション保護サービスは開始時に有効化されます。
  • インジケーターパラメーター: トレーリングオフセットNpipsは価格とストップの間の距離を制御します; PriceModeはトレーリングストップが使用するローソク足価格を選択します。
  • デフォルト値:
    • Volume = 1
    • StopLossSteps = 1000
    • TakeProfitSteps = 2000
    • BuyPositionOpen = true
    • SellPositionOpen = true
    • BuyPositionClose = true
    • SellPositionClose = true
    • Npips = 0.004
    • PriceMode = Close
    • SignalBar = 1

パラメーター

パラメーター 説明
CandleType Stopreversal計算と取引の両方に使用するローソク足サブスクリプション。デフォルトは4時間時間軸。
Volume 新しいポジションに入る際に送信する基本注文サイズ。
StopLossSteps エントリーからストップロスまでの距離(価格ステップ単位); 0に設定して無効化。
TakeProfitSteps エントリーからテイクプロフィットまでの距離(価格ステップ単位); 0に設定して無効化。
BuyPositionOpen 強気シグナル発生時のロングポジション開設を有効化します。
SellPositionOpen 弱気シグナル発生時のショートポジション開設を有効化します。
BuyPositionClose 弱気シグナルを受信したとき、既存のロングポジションを閉じます。
SellPositionClose 強気シグナルを受信したとき、既存のショートポジションを閉じます。
Npips リバーサル距離を広げたり狭めたりするためにトレーリングストップに適用する比率乗数。
PriceMode 適用される価格バリアント(終値、始値、高値、安値、中央値、典型値、加重値、単純平均、四価平均、トレンドフォロー、またはDemark)。
SignalBar シグナルに反応する前に待機する完全に閉じたローソク足の数(MT5パラメーターに対応)。

フィルター

  • カテゴリ: トレンドフォローのリバーサル
  • 方向: 双方向
  • インジケーター: Stopreversal(ATRバックのトレーリングストップ)
  • ストップ: 静的ストップロスとテイクプロフィット、オプション
  • 時間軸: 設定可能(デフォルトH4)
  • 季節性: なし
  • ニューラルネットワーク: いいえ
  • ダイバージェンス: いいえ
  • 複雑さ: カスタムトレーリングロジックにより中程度
  • リスクレベル: ストップ距離とトレーリングオフセットで調整可能
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>
/// Stopreversal indicator based trailing stop strategy.
/// </summary>
public class StopreversalTrailingStrategy : Strategy
{
	private readonly StrategyParam<int> _atrPeriod;

	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _stopLossSteps;
	private readonly StrategyParam<int> _takeProfitSteps;
	private readonly StrategyParam<bool> _buyPositionOpen;
	private readonly StrategyParam<bool> _sellPositionOpen;
	private readonly StrategyParam<bool> _buyPositionClose;
	private readonly StrategyParam<bool> _sellPositionClose;
	private readonly StrategyParam<decimal> _npips;
	private readonly StrategyParam<AppliedPriceModes> _priceMode;
	private readonly StrategyParam<int> _signalBar;

	private readonly List<SignalInfo> _signals = new();

	private AverageTrueRange _atr = null!;
	private decimal? _previousStopLevel;
	private decimal? _previousPrice;

	private decimal? _longStop;
	private decimal? _longTake;
	private decimal? _shortStop;
	private decimal? _shortTake;

	/// <summary>
	/// Initializes a new instance of <see cref="StopreversalTrailingStrategy"/>.
	/// </summary>
	public StopreversalTrailingStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Stopreversal timeframe", "General");

		_atrPeriod = Param(nameof(AtrPeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR lookback for trailing stop", "Indicator");

		_stopLossSteps = Param(nameof(StopLossSteps), 10)
		.SetNotNegative()
		.SetDisplay("Stop Loss Steps", "Stop loss distance in price steps", "Risk")
		;

		_takeProfitSteps = Param(nameof(TakeProfitSteps), 20)
		.SetNotNegative()
		.SetDisplay("Take Profit Steps", "Take profit distance in price steps", "Risk")
		;

		_buyPositionOpen = Param(nameof(BuyPositionOpen), true)
		.SetDisplay("Open Long", "Allow opening long positions", "Trading");

		_sellPositionOpen = Param(nameof(SellPositionOpen), true)
		.SetDisplay("Open Short", "Allow opening short positions", "Trading");

		_buyPositionClose = Param(nameof(BuyPositionClose), true)
		.SetDisplay("Close Long", "Close long positions on sell signals", "Trading");

		_sellPositionClose = Param(nameof(SellPositionClose), true)
		.SetDisplay("Close Short", "Close short positions on buy signals", "Trading");

		_npips = Param(nameof(Npips), 0.004m)
		.SetGreaterThanZero()
		.SetDisplay("Trailing Offset", "Fractional offset applied to the stop line", "Indicator")
		;

		_priceMode = Param(nameof(PriceMode), AppliedPriceModes.Close)
		.SetDisplay("Applied Price", "Price source used by the trailing stop", "Indicator");

		_signalBar = Param(nameof(SignalBar), 1)
		.SetNotNegative()
		.SetDisplay("Signal Bar", "Bar delay before acting on a signal", "Indicator")
		;
	}

	/// <summary>
	/// Candle subscription type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// ATR period used for the trailing stop calculation.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Stop loss distance measured in price steps.
	/// </summary>
	public int StopLossSteps
	{
		get => _stopLossSteps.Value;
		set => _stopLossSteps.Value = value;
	}

	/// <summary>
	/// Take profit distance measured in price steps.
	/// </summary>
	public int TakeProfitSteps
	{
		get => _takeProfitSteps.Value;
		set => _takeProfitSteps.Value = value;
	}

	/// <summary>
	/// Enable long entries.
	/// </summary>
	public bool BuyPositionOpen
	{
		get => _buyPositionOpen.Value;
		set => _buyPositionOpen.Value = value;
	}

	/// <summary>
	/// Enable short entries.
	/// </summary>
	public bool SellPositionOpen
	{
		get => _sellPositionOpen.Value;
		set => _sellPositionOpen.Value = value;
	}

	/// <summary>
	/// Close long positions on short signals.
	/// </summary>
	public bool BuyPositionClose
	{
		get => _buyPositionClose.Value;
		set => _buyPositionClose.Value = value;
	}

	/// <summary>
	/// Close short positions on long signals.
	/// </summary>
	public bool SellPositionClose
	{
		get => _sellPositionClose.Value;
		set => _sellPositionClose.Value = value;
	}

	/// <summary>
	/// Fractional offset used by the trailing stop calculation.
	/// </summary>
	public decimal Npips
	{
		get => _npips.Value;
		set => _npips.Value = value;
	}

	/// <summary>
	/// Price source used when computing the trailing level.
	/// </summary>
	public AppliedPriceModes PriceMode
	{
		get => _priceMode.Value;
		set => _priceMode.Value = value;
	}

	/// <summary>
	/// Number of bars to delay before reacting to a signal.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_signals.Clear();
		_previousStopLevel = null;
		_previousPrice = null;
		_longStop = null;
		_longTake = null;
		_shortStop = null;
		_shortTake = null;
	}

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

		_atr = new AverageTrueRange
		{
			Length = AtrPeriod
		};

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

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

		// no protection
	}

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

		UpdateStops(candle);

		var price = GetAppliedPrice(candle);
		var prevStop = _previousStopLevel ?? price * (1m - Npips);
		var prevPrice = _previousPrice ?? price;
		var hasPrev = _previousStopLevel.HasValue && _previousPrice.HasValue;

		var stop = CalculateStop(price, prevPrice, prevStop);

		var buySignal = hasPrev && price > stop && prevPrice < prevStop && prevStop != 0m;
		var sellSignal = hasPrev && price < stop && prevPrice > prevStop && prevStop != 0m;

		_previousPrice = price;
		_previousStopLevel = stop;

		_signals.Add(new SignalInfo
		{
			BuySignal = buySignal,
			SellSignal = sellSignal,
			ClosePrice = candle.ClosePrice,
			Time = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime
		});

		TrimSignals();

		if (_signals.Count <= SignalBar)
		return;

		var index = _signals.Count - 1 - SignalBar;
		if (index < 0)
		return;

		var signal = _signals[index];
		var allowTrading = _atr.IsFormed;

		ExecuteSignal(signal, allowTrading);
	}

	private void ExecuteSignal(SignalInfo signal, bool allowTrading)
	{
		if (SellPositionClose && signal.BuySignal && Position < 0)
		{
			BuyMarket();
			ResetShortStops();
		}

		if (BuyPositionClose && signal.SellSignal && Position > 0)
		{
			SellMarket();
			ResetLongStops();
		}

		if (!allowTrading || Position != 0)
		return;

		if (BuyPositionOpen && signal.BuySignal)
		{
			if (Volume > 0)
			{
				BuyMarket();
				ResetShortStops();
				SetLongStops(signal.ClosePrice);
			}
		}
		else if (SellPositionOpen && signal.SellSignal)
		{
			if (Volume > 0)
			{
				SellMarket();
				ResetLongStops();
				SetShortStops(signal.ClosePrice);
			}
		}
	}

	private void UpdateStops(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_longStop is decimal longStop && candle.LowPrice <= longStop)
			{
				SellMarket();
				ResetLongStops();
				return;
			}

			if (_longTake is decimal longTake && candle.HighPrice >= longTake)
			{
				SellMarket();
				ResetLongStops();
			}
		}
		else if (Position < 0)
		{
			if (_shortStop is decimal shortStop && candle.HighPrice >= shortStop)
			{
				BuyMarket();
				ResetShortStops();
				return;
			}

			if (_shortTake is decimal shortTake && candle.LowPrice <= shortTake)
			{
				BuyMarket();
				ResetShortStops();
			}
		}
	}

	private void TrimSignals()
	{
		var max = Math.Max(SignalBar + 5, 10);
		while (_signals.Count > max)
		{
			try { _signals.RemoveAt(0); } catch { break; }
		}
	}

	private decimal CalculateStop(decimal price, decimal prevPrice, decimal prevStop)
	{
		var offset = Npips;

		if (price == prevStop)
		return prevStop;

		if (prevPrice < prevStop && price < prevStop)
		return Math.Min(prevStop, price * (1m + offset));

		if (prevPrice > prevStop && price > prevStop)
		return Math.Max(prevStop, price * (1m - offset));

		return price > prevStop
		? price * (1m - offset)
		: price * (1m + offset);
	}

	private void SetLongStops(decimal basePrice)
	{
		var step = GetEffectiveStep();

		_longStop = StopLossSteps > 0 ? basePrice - step * StopLossSteps : null;
		_longTake = TakeProfitSteps > 0 ? basePrice + step * TakeProfitSteps : null;
	}

	private void SetShortStops(decimal basePrice)
	{
		var step = GetEffectiveStep();

		_shortStop = StopLossSteps > 0 ? basePrice + step * StopLossSteps : null;
		_shortTake = TakeProfitSteps > 0 ? basePrice - step * TakeProfitSteps : null;
	}

	private void ResetLongStops()
	{
		_longStop = null;
		_longTake = null;
	}

	private void ResetShortStops()
	{
		_shortStop = null;
		_shortTake = null;
	}

	private decimal GetEffectiveStep()
	{
		var step = Security?.PriceStep;
		if (step is decimal s && s > 0)
		return s;

		return 0.0001m;
	}

	private decimal GetAppliedPrice(ICandleMessage candle)
	{
		var open = candle.OpenPrice;
		var close = candle.ClosePrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;

		return PriceMode switch
		{
			AppliedPriceModes.Close => close,
			AppliedPriceModes.Open => open,
			AppliedPriceModes.High => high,
			AppliedPriceModes.Low => low,
			AppliedPriceModes.Median => (high + low) / 2m,
			AppliedPriceModes.Typical => (close + high + low) / 3m,
			AppliedPriceModes.Weighted => (2m * close + high + low) / 4m,
			AppliedPriceModes.Simple => (open + close) / 2m,
			AppliedPriceModes.Quarter => (open + close + high + low) / 4m,
			AppliedPriceModes.TrendFollow0 => close > open ? high : close < open ? low : close,
			AppliedPriceModes.TrendFollow1 => close > open ? (high + close) / 2m : close < open ? (low + close) / 2m : close,
			AppliedPriceModes.Demark => CalculateDemarkPrice(open, high, low, close),
			_ => close
		};
	}

	private static decimal CalculateDemarkPrice(decimal open, decimal high, decimal low, decimal close)
	{
		var result = high + low + close;

		if (close < open)
		result = (result + low) / 2m;
		else if (close > open)
		result = (result + high) / 2m;
		else
		result = (result + close) / 2m;

		return ((result - low) + (result - high)) / 2m;
	}

	private sealed class SignalInfo
	{
		public bool BuySignal { get; init; }
		public bool SellSignal { get; init; }
		public decimal ClosePrice { get; init; }
		public DateTimeOffset Time { get; init; }
	}

	/// <summary>
	/// Available price calculation modes.
	/// </summary>
	public enum AppliedPriceModes
	{
		/// <summary>
		/// Closing price.
		/// </summary>
		Close,

		/// <summary>
		/// Opening price.
		/// </summary>
		Open,

		/// <summary>
		/// Highest price.
		/// </summary>
		High,

		/// <summary>
		/// Lowest price.
		/// </summary>
		Low,

		/// <summary>
		/// Median price (high + low) / 2.
		/// </summary>
		Median,

		/// <summary>
		/// Typical price (close + high + low) / 3.
		/// </summary>
		Typical,

		/// <summary>
		/// Weighted close price (2 * close + high + low) / 4.
		/// </summary>
		Weighted,

		/// <summary>
		/// Simple average of open and close.
		/// </summary>
		Simple,

		/// <summary>
		/// Average of open, close, high and low.
		/// </summary>
		Quarter,

		/// <summary>
		/// Trend follow price variant 0.
		/// </summary>
		TrendFollow0,

		/// <summary>
		/// Trend follow price variant 1.
		/// </summary>
		TrendFollow1,

		/// <summary>
		/// Demark price formula.
		/// </summary>
		Demark
	}
}