GitHub で見る

PriceChannel シグナル v2 戦略

概要

PriceChannel Signal v2 は、変更された Donchian チャネルを中心に構築されたトレンド追跡ブレイクアウト システムです。オリジナルの MQL5 エキスパート アドバイザーは、チャネル トレンドの推移、価格がバンドを押し戻されたときのオプションのリエントリー条件、同じレンジから導出される保護的な出口レベルを監視します。 StockSharp ポートは元の動作を維持します。一度に 1 つのポジションを取引し、完了したローソク足にのみ反応し、日中ウィンドウに制限できます。

取引ロジック

  1. Donchian チャンネルの高値と低値は、設定された ChannelPeriod に基づいて計算されます。
  2. 生の範囲は 2 つの乗数によってシフトされます。
    • リスクファクター – エントリーバンドをチャネル中央値に向かって圧縮します。
    • 終了レベル – 終了をトリガーする内側バンドの 2 番目のペアを構築します。
  3. トレンド状態が維持されます。
    • 終値が上部エントリーバンドを上抜けると、トレンドは強気になります。
    • 終値が下限エントリーバンドを下回ると、トレンドは弱気になります。
    • それ以外の場合は、以前の傾向が維持されます。
  4. その状態から生成される信号:
    • ロングエントリー – トレンドが弱気から強気に反転します。
    • ショートエントリー – トレンドが強気から弱気に反転します。
    • ロングリエントリー – オプション。トレンドがすでに強気である間に価格が上限バンドを上回って終了します。
    • ショートリエントリー – オプション。トレンドがすでに弱気である間に、価格は下限バンドを下回って終了します。
    • ロングエグジット – オプション。価格は前のバーで強気エグジットバンドを上回った後、その下で終了します。
    • ショートエグジット – オプション。価格は前の足で弱気エグジットバンドを下回った後、その上で終了します。
  5. バーごと、方向ごとに 1 つのオーダーのみが許可されます。この戦略は、別のポジションがすでにアクティブである場合、新しいポジションをオープンすることを拒否します。
  6. 日中時間フィルターが有効な場合、構成されたウィンドウ外では上記の信号はすべて無視されます。

パラメーター

パラメータ 説明
ChannelPeriod 価格チャネルと出口バンドの計算に使用される Donchian のルックバック長。
RiskFactor エントリーバンドのシフト (0 ~ 10)。値を低くすると帯域が広がり、値を高くすると帯域が狭くなります。
ExitLevel 出口バンドのシフト。エントリ範囲内に収まるようにするには、RiskFactor より大きくする必要があります。
UseReEntry 価格がアクティブバンドを通過して押し戻されたときに、リエントリー取引が可能になります。
UseExitSignals 内側の保護バンドに基づいて終了ロジックを有効にします。
CandleType ローソク足を構築し、インジケーターを実行するために使用される時間枠。
UseTimeControl 日中取引ウィンドウを切り替えます。
StartHour / StartMinute 時間制御がアクティブな場合の取引ウィンドウの包括的な開始。
EndHour / EndMinute 時間制御がアクティブな場合の取引ウィンドウの排他的な終了。

入退場のルール

  • ロングエントリー: トレンドが強気またはリエントリー条件に反転し、現在位置はフラットで、バーは許容時間枠内にあります。
  • ショート入力: トレンドが弱気またはショートリエントリー条件に反転し、現在位置は横ばいで、バーは許容時間枠内にあります。
  • Exit Long: UseExitSignals が有効になっており、終値は前のバーでExit バンドを上回った後、下に落ちます。
  • エグジットショート: UseExitSignals が有効で、終値は前の足でエグジットバンドを下回った後、エグジットバンドを上回ります。

追加の注意事項

  • この戦略は成行注文で機能し、ポジションをピラミッド化するものではありません。
  • インジケーター値は、バー内再描画を避けるために、完成したローソク足でのみ処理されます。
  • 明示的に指定しない場合、ボリュームはデフォルトで 1 コントラクトになります。
  • 時間制御は元の EA の動作に従います。終了時刻は排他的であり、午前 0 時をまたぐラップがサポートされています。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Price Channel Signal v2 strategy that reacts to Donchian channel breakouts.
/// </summary>
public class PriceChannelSignalV2Strategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private readonly Queue<decimal> _highHistory = new();
	private readonly Queue<decimal> _lowHistory = new();
	private int _previousTrend;
	private decimal? _previousClose;

	/// <summary>
	/// Channel lookback length.
	/// </summary>
	public int ChannelPeriod
	{
		get => _channelPeriod.Value;
		set => _channelPeriod.Value = value;
	}

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

	/// <summary>
	/// Initialize a new instance of <see cref="PriceChannelSignalV2Strategy"/>.
	/// </summary>
	public PriceChannelSignalV2Strategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Channel Period", "Donchian lookback used for Price Channel", "Price Channel");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for Price Channel", "General");
	}

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

		_previousTrend = 0;
		_previousClose = null;
		_highHistory.Clear();
		_lowHistory.Clear();

		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;

		if (_highHistory.Count < ChannelPeriod)
		{
			EnqueueCandle(candle);
			return;
		}

		var highs = _highHistory.ToArray();
		var lows = _lowHistory.ToArray();
		var channelHigh = GetMax(highs);
		var channelLow = GetMin(lows);
		var range = channelHigh - channelLow;
		if (range <= 0m)
		{
			_previousClose = candle.ClosePrice;
			EnqueueCandle(candle);
			return;
		}

		var mid = (channelHigh + channelLow) / 2m;

		// Update trend state based on channel breakout
		var trend = _previousTrend;
		if (candle.ClosePrice > channelHigh + range * 0.05m)
			trend = 1;
		else if (candle.ClosePrice < channelLow - range * 0.05m)
			trend = -1;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Trend reversal signals
		var changedPosition = false;

		if (trend > 0 && _previousTrend <= 0)
		{
			if (Position <= 0)
			{
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
				changedPosition = true;
			}
		}
		else if (trend < 0 && _previousTrend >= 0)
		{
			if (Position >= 0)
			{
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
				changedPosition = true;
			}
		}

		// Exit on mid-line cross
		if (!changedPosition && Position > 0 && _previousClose is decimal pc1 && pc1 >= mid && candle.ClosePrice < mid)
		{
			SellMarket(Math.Abs(Position));
		}
		else if (!changedPosition && Position < 0 && _previousClose is decimal pc2 && pc2 <= mid && candle.ClosePrice > mid)
		{
			BuyMarket(Math.Abs(Position));
		}

		_previousTrend = trend;
		_previousClose = candle.ClosePrice;
		EnqueueCandle(candle);
	}

	private void EnqueueCandle(ICandleMessage candle)
	{
		_highHistory.Enqueue(candle.HighPrice);
		_lowHistory.Enqueue(candle.LowPrice);

		while (_highHistory.Count > ChannelPeriod)
			_highHistory.Dequeue();

		while (_lowHistory.Count > ChannelPeriod)
			_lowHistory.Dequeue();
	}

	private static decimal GetMax(IEnumerable<decimal> values)
	{
		var max = decimal.MinValue;

		foreach (var value in values)
		{
			if (value > max)
				max = value;
		}

		return max;
	}

	private static decimal GetMin(IEnumerable<decimal> values)
	{
		var min = decimal.MaxValue;

		foreach (var value in values)
		{
			if (value < min)
				min = value;
		}

		return min;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_previousTrend = 0;
		_previousClose = null;
		_highHistory.Clear();
		_lowHistory.Clear();

		base.OnReseted();
	}
}