GitHub で見る

Kolier SuperTrend X2戦略

概要

この戦略は、異なる時間軸で動作する2つのSuperTrendフィルターを組み合わせることで、オリジナルのMetaTraderエキスパートを再現します。上位時間軸のSuperTrendが市場の支配的なバイアスを定義し、下位時間軸のSuperTrendがエントリーを発動する同期ブレイクアウトを探します。StockSharpポートは高レベルAPIバインディングを使用するため、インジケーターはロウソク足の更新を直接受け取り、独自の履歴を維持します。

トレードロジック

  • トレンドフィルター: 上位時間軸のSuperTrendが上昇または下降トレンドを確認する必要があります。確認遅延はTrendSignalShiftで制御され、モード(TrendMode)は単一バー(NewWay)または2本連続バー(その他全モード)のどちらが必要かを定義します。
  • エントリーシグナル: 下位時間軸のSuperTrendは、現在のトレンドフィルターと一致した方向転換を待ちます。EntrySignalShiftはシグナルを遅延させて完全に閉じたバーに依拠し、EntryModeは戦略が即座に反応するか(NewWay)確認済みリバーサル後にのみ反応するか(その他モード)を制御します。
  • ロングエントリー: EnableBuyEntriestrueで、トレンドフィルターが強気であり、選択したモードに従ってエントリーSuperTrendが強気に転換したときに許可されます。既存のショートポジションを先にクローズし、その後Volume + |Position|のボリュームでロングポジションをオープンします。
  • ショートエントリー: EnableSellEntriestrueで、トレンドフィルターが弱気であり、エントリーSuperTrendが弱気に転換したときに許可されます。ショートエントリー前に既存ロングポジションをカバーします。
  • エグジット:
    • 上位時間軸での反転はロング(CloseBuyOnTrendFlip)またはショート(CloseSellOnTrendFlip)をクローズします。
    • エントリー時間軸の転換も、CloseBuyOnEntryFlip/CloseSellOnEntryFlipが有効な場合にポジションをクローズできます。
    • オプションの固定ストップ(StopLossPointsTakeProfitPoints)はSecurity.PriceStepの倍数として適用されます。

インジケーター

  • StockSharp SuperTrend の2インスタンス(1つはトレンド時間軸用、1つはエントリー用)。

パラメーター

  • TrendCandleType – トレンドフィルターの時間軸。
  • EntryCandleType – エントリーシグナルの時間軸。
  • TrendAtrPeriodTrendAtrMultiplier – トレンドSuperTrendのATR設定。
  • EntryAtrPeriodEntryAtrMultiplier – エントリーSuperTrendのATR設定。
  • TrendModeEntryMode – 確認モード:NewWayは1バー後に反応し、その他モードは2本連続バーが必要(このポートではVisualとExpertSignalはクラシックSuperTrendと同様に動作)。
  • TrendSignalShiftEntrySignalShift – インジケーター値を使用する前に待機する閉じたバーの数。
  • EnableBuyEntriesEnableSellEntries – ロング/ショートトレードを有効化。
  • CloseBuyOnTrendFlipCloseSellOnTrendFlip – トレンドフィルターからの逆シグナルでエグジット。
  • CloseBuyOnEntryFlipCloseSellOnEntryFlip – エントリー時間軸からの逆シグナルでエグジット。
  • StopLossPointsTakeProfitPoints – 保護注文の価格ステップ単位の距離(0で無効化)。
  • Volume – 新規ポジションのベースボリューム。
  • Slippage – ソースエキスパートとの互換性のために保持されたプレースホルダーパラメーター。

注記

  • このポートはStockSharpの高レベルワークフローに焦点を当てています:ロウソク足はSubscribeCandlesで購読され、インジケーターはBindExでバインドされ、戦略は最小限の状態(トレンド方向、ストップレベル)のみを保持します。
  • StartProtection()は標準StockSharpポジション保護ヘルパーを有効化するために1回呼び出されます。
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>
/// Kolier SuperTrend X2 strategy that combines higher timeframe trend filtering and lower timeframe entries.
/// </summary>
public class KolierSuperTrendX2Strategy : Strategy
{
	private readonly StrategyParam<DataType> _trendCandleType;
	private readonly StrategyParam<DataType> _entryCandleType;
	private readonly StrategyParam<int> _trendAtrPeriod;
	private readonly StrategyParam<decimal> _trendAtrMultiplier;
	private readonly StrategyParam<int> _entryAtrPeriod;
	private readonly StrategyParam<decimal> _entryAtrMultiplier;
	private readonly StrategyParam<KolierTrendModes> _trendMode;
	private readonly StrategyParam<KolierTrendModes> _entryMode;
	private readonly StrategyParam<int> _trendSignalShift;
	private readonly StrategyParam<int> _entrySignalShift;
	private readonly StrategyParam<bool> _enableBuyEntries;
	private readonly StrategyParam<bool> _enableSellEntries;
	private readonly StrategyParam<bool> _closeBuyOnTrendFlip;
	private readonly StrategyParam<bool> _closeSellOnTrendFlip;
	private readonly StrategyParam<bool> _closeBuyOnEntryFlip;
	private readonly StrategyParam<bool> _closeSellOnEntryFlip;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;

	private SuperTrend _trendSuperTrend = null!;
	private SuperTrend _entrySuperTrend = null!;

	private readonly List<int> _trendDirections = new();
	private readonly List<int> _entryDirections = new();

	private int _trendDirection;
	private decimal? _stopLossPrice;
	private decimal? _takeProfitPrice;

	/// <summary>
	/// Initializes a new instance of <see cref="KolierSuperTrendX2Strategy"/>.
	/// </summary>
	public KolierSuperTrendX2Strategy()
	{

		_trendCandleType = Param(nameof(TrendCandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Trend Timeframe", "Timeframe for trend SuperTrend", "Data");

		_entryCandleType = Param(nameof(EntryCandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Entry Timeframe", "Timeframe for entry SuperTrend", "Data");

		_trendAtrPeriod = Param(nameof(TrendAtrPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Trend ATR Period", "ATR period for trend SuperTrend", "Trend");

		_trendAtrMultiplier = Param(nameof(TrendAtrMultiplier), 3m)
			.SetGreaterThanZero()
			.SetDisplay("Trend ATR Multiplier", "ATR multiplier for trend SuperTrend", "Trend");

		_entryAtrPeriod = Param(nameof(EntryAtrPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Entry ATR Period", "ATR period for entry SuperTrend", "Entry");

		_entryAtrMultiplier = Param(nameof(EntryAtrMultiplier), 3m)
			.SetGreaterThanZero()
			.SetDisplay("Entry ATR Multiplier", "ATR multiplier for entry SuperTrend", "Entry");

		_trendMode = Param(nameof(TrendMode), KolierTrendModes.NewWay)
			.SetDisplay("Trend Mode", "Mode used for trend confirmation", "Trend");

		_entryMode = Param(nameof(EntryMode), KolierTrendModes.NewWay)
			.SetDisplay("Entry Mode", "Mode used for entry detection", "Entry");

		_trendSignalShift = Param(nameof(TrendSignalShift), 1)
			.SetRange(0, 10)
			.SetDisplay("Trend Signal Shift", "Bars to delay trend confirmation", "Trend");

		_entrySignalShift = Param(nameof(EntrySignalShift), 1)
			.SetRange(0, 10)
			.SetDisplay("Entry Signal Shift", "Bars to delay entry confirmation", "Entry");

		_enableBuyEntries = Param(nameof(EnableBuyEntries), true)
			.SetDisplay("Enable Long Entries", "Allow opening long positions", "Trading");

		_enableSellEntries = Param(nameof(EnableSellEntries), true)
			.SetDisplay("Enable Short Entries", "Allow opening short positions", "Trading");

		_closeBuyOnTrendFlip = Param(nameof(CloseBuyOnTrendFlip), true)
			.SetDisplay("Close Long On Trend Flip", "Close longs when higher timeframe turns bearish", "Exits");

		_closeSellOnTrendFlip = Param(nameof(CloseSellOnTrendFlip), true)
			.SetDisplay("Close Short On Trend Flip", "Close shorts when higher timeframe turns bullish", "Exits");

		_closeBuyOnEntryFlip = Param(nameof(CloseBuyOnEntryFlip), false)
			.SetDisplay("Close Long On Entry Flip", "Close longs when entry SuperTrend flips down", "Exits");

		_closeSellOnEntryFlip = Param(nameof(CloseSellOnEntryFlip), false)
			.SetDisplay("Close Short On Entry Flip", "Close shorts when entry SuperTrend flips up", "Exits");

		_stopLossPoints = Param(nameof(StopLossPoints), 1000m)
			.SetDisplay("Stop Loss (steps)", "Stop loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000m)
			.SetDisplay("Take Profit (steps)", "Take profit distance in price steps", "Risk");
	}


	/// <summary>
	/// Candle type used for the trend filter.
	/// </summary>
	public DataType TrendCandleType
	{
		get => _trendCandleType.Value;
		set => _trendCandleType.Value = value;
	}

	/// <summary>
	/// Candle type used for entry signals.
	/// </summary>
	public DataType EntryCandleType
	{
		get => _entryCandleType.Value;
		set => _entryCandleType.Value = value;
	}

	/// <summary>
	/// ATR period for the trend SuperTrend filter.
	/// </summary>
	public int TrendAtrPeriod
	{
		get => _trendAtrPeriod.Value;
		set => _trendAtrPeriod.Value = value;
	}

	/// <summary>
	/// ATR multiplier for the trend SuperTrend filter.
	/// </summary>
	public decimal TrendAtrMultiplier
	{
		get => _trendAtrMultiplier.Value;
		set => _trendAtrMultiplier.Value = value;
	}

	/// <summary>
	/// ATR period for the entry SuperTrend filter.
	/// </summary>
	public int EntryAtrPeriod
	{
		get => _entryAtrPeriod.Value;
		set => _entryAtrPeriod.Value = value;
	}

	/// <summary>
	/// ATR multiplier for the entry SuperTrend filter.
	/// </summary>
	public decimal EntryAtrMultiplier
	{
		get => _entryAtrMultiplier.Value;
		set => _entryAtrMultiplier.Value = value;
	}

	/// <summary>
	/// Confirmation mode for the trend timeframe.
	/// </summary>
	public KolierTrendModes TrendMode
	{
		get => _trendMode.Value;
		set => _trendMode.Value = value;
	}

	/// <summary>
	/// Confirmation mode for the entry timeframe.
	/// </summary>
	public KolierTrendModes EntryMode
	{
		get => _entryMode.Value;
		set => _entryMode.Value = value;
	}

	/// <summary>
	/// Bars to shift when confirming trend direction.
	/// </summary>
	public int TrendSignalShift
	{
		get => _trendSignalShift.Value;
		set => _trendSignalShift.Value = value;
	}

	/// <summary>
	/// Bars to shift when confirming entry signals.
	/// </summary>
	public int EntrySignalShift
	{
		get => _entrySignalShift.Value;
		set => _entrySignalShift.Value = value;
	}

	/// <summary>
	/// Allow opening long positions.
	/// </summary>
	public bool EnableBuyEntries
	{
		get => _enableBuyEntries.Value;
		set => _enableBuyEntries.Value = value;
	}

	/// <summary>
	/// Allow opening short positions.
	/// </summary>
	public bool EnableSellEntries
	{
		get => _enableSellEntries.Value;
		set => _enableSellEntries.Value = value;
	}

	/// <summary>
	/// Close long positions when the higher timeframe turns bearish.
	/// </summary>
	public bool CloseBuyOnTrendFlip
	{
		get => _closeBuyOnTrendFlip.Value;
		set => _closeBuyOnTrendFlip.Value = value;
	}

	/// <summary>
	/// Close short positions when the higher timeframe turns bullish.
	/// </summary>
	public bool CloseSellOnTrendFlip
	{
		get => _closeSellOnTrendFlip.Value;
		set => _closeSellOnTrendFlip.Value = value;
	}

	/// <summary>
	/// Close long positions when the entry SuperTrend flips bearish.
	/// </summary>
	public bool CloseBuyOnEntryFlip
	{
		get => _closeBuyOnEntryFlip.Value;
		set => _closeBuyOnEntryFlip.Value = value;
	}

	/// <summary>
	/// Close short positions when the entry SuperTrend flips bullish.
	/// </summary>
	public bool CloseSellOnEntryFlip
	{
		get => _closeSellOnEntryFlip.Value;
		set => _closeSellOnEntryFlip.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price steps.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in price steps.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, EntryCandleType);

		if (!TrendCandleType.Equals(EntryCandleType))
		{
			yield return (Security, TrendCandleType);
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_trendDirections.Clear();
		_entryDirections.Clear();
		_trendDirection = 0;
		_stopLossPrice = null;
		_takeProfitPrice = null;
	}

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

		_trendSuperTrend = new SuperTrend
		{
			Length = TrendAtrPeriod,
			Multiplier = TrendAtrMultiplier
		};

		_entrySuperTrend = new SuperTrend
		{
			Length = EntryAtrPeriod,
			Multiplier = EntryAtrMultiplier
		};

		var entrySubscription = SubscribeCandles(EntryCandleType);

		if (TrendCandleType.Equals(EntryCandleType))
		{
			entrySubscription
				.BindEx(_trendSuperTrend, ProcessTrendCandle)
				.BindEx(_entrySuperTrend, ProcessEntryCandle)
				.Start();
		}
		else
		{
			entrySubscription
				.BindEx(_entrySuperTrend, ProcessEntryCandle)
				.Start();

			var trendSubscription = SubscribeCandles(TrendCandleType);
			trendSubscription
				.BindEx(_trendSuperTrend, ProcessTrendCandle)
				.Start();
		}

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

	}

	private void ProcessTrendCandle(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!value.IsFormed || value is not SuperTrendIndicatorValue trendValue)
			return;

		if (!_trendSuperTrend.IsFormed)
			return;

		var direction = trendValue.IsUpTrend ? 1 : -1;
		if (direction == 0)
			return;

		UpdateHistory(_trendDirections, direction, TrendSignalShift + 3);

		var current = GetDirection(_trendDirections, TrendSignalShift);
		var previous = GetDirection(_trendDirections, TrendSignalShift + 1);

		if (current is null || previous is null)
			return;

		switch (TrendMode)
		{
			case KolierTrendModes.NewWay:
				_trendDirection = current.Value;
				break;
			default:
				if (current == previous)
				{
					_trendDirection = current.Value;
				}
				break;
		}
	}

	private void ProcessEntryCandle(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!value.IsFormed || value is not SuperTrendIndicatorValue entryValue)
			return;

		if (!_entrySuperTrend.IsFormed)
			return;

		var direction = entryValue.IsUpTrend ? 1 : -1;
		if (direction == 0)
			return;

		UpdateHistory(_entryDirections, direction, EntrySignalShift + 3);

		if (HandleStops(candle))
			return;

		var current = GetDirection(_entryDirections, EntrySignalShift);
		var previous = GetDirection(_entryDirections, EntrySignalShift + 1);

		if (current is null || previous is null)
			return;

		var flipToUp = IsFlipTo(1, current, previous);
		var flipToDown = IsFlipTo(-1, current, previous);

		var closeLong = Position > 0 && ((CloseBuyOnTrendFlip && _trendDirection < 0) || (CloseBuyOnEntryFlip && current < 0));
		var closeShort = Position < 0 && ((CloseSellOnTrendFlip && _trendDirection > 0) || (CloseSellOnEntryFlip && current > 0));

		if (closeLong)
		{
			SellMarket();
			ResetStops();
		}

		if (closeShort)
		{
			BuyMarket();
			ResetStops();
		}

		if (EnableBuyEntries && _trendDirection > 0 && flipToUp && Position <= 0)
		{
			BuyMarket();
			UpdateStops(candle.ClosePrice, true);
		}
		else if (EnableSellEntries && _trendDirection < 0 && flipToDown && Position >= 0)
		{
			SellMarket();
			UpdateStops(candle.ClosePrice, false);
		}
	}

	private bool HandleStops(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_stopLossPrice is decimal sl && candle.LowPrice <= sl)
			{
				SellMarket();
				ResetStops();
				return true;
			}

			if (_takeProfitPrice is decimal tp && candle.HighPrice >= tp)
			{
				SellMarket();
				ResetStops();
				return true;
			}
		}
		else if (Position < 0)
		{
			var absPos = Math.Abs(Position);

			if (_stopLossPrice is decimal sl && candle.HighPrice >= sl)
			{
				BuyMarket(absPos);
				ResetStops();
				return true;
			}

			if (_takeProfitPrice is decimal tp && candle.LowPrice <= tp)
			{
				BuyMarket(absPos);
				ResetStops();
				return true;
			}
		}

		return false;
	}

	private void UpdateStops(decimal entryPrice, bool isLong)
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
		{
			_stopLossPrice = null;
			_takeProfitPrice = null;
			return;
		}

		_stopLossPrice = StopLossPoints > 0m
			? isLong ? entryPrice - StopLossPoints * step : entryPrice + StopLossPoints * step
			: null;

		_takeProfitPrice = TakeProfitPoints > 0m
			? isLong ? entryPrice + TakeProfitPoints * step : entryPrice - TakeProfitPoints * step
			: null;
	}

	private void ResetStops()
	{
		_stopLossPrice = null;
		_takeProfitPrice = null;
	}

	private static void UpdateHistory(List<int> history, int direction, int maxLength)
	{
		history.Insert(0, direction);

		while (history.Count > maxLength && history.Count > 0)
		{
			try { history.RemoveAt(history.Count - 1); }
			catch { break; }
		}
	}

	private static int? GetDirection(List<int> history, int offset)
	{
		return history.Count > offset ? history[offset] : null;
	}

	private bool IsFlipTo(int targetDirection, int? current, int? previous)
	{
		if (current != targetDirection)
			return false;

		return EntryMode switch
		{
			KolierTrendModes.NewWay => previous != targetDirection,
			_ => previous == -targetDirection,
		};
	}

	/// <summary>
	/// Enumeration mirroring the original MQL SuperTrend modes.
	/// </summary>
	public enum KolierTrendModes
	{
		/// <summary>
		/// Classic SuperTrend confirmation requiring consecutive candles.
		/// </summary>
		SuperTrend,

		/// <summary>
		/// Faster confirmation that reacts after a single candle flip.
		/// </summary>
		NewWay,

		/// <summary>
		/// Visual mode (treated like classic confirmation in this port).
		/// </summary>
		Visual,

		/// <summary>
		/// Expert signal mode (treated like classic confirmation in this port).
		/// </summary>
		ExpertSignal,
	}
}