GitHub で見る

Parabolic SAR アラート戦略

概要

この戦略は、MetaTrader 4 エキスパート アドバイザー pSAR_alert.mq4 の StockSharp 移植版です。元のスクリプトは、Parabolic SAR インジケーターが価格の一方の側からもう一方の側に反転するたびに警告音を再生するだけでした。変換では同じ決定ロジックが維持されますが、アラートが実際の成行注文に変換され、シグナルが StockSharp 内で自動的に取引されるようになります。

取引ロジック

  • このストラテジーは、設定されたローソク足タイプをサブスクライブし、デフォルトでクラシック加速係数 (0.02) と最大加速係数 (0.2) を使用して Parabolic SAR インジケーターを実行します。
  • この戦略は、終了したローソク足ごとに、Parabolic SAR の値をローソク足の終値と比較し、前のローソク足のコンテキストも追跡します。
  • 前回のローソク足が SAR を下回って終了したが、現在の終値が上を向いている場合、インジケーターは下向きに反転し、ロングポジションがオープンします (または既存のショートポジションが反転します)。
  • 前回のローソク足が SAR を上回って終了したが、現在の終値が下回った場合、インジケーターは上向きになり、ショートポジションがオープンします (または既存のロングポジションが反転します)。
  • 取引量は、基本戦略量に絶対的な現在のポジションを加えたものとして計算され、新しい方向に入る前に反転が前の取引から完全に抜け出すことが保証されます。
  • StartProtection() は開始時に実行されるため、ポジションがオープンしている間の予期せぬ切断は StockSharp によって自動的に処理されます。

パラメーター

パラメータ デフォルト 説明
AccelerationFactor 0.02 Parabolic SAR が価格変動にどれだけ早く従うかを制御する初期加速ステップ。
MaxAccelerationFactor 0.2 加速ステップの上限。強いトレンド中に SAR がどれだけ積極的に加速するかを制限します。
CandleType 5分の時間枠 インジケーターの更新に使用される市場データの種類。これを変更して、タイムフレームまたは他のローソク足表現を切り替えます。

すべてのパラメータは StrategyParam<T> を通じて公開されるため、StockSharp デザイナーで直接最適化できます。

インジケーターのワークフロー

  1. SubscribeCandles 経由で設定済みのキャンドル ストリームを購読します。
  2. ストリームを ParabolicSar インジケーターにバインドして、StockSharp が自動的に更新されるようにします。
  3. バインディング コールバック内で、現在の SAR 値と終値を比較し、以前の SAR/close ペアを保持します。
  4. SAR が終値の上から下に移動した (強気の反転) か、下から上に移動した (弱気の反転) かを評価することでクロスオーバーを検出します。
  5. 必要に応じて BuyMarket または SellMarket を実行し、取引ごとに説明メッセージを記録します。

実用上の注意

  • この戦略は確認されたローソク足の終値にのみ反応するため、バーが終了する前に消える可能性のある時期尚早のシグナルを回避します。
  • デフォルトのパラメータは MQL スクリプトの動作を再現しますが、パラメータを調整して Parabolic SAR の感度を調整することができます。
  • きれいにトレンドを示す銘柄に戦略を適用します。 SAR フリップ ロジックは、ノイズが多い場合ではなく、反転が決定的な場合に最適に機能します。
  • チャート領域が利用可能になると、チャートの視覚化が自動的に有効になります。ローソク足、Parabolic SAR インジケーター、および独自の取引が描画され、簡単に検査できます。

ファイル

  • CS/ParabolicSarCrossoverAlertStrategy.cs – 戦略の C# 実装。
  • README.md – このドキュメントは英語です。
  • README_zh.md – ドキュメントの中国語翻訳。
  • README_ru.md – ドキュメントのロシア語翻訳。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Parabolic SAR Crossover: EMA crossover with ATR stops.
/// </summary>
public class ParabolicSarCrossoverAlertStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public ParabolicSarCrossoverAlertStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastEmaLength = Param(nameof(FastEmaLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowEmaLength = Param(nameof(SlowEmaLength), 30)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

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

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, atr, ProcessCandle)
			.Start();

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

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

		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (fastVal < slowVal && _prevFast >= _prevSlow)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (close <= _entryPrice - atrVal * 2m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (close >= _entryPrice + atrVal * 2m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (fastVal < slowVal && _prevFast >= _prevSlow)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}