GitHub で見る

EMA (barabashkakvn Edition)戦略

MetaTrader 5エキスパートアドバイザー「EMA (barabashkakvn's edition)」から変換されました。このシステムは中間値価格で計算された2つの指数移動平均のクロスオーバーを取引し、ピップで表現された仮想テイクプロフィット/ストップロスレベルを使用します。ポジションは確認されたクロスオーバーと前のローソク足の極値への小さな押し目の後にのみ開かれます。

核心的な考え方

  1. 選択した時間軸で中間値価格の5期間と10期間EMAを追跡する。
  2. 高速EMAが低速EMAをクロスした時、即座に取引するのではなく保留シグナルをセットする。
  3. EMAスプレッドが2 * pipSizeを超えながら、価格が前のローソク足の極値からMoveBackPips押し目する。
  4. 押し目が発生したらクロスオーバーの方向にエントリーする。
  5. エントリー価格からピップ単位で測定された仮想ターゲットとストップでオープンポジションを管理する。

この動作は元のMQL実装を反映しています:エキスパートアドバイザーはクロスオーバーフラグ(check)を待ってから、トレードをトリガーするためにEMAスプレッドプラス前のローソク足に対する価格押し目を要求しました。エグジットルールも、指定された距離にbid/askが触れると仮定してポジションを閉じる「仮想」アプローチに従います。

インジケーターとデータ

  • 中間値価格(high + low)/ 2での5期間EMA。
  • 中間値価格での10期間EMA。
  • 押し目確認のための前の完成ローソク足の高値/安値。
  • すべての処理は設定されたCandleTypeサブスクリプションからの完成ローソク足を使用。

パラメーター

パラメーター デフォルト 説明
OrderVolume 0.1 各エントリーのロット/契約での取引ボリューム。
VirtualProfitPips 5 エントリー価格と仮想テイクプロフィット間の距離(ピップ単位)。
MoveBackPips 3 クロスオーバー後に必要な押し目、前のローソク足の極値から計測。
StopLossPips 20 エントリー価格と仮想ストップロス間の距離(ピップ単位)。
PipSize 0.0001 価格単位で表現されたピップサイズ。異なるピップ定義のシンボルを取引する際に上書き。
FastLength 5 高速EMAの長さ。
SlowLength 10 低速EMAの長さ。
CandleType TimeFrame(1m) 計算に使用されるローソク足ソース。

すべてのピップベースの値はpipValue = PipSizeを使用して価格距離に変換されます。パラメーターがゼロまたは負の数のままの場合、戦略はSecurity.PriceStepにフォールバックします(ボードから提供される場合)。

トレードロジック

エントリー条件

  • シグナルのアーミング: クロスオーバーが発生するたびに保留シグナルを保存(FastEMASlowEMAを上回りクロスまたはその逆)。まだトレードは配置されません。
  • ショートエントリー: 要件
    • 保留シグナルが存在する。
    • SlowEMA - FastEMA > 2 * pipSize
    • 現在のローソク足の高値 ≥ 前のローソク足の安値 + MoveBackPips * pipSize(価格が前の安値から上に押し目した)。
  • ロングエントリー: 要件
    • 保留シグナルが存在する。
    • FastEMA - SlowEMA > 2 * pipSize
    • 現在のローソク足の安値 ≤ 前のローソク足の高値 - MoveBackPips * pipSize(価格が前の高値から下に押し目した)。

ポジションを開いた後、重複エントリーを避けるために保留フラグはリセットされます。

エグジット条件

仮想ターゲットはローソク足の極値をプリセットの距離と比較することでMQL動作をエミュレートします:

  • ロングポジション:
    • ローソク足の高値 ≥ エントリー価格 + VirtualProfitPips * pipSizeの場合クローズ。
    • ローソク足の安値 ≤ エントリー価格 - StopLossPips * pipSizeの場合クローズ。
  • ショートポジション:
    • ローソク足の安値 ≤ エントリー価格 - VirtualProfitPips * pipSizeの場合クローズ。
    • ローソク足の高値 ≥ エントリー価格 + StopLossPips * pipSizeの場合クローズ。

エグジット後、仮想レベルはリセットされ、戦略は次のクロスオーバーを待ちます。

実装に関する注意事項

  • 高レベルのローソク足サブスクリプション(SubscribeCandles)を使用し、オプションのチャートエリアにEMAとトレードを描画します。
  • 中間値価格はMetaTraderのPRICE_MEDIANに一致するようにローソク足の高値/安値から直接計算されます。
  • クロスオーバーフラグ(_hasCrossSignal)は元のcheck変数を再現し、クロスオーバーと押し目の確認後にのみトレードが発生することを保証します。
  • StartProtection()OnStartedで呼び出され、戦略がエグジットを手動で処理しても組み込みのリスク監視を有効にします。
  • コードはリクエストに従ってすべてのコメントを英語で保持し、インジケーターバッファに直接アクセスせずに完成ローソク足のみに依存します。

使用上のヒント

  • 非標準のピップ定義のインストゥルメント(例:JPYペア、インデックス、暗号通貨の相場)で操作する場合はPipSizeを調整してください。
  • エグジットはローソク足の極値に依存するため、より短い時間軸(1〜5分)を使用すると元のティックベースのエキスパートに近い動作が維持されます。
  • 最適化は提供されたパラメーターメタデータを使用してEMAの長さ、ピップ距離、押し目値を探索できます。
  • 戦略は一度に1つのポジションを取引します。同じ銘柄の外部ポジションは仮想エグジット追跡に干渉する可能性があります。

リスク

  • ローソク足ベースのシミュレーションは仮想レベルのイントラバータッチを見逃す可能性があります。精度が重要な場合は高解像度データを検討してください。
  • 仮想エグジットは実際の保護注文を配置しないため、ライブ取引での切断やスリッページは予想より大きな損失につながる可能性があります。
  • どのクロスオーバーシステムでも、レンジ相場ではパフォーマンスが低下します。必要に応じてフィルターと組み合わせてください。
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>
/// EMA crossover strategy with virtual take profit and stop loss distances.
/// Converted from the MQL5 expert "EMA (barabashkakvn's edition)".
/// </summary>
public class EmaBarabashkakvnEditionStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _virtualProfitPips;
	private readonly StrategyParam<int> _moveBackPips;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<decimal> _pipSize;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;
	private bool _hasCrossSignal;
	private decimal? _prevFast;
	private decimal? _prevSlow;
	private decimal? _prevHigh;
	private decimal? _prevLow;
	private decimal? _entryPrice;
	private decimal? _virtualTarget;
	private decimal? _virtualStop;

	/// <summary>
	/// Order volume in lots.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Virtual take profit distance in pips.
	/// </summary>
	public int VirtualProfitPips
	{
		get => _virtualProfitPips.Value;
		set => _virtualProfitPips.Value = value;
	}

	/// <summary>
	/// Retracement distance after a crossover in pips.
	/// </summary>
	public int MoveBackPips
	{
		get => _moveBackPips.Value;
		set => _moveBackPips.Value = value;
	}

	/// <summary>
	/// Virtual stop loss distance in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Pip size in price units.
	/// </summary>
	public decimal PipSize
	{
		get => _pipSize.Value;
		set => _pipSize.Value = value;
	}

	/// <summary>
	/// Fast EMA length applied to median price.
	/// </summary>
	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	/// <summary>
	/// Slow EMA length applied to median price.
	/// </summary>
	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of <see cref="EmaBarabashkakvnEditionStrategy"/>.
	/// </summary>
	public EmaBarabashkakvnEditionStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume in lots", "Trading")
			
			.SetOptimize(0.05m, 1m, 0.05m);

		_virtualProfitPips = Param(nameof(VirtualProfitPips), 5)
			.SetGreaterThanZero()
			.SetDisplay("Virtual Profit", "Take profit distance in pips", "Risk")
			
			.SetOptimize(2, 20, 1);

		_moveBackPips = Param(nameof(MoveBackPips), 3)
			.SetGreaterThanZero()
			.SetDisplay("Move Back", "Retracement after crossover in pips", "Entries")
			
			.SetOptimize(1, 10, 1);

		_stopLossPips = Param(nameof(StopLossPips), 20)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Virtual stop loss distance in pips", "Risk")
			
			.SetOptimize(10, 60, 2);

		_pipSize = Param(nameof(PipSize), 0.0001m)
			.SetGreaterThanZero()
			.SetDisplay("Pip Size", "Instrument pip size in price units", "General");

		_fastLength = Param(nameof(FastLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA length on median price", "Indicators")
			
			.SetOptimize(3, 15, 1);

		_slowLength = Param(nameof(SlowLength), 10)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA length on median price", "Indicators")
			
			.SetOptimize(8, 40, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Source candles", "General");
	}

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

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

		_fastEma = default;
		_slowEma = default;
		_hasCrossSignal = false;
		_prevFast = default;
		_prevSlow = default;
		_prevHigh = default;
		_prevLow = default;
		_entryPrice = default;
		_virtualTarget = default;
		_virtualStop = default;
	}

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

		_fastEma = new EMA { Length = FastLength };
		_slowEma = new EMA { Length = SlowLength };

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

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

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

		// Calculate median price as in the original expert (PRICE_MEDIAN).
		var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;

		// Update EMA values using the median price.
		var fastValue = _fastEma.Process(new DecimalIndicatorValue(_fastEma, medianPrice, candle.OpenTime) { IsFinal = true });
		var slowValue = _slowEma.Process(new DecimalIndicatorValue(_slowEma, medianPrice, candle.OpenTime) { IsFinal = true });

		if (!_fastEma.IsFormed || !_slowEma.IsFormed)
		{
			_prevHigh = candle.HighPrice;
			_prevLow = candle.LowPrice;
			_prevFast = fastValue.ToDecimal();
			_prevSlow = slowValue.ToDecimal();
			return;
		}

		var fast = fastValue.ToDecimal();
		var slow = slowValue.ToDecimal();

		if (_prevFast is decimal prevFast && _prevSlow is decimal prevSlow)
		{
			var bullishCross = prevFast <= prevSlow && fast > slow;
			var bearishCross = prevFast >= prevSlow && fast < slow;

			if (bullishCross || bearishCross)
				_hasCrossSignal = true;
		}

		_prevFast = fast;
		_prevSlow = slow;

		var pipValue = PipSize;
		if (pipValue <= 0m)
			pipValue = Security?.PriceStep ?? 0.0001m;

		var moveBackPrice = MoveBackPips * pipValue;
		var profitDistance = VirtualProfitPips * pipValue;
		var stopDistance = StopLossPips * pipValue;

		if (Position == 0 && _hasCrossSignal && _prevHigh is decimal prevHigh && _prevLow is decimal prevLow)
		{
			var bearishSpread = slow - fast;
			var bullishSpread = fast - slow;

			var bearishReady = bearishSpread > 2m * pipValue && candle.HighPrice >= prevLow + moveBackPrice;
			var bullishReady = bullishSpread > 2m * pipValue && candle.LowPrice <= prevHigh - moveBackPrice;

			if (bearishReady)
			{
				// Enter short after bearish cross and retracement above the previous low.
				_entryPrice = candle.ClosePrice;
				_virtualTarget = _entryPrice - profitDistance;
				_virtualStop = _entryPrice + stopDistance;
				SellMarket();
				_hasCrossSignal = false;
			}
			else if (bullishReady)
			{
				// Enter long after bullish cross and retracement below the previous high.
				_entryPrice = candle.ClosePrice;
				_virtualTarget = _entryPrice + profitDistance;
				_virtualStop = _entryPrice - stopDistance;
				BuyMarket();
				_hasCrossSignal = false;
			}
		}
		else if (Position != 0 && _entryPrice is decimal && _virtualTarget is decimal target && _virtualStop is decimal stop)
		{
			if (Position > 0)
			{
				// Long position: use high for profit target and low for stop.
				var hitTarget = candle.HighPrice >= target;
				var hitStop = candle.LowPrice <= stop;

				if (hitTarget || hitStop)
				{
					SellMarket();
					_hasCrossSignal = false;
					_entryPrice = null;
					_virtualTarget = null;
					_virtualStop = null;
				}
			}
			else if (Position < 0)
			{
				// Short position: use low for profit target and high for stop.
				var hitTarget = candle.LowPrice <= target;
				var hitStop = candle.HighPrice >= stop;

				if (hitTarget || hitStop)
				{
					BuyMarket();
					_hasCrossSignal = false;
					_entryPrice = null;
					_virtualTarget = null;
					_virtualStop = null;
				}
			}
		}

		if (Position == 0)
		{
			_entryPrice = null;
			_virtualTarget = null;
			_virtualStop = null;
		}

		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
	}
}