GitHub で見る

フラットチャンネル戦略 (2684)

この戦略は MetaTrader 5 エキスパートアドバイザー Flat Channel (barabashkakvn 版) の C# 変換です。標準偏差インジケーターを使用して低ボラティリティ期間(「フラット」チャンネル)を検出し、チャンネルの境界にブレイクアウトのストップ注文を配置します。価格がフラットレンジから抜け出すと対応するストップ注文が発動し、市場の両側に挟まれることを避けるために反対方向の注文がキャンセルされます。

コアロジック

  1. ボラティリティフィルター – 戦略はローソク足をサブスクライブし、中間価格の標準偏差を計算します。値が少なくとも FlatBars 連続ローソク足で下落し続けるとフラットフェーズが確認されます。
  2. チャンネル構築 – フラットフェーズが確認されると、フラットレンジの最高値と最安値が追跡されます。チャンネル幅は ChannelMinPipsChannelMaxPips の間(インストゥルメントのティックサイズで価格単位に変換)に維持される必要があります。
  3. エントリー注文 – 価格がチャンネル内で取引されている間、戦略は以下を配置します:
    • チャンネル高値にバイストップ、ストップロスはエントリーの 2 × チャンネル幅 下、テイクプロフィットは 1 × チャンネル幅 上。
    • チャンネル安値にセルストップ、対称的なストップロス/テイクプロフィット距離で。
  4. 注文の有効期限 – 保留中のストップ注文は OrderLifetimeSeconds 後に期限切れになります。タイムアウトが経過するとキャンセルされ、フラット状態が続く場合は再作成できます。
  5. ポジション管理 – エントリー注文が執行された後、反対方向のストップ注文がキャンセルされ、新しい保護注文(ストップロスとテイクプロフィット)が登録されます。オプションのブレークイーブンロジックは、価格がテイクプロフィット目標に向かってフィボナッチ比率(FiboTrail)の距離を移動したときにストップロスをエントリー価格に移動します。
  6. 取引ウィンドウUseTradingHours フィルターは曜日と月曜日/金曜日の特定の時間帯でアクティビティを制限し、元の EA のスケジュール制御をエミュレートします。

インジケーター

  • StandardDeviation(中間価格、長さ = StdDevPeriod) – ボラティリティの低下を検出します。
  • DonchianChannels(長さ = FlatBars) – フラットチャンネルの初期高値/安値の境界を提供します。

リスクと資金管理

  • UseMoneyManagement が無効の場合、FixedVolume がロットサイズを定義します。
  • UseMoneyManagement が有効の場合、ポジションサイズは PriceStepStepPrice を使用して金額で表されたストップロス距離で割った現在のポートフォリオ価値の RiskPercent から推定されます。
  • 損失取引の後、次のポジションは FixedVolume × 4 を使用し、元の EA の回復動作を再現します。

パラメーター

パラメーター 説明
UseTradingHours 曜日/時間スケジュールフィルターを有効または無効にします。
TradeTuesdayTradeWednesdayTradeThursday 週半ばの個別の日の取引を許可します(月曜日と金曜日は常に許可されますが、時間制限で制御されます)。
MondayStartHourFridayStopHour 月曜日の開始時間と金曜日の終了時間(24時間制)。
UseMoneyManagementRiskPercentFixedVolume 上記の資金管理オプション。
OrderLifetimeSeconds 保留中のエントリー注文の有効期限(0 = 有効期限なし)。
StdDevPeriodFlatBars フラットフェーズ検出を制御するインジケーター設定。
ChannelMinPipsChannelMaxPips pips で表された許容チャンネル幅(インストゥルメントのティックサイズを使用して変換)。
UseBreakevenFiboTrail ブレークイーブンロジックを有効にし、ストップ調整を発動するフィボナッチ乗数を設定します。
CandleType 計算に使用するローソク足データタイプまたは時間軸。

注意事項

  • 戦略は、pips ベースのしきい値を実際の価格に変換できるように PriceStepStepPrice を公開するシンボルを期待します。
  • 保留中の注文はボラティリティが下落し続ける場合にのみ再作成されます。ボラティリティが上昇するとフラット状態がリセットされ、すべてのエントリー注文がキャンセルされます。
  • 保護的なストップとテイクプロフィット注文はポジションが閉じると自動的にキャンセルされます。

免責事項

この例は教育目的のみに提供されています。元の戦略の過去のパフォーマンスは将来の結果を保証しません。ライブ市場に展開する前にパラメーターを徹底的にテストして調整してください。

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>
/// Flat channel breakout strategy converted from the MetaTrader 5 version.
/// Detects consolidation via falling standard deviation, then trades breakouts of the channel.
/// </summary>
public class FlatChannelStrategy : Strategy
{
	private readonly StrategyParam<int> _stdDevPeriod;
	private readonly StrategyParam<int> _flatBars;
	private readonly StrategyParam<decimal> _channelMinPips;
	private readonly StrategyParam<decimal> _channelMaxPips;
	private readonly StrategyParam<DataType> _candleType;

	private StandardDeviation _stdDev = null!;
	private DonchianChannels _donchian = null!;

	private decimal _previousStdDev;
	private int _flatBarCount;
	private decimal _channelHigh;
	private decimal _channelLow;

	private decimal? _pendingBuyPrice;
	private decimal? _pendingSellPrice;
	private decimal _entryPrice;
	private decimal _longStop;
	private decimal _longTake;
	private decimal _shortStop;
	private decimal _shortTake;

	/// <summary>
	/// Standard deviation indicator period.
	/// </summary>
	public int StdDevPeriod
	{
		get => _stdDevPeriod.Value;
		set => _stdDevPeriod.Value = value;
	}

	/// <summary>
	/// Minimum number of bars with falling volatility required to form a flat channel.
	/// </summary>
	public int FlatBars
	{
		get => _flatBars.Value;
		set => _flatBars.Value = value;
	}

	/// <summary>
	/// Minimum channel width expressed in pips.
	/// </summary>
	public decimal ChannelMinPips
	{
		get => _channelMinPips.Value;
		set => _channelMinPips.Value = value;
	}

	/// <summary>
	/// Maximum channel width expressed in pips.
	/// </summary>
	public decimal ChannelMaxPips
	{
		get => _channelMaxPips.Value;
		set => _channelMaxPips.Value = value;
	}

	/// <summary>
	/// Candle type to analyse.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public FlatChannelStrategy()
	{
		_stdDevPeriod = Param(nameof(StdDevPeriod), 37)
			.SetDisplay("StdDev Period", "Standard deviation indicator period", "Indicators")
			.SetGreaterThanZero();

		_flatBars = Param(nameof(FlatBars), 2)
			.SetDisplay("Flat Bars", "Minimum bars in flat state", "Indicators")
			.SetGreaterThanZero();

		_channelMinPips = Param(nameof(ChannelMinPips), 10m)
			.SetDisplay("Channel Min Pips", "Minimum channel width in pips", "Indicators")
			.SetGreaterThanZero();

		_channelMaxPips = Param(nameof(ChannelMaxPips), 100000m)
			.SetDisplay("Channel Max Pips", "Maximum channel width in pips", "Indicators")
			.SetGreaterThanZero();

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle type", "General");
	}

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

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

		_previousStdDev = 0m;
		_flatBarCount = 0;
		_channelHigh = 0m;
		_channelLow = 0m;
		_pendingBuyPrice = null;
		_pendingSellPrice = null;
		_entryPrice = 0m;
		_longStop = 0m;
		_longTake = 0m;
		_shortStop = 0m;
		_shortTake = 0m;
	}

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

		_stdDev = new StandardDeviation { Length = StdDevPeriod };
		_donchian = new DonchianChannels { Length = FlatBars };

		var subscription = SubscribeCandles(CandleType);

		subscription
			.BindEx(_donchian, ProcessCandle)
			.Start();

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

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

		var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;
		var stdDevValue = _stdDev.Process(new DecimalIndicatorValue(_stdDev, medianPrice, candle.CloseTime) { IsFinal = true }).ToDecimal();

		if (!_stdDev.IsFormed || channelValue is not DonchianChannelsValue donchianValue)
		{
			_previousStdDev = stdDevValue;
			return;
		}

		if (donchianValue.UpperBand is not decimal upper || donchianValue.LowerBand is not decimal lower)
		{
			_previousStdDev = stdDevValue;
			return;
		}

		// Update flat state based on StdDev direction.
		UpdateStdDevState(stdDevValue, upper, lower, candle);

		// Check simulated pending entries.
		CheckPendingEntries(candle);

		// Manage existing positions with SL/TP.
		ManagePosition(candle);

		// If flat and no position, set up pending breakout entries.
		if (Position == 0 && _flatBarCount >= FlatBars && _channelHigh > _channelLow)
		{
			var channelWidth = _channelHigh - _channelLow;
			var priceStep = Security?.PriceStep ?? 0.01m;
			if (priceStep <= 0m) priceStep = 0.01m;
			var minWidth = ChannelMinPips * priceStep;
			var maxWidth = ChannelMaxPips * priceStep;

			if (channelWidth >= minWidth && channelWidth <= maxWidth)
			{
				// Set pending breakout entries at channel boundaries.
				_pendingBuyPrice = _channelHigh;
				_pendingSellPrice = _channelLow;
				_longStop = _channelHigh - channelWidth * 2m;
				_longTake = _channelHigh + channelWidth;
				_shortStop = _channelLow + channelWidth * 2m;
				_shortTake = _channelLow - channelWidth;
			}
		}

		_previousStdDev = stdDevValue;
	}

	private void UpdateStdDevState(decimal stdDevValue, decimal upper, decimal lower, ICandleMessage candle)
	{
		if (_previousStdDev == 0m)
		{
			_previousStdDev = stdDevValue;
			return;
		}

		if (stdDevValue < _previousStdDev)
		{
			_flatBarCount++;

			if (_flatBarCount == FlatBars)
			{
				_channelHigh = upper;
				_channelLow = lower;
			}
			else if (_flatBarCount > FlatBars)
			{
				if (candle.HighPrice > _channelHigh)
					_channelHigh = candle.HighPrice;
				if (candle.LowPrice < _channelLow)
					_channelLow = candle.LowPrice;
			}
		}
		else if (stdDevValue > _previousStdDev)
		{
			_flatBarCount = 0;
			_channelHigh = 0m;
			_channelLow = 0m;
			_pendingBuyPrice = null;
			_pendingSellPrice = null;
		}
		else if (_flatBarCount >= FlatBars && _channelHigh <= _channelLow)
		{
			_channelHigh = upper;
			_channelLow = lower;
		}
	}

	private void CheckPendingEntries(ICandleMessage candle)
	{
		if (Position != 0)
			return;

		if (_pendingBuyPrice is decimal buyPrice && candle.HighPrice >= buyPrice)
		{
			BuyMarket();
			_entryPrice = buyPrice;
			_pendingBuyPrice = null;
			_pendingSellPrice = null;
			return;
		}

		if (_pendingSellPrice is decimal sellPrice && candle.LowPrice <= sellPrice)
		{
			SellMarket();
			_entryPrice = sellPrice;
			_pendingBuyPrice = null;
			_pendingSellPrice = null;
		}
	}

	private void ManagePosition(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_longStop > 0m && candle.LowPrice <= _longStop)
			{
				SellMarket();
				ResetPositionState();
				return;
			}
			if (_longTake > 0m && candle.HighPrice >= _longTake)
			{
				SellMarket();
				ResetPositionState();
			}
		}
		else if (Position < 0)
		{
			if (_shortStop > 0m && candle.HighPrice >= _shortStop)
			{
				BuyMarket();
				ResetPositionState();
				return;
			}
			if (_shortTake > 0m && candle.LowPrice <= _shortTake)
			{
				BuyMarket();
				ResetPositionState();
			}
		}
	}

	private void ResetPositionState()
	{
		_entryPrice = 0m;
		_longStop = 0m;
		_longTake = 0m;
		_shortStop = 0m;
		_shortTake = 0m;
		_pendingBuyPrice = null;
		_pendingSellPrice = null;
	}
}