GitHub で見る

20 ピップス価格チャネル戦略

概要

20 ピップス価格チャネル戦略は、Donchian スタイルの価格チャネルと短期移動平均フィルターを組み合わせた、元の MetaTrader エキスパート アドバイザー 20 ピップス の変換です。このアルゴリズムは、現在のローソク足が前のローソク足と逆に開いた場合にのみ取引を開始し、典型的な価格に基づいて計算された移動平均で方向をフィルターし、動的なチャネルベースのトレーリングストップによってサポートされる固定の20ピップターゲットを通した出口を管理します。

StockSharp バージョンは、元のアプローチの精神を維持しながら、注文管理を高レベルの API に適応させます。成行注文はエントリーとエグジットに使用され、利益目標は内部で監視され、ストップレベルは価格チャネルの状況に応じてエミュレートされます。

取引ロジック

  1. インジケータースタック

    • 典型的な価格 (H+L+C)/3 の 1 期間の単純移動平均は、前のローソク足の典型的な価格を反映する高速ベースラインとして機能します。
    • 終値に基づいて計算される構成可能な低速単純移動平均 (デフォルトは 20) は、EA からの MA_Low フィルターの役割を果たします。
    • 価格チャネルと同じ期間の最高値および最低値のインジケーター (デフォルトは 20) は、元のカスタム インジケーター バッファーをエミュレートします。
  2. エントリー条件

    • ロングセットアップ: 以前の高速の典型的な価格が以前のゆっくりとした移動平均を上回っており、かつ現在のローソク足の始値が前の始値を下回っています。取引に負けた後は、出来高に回復係数が乗算されます (デフォルトは 2)。エントリー価格は損益を追跡するために記録されます。
    • 短いセットアップ: 以前の高速の典型的な価格が以前のゆっくりとした移動平均を下回っており、かつ現在のローソク足の始値が前の始値を上回っています。出来高のスケーリングは、ロングトレードの場合と同じ回復ロジックに従います。
  3. 出口管理

    • ポジションがオープンすると、TakeProfitPips に商品価格ステップを乗算した値に等しい固定テイクプロフィットターゲットが設定されます。
    • チャネル駆動のトレーリング ストップは、元の OrderModify 呼び出しを模倣します。前のバーが価格チャネルを超えた場合 (MT4 ロジックから 2 バーのシフト)、保護ストップは前の極端なマイナス/プラスのトレーリング オフセット (ピップ単位) に移動します。次のローソク足のギャップがその極端な値を超えた場合、ポジションは始値ですぐに決済されます。
    • テイクプロフィット、トレーリングストップ、ギャップエグジットはすべて成行注文を通じて実行され、実際のエグジット価格を追跡してマーチンゲール形式のスケーリングの勝敗フラグを更新します。
  4. Martingale の回復

    • 負けポジションがクローズされるたびに、次のエントリーサイズに RecoveryMultiplier が乗算されます。利益のある取引はフラグをリセットし、基本ボリュームに戻ります。

パラメーター

名前 説明 デフォルト
CandleType 計算に使用される主な時間枠。 1時間キャンドル
ChannelPeriod Donchian スタイル チャンネルのルックバック期間。 20
SlowMaPeriod 低速移動平均フィルターの長さ。 20
TakeProfitPips 固定利益目標までの距離 (pips)。 20
TrailingOffsetPips ストップを以前の極限まで締めるときに使用されるオフセット。 10
RecoveryMultiplier 損失後に適用されるボリューム乗数。 2
Volume 回復スケーリング前の基本取引高。 0.1

使用上の注意

  • この戦略では、Security.PriceStep が取引商品の pip 値を反映すると想定しています。シンボルが異なる pip 定義を使用している場合は、TakeProfitPipsTrailingOffsetPips を調整します。
  • StockSharp は終了に成行注文を使用するため、バックテストでは元の MT4 のストップ注文と指値注文と比較してスリッページが表示される場合があります。ロジックは引き続き同じ価格しきい値を再現します。
  • チャネル値は、iCustom(..., shift=2) 呼び出しをエミュレートするためにシフトされます。トレーリング動作を変更するときは、このことに留意してください。
  • 回復乗数を 1 に設定すると、マーチンゲール スタイルのスケーリングを無効にすることができます。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Converted "20 pips" price channel strategy.
/// Uses Donchian channel breakouts with MA filter, trailing stop, and recovery multiplier.
/// </summary>
public class TwentyPipsPriceChannelStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();
	private decimal? _prevChannelUpper;
	private decimal? _prevChannelLower;

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

	/// <summary>
	/// Donchian channel lookback period.
	/// </summary>
	public int ChannelPeriod
	{
		get => _channelPeriod.Value;
		set => _channelPeriod.Value = value;
	}

	/// <summary>
	/// Slow moving average length.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

	/// <summary>
	/// Stop loss distance in absolute price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit distance in absolute price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="TwentyPipsPriceChannelStrategy"/>.
	/// </summary>
	public TwentyPipsPriceChannelStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle type", "General");

		_channelPeriod = Param(nameof(ChannelPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Channel Period", "Donchian channel lookback", "Parameters");

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA Period", "Slow moving average length", "Parameters");

		_stopLoss = Param(nameof(StopLoss), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop loss distance", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take profit distance", "Risk");
	}

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

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

		_highs.Clear();
		_lows.Clear();
		_prevChannelUpper = null;
		_prevChannelLower = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		var slowMa = new SMA { Length = SlowMaPeriod };

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

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

		// Use StartProtection for SL/TP
		var tp = TakeProfit > 0 ? new Unit(TakeProfit, UnitTypes.Absolute) : null;
		var sl = StopLoss > 0 ? new Unit(StopLoss, UnitTypes.Absolute) : null;
		StartProtection(tp, sl);

		base.OnStarted2(time);
	}

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

		// Track highs and lows for manual Donchian channel
		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		while (_highs.Count > ChannelPeriod)
			_highs.RemoveAt(0);
		while (_lows.Count > ChannelPeriod)
			_lows.RemoveAt(0);

		if (_highs.Count < ChannelPeriod)
		{
			_prevChannelUpper = null;
			_prevChannelLower = null;
			return;
		}

		var channelUpper = _highs.Max();
		var channelLower = _lows.Min();

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevChannelUpper = channelUpper;
			_prevChannelLower = channelLower;
			return;
		}

		// Channel breakout with MA filter
		if (_prevChannelUpper.HasValue && _prevChannelLower.HasValue)
		{
			// Breakout above the previous channel high -> buy signal
			if (candle.ClosePrice > _prevChannelUpper.Value && candle.ClosePrice > slowMaValue && Position <= 0)
			{
				if (Position < 0)
					BuyMarket(Math.Abs(Position));
				BuyMarket(Volume);
			}
			// Breakout below the previous channel low -> sell signal
			else if (candle.ClosePrice < _prevChannelLower.Value && candle.ClosePrice < slowMaValue && Position >= 0)
			{
				if (Position > 0)
					SellMarket(Position);
				SellMarket(Volume);
			}
		}

		_prevChannelUpper = channelUpper;
		_prevChannelLower = channelLower;
	}
}