GitHub で見る

Envelopes EA戦略

概要

この戦略は、MetaTrader 4 のエキスパートアドバイザー "EnvelopesEA" を再現します。主要ローソク足ストリームに指数移動平均のエンベロープを適用し、Mean Reversion を取引します。市場がエンベロープの大きく外側へ押し出されると、逆張りの成行注文を送信します。価格が反対方向からエンベロープへ戻ると、ポジションは直ちに決済されます。元のエキスパートは 2019 年に EUR/USD でテストされました。StockSharp 版は同じロジックを維持し、主要な入力をすべて最適化可能なパラメーターとして公開します。

取引ロジック

  1. 選択したローソク足で、長さ EnvelopePeriod の指数移動平均 (EMA) を計算します。
  2. EMA をそれぞれ UpperDeviationPercentLowerDeviationPercent で拡張し、上側と下側のエンベロープを作ります。
  3. 早すぎる取引を避けるため、EntryOffsetPoints で定義される追加エントリーバッファー (商品の価格ステップを掛けたもの) を適用します。
  4. オープンポジションがない場合:
    • 終値が下側エンベロープからエントリーバッファーを引いた水準を下回るとロングに入ります。
    • 終値が上側エンベロープにエントリーバッファーを加えた水準を上回るとショートに入ります。
  5. ポジションがある場合:
    • 終値が上側エンベロープを上回って戻ると、ロングポジションを閉じます。
    • 終値が下側エンベロープを下回って戻ると、ショートポジションを閉じます。

戦略は常に最大 1 つのオープンポジションだけを保持し、エントリーとエグジットの両方に成行注文を使います。

資金管理

注文数量は Volume パラメーター (ロット) で直接指定します。自動 martingale やピラミッディングのルールはなく、スケーリング機能がデフォルトで無効だった最新 MQ4 実装と同じ動作を保ちます。

パラメーター

パラメーター 説明 デフォルト
Volume ロット単位の注文数量。 0.2
EnvelopePeriod エンベロープの基準を形成する EMA の長さ。 50
UpperDeviationPercent 上側バンドに適用する割合偏差。 0.5
LowerDeviationPercent 下側バンドに適用する割合偏差。 0.5
EntryOffsetPoints エントリー前に価格がバンドを超えて進む必要がある追加距離 (価格ステップ単位)。 100
CandleType ローソク足とインジケーター計算に使う時間枠。 30 分足

すべての数値パラメーター (CandleType を除く) は、元の最適化ワークフローを再現しやすいように最適化可能としてマークされています。

注意事項

  • エンベロープは、以前のバージョンの SMA ではなく EMA を使います。これは MQ4 スクリプトの最新反復が指数ベースへ進化したためです。これにより価格変動への反応が速くなり、Mean Reversion のタイミングが改善されます。
  • エントリーバッファーは商品の PriceStep を掛けます。銘柄メタデータに有効なステップサイズが含まれていることを確認してください。ない場合、戦略は保守的なデフォルト 0.0001 にフォールバックします。
  • チャート表示には価格ローソク足、EMA エンベロープ、戦略の取引が含まれるため、元の Expert Advisor と比較してシグナル動作を簡単に検証できます。
using System;
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;

public class EnvelopesEaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public EnvelopesEaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}