GitHub で見る

MA by MA クロスオーバー戦略

この戦略は二重平滑化移動平均クロスオーバーを実装します。 価格系列はまず高速指数移動平均(EMA)によって平滑化されます。 高速EMAの結果はさらに低速EMAによって再び平滑化されます。 二つの系列を比較してシグナルを生成します:

  • 高速EMAが低速EMAを上抜けるとロングポジションを開きます。
  • 高速EMAが低速EMAを下抜けるとショートポジションを開きます。 クロスオーバー時に既存の反対ポジションは閉じられます。

この戦略はあらゆる時間軸のローソク足で機能します。

パラメーター

  • FastLength – 高速EMAの期間。
  • SlowLength – 高速EMA出力に適用する低速EMAの期間。
  • EnableLong – ロングポジションの開設を許可する。
  • EnableShort – ショートポジションの開設を許可する。
  • CandleType – 計算に使用するローソク足の種類。

詳細

  • エントリー条件:
    • ロング: 高速EMAが低速EMAを上抜ける。
    • ショート: 高速EMAが低速EMAを下抜ける。
  • ロング/ショート: 両方向対応。
  • エグジット条件:
    • 反対方向のクロスオーバーで既存ポジションを閉じる。
  • ストップ: 明示的なストップロスまたはテイクプロフィットは使用しない。
  • デフォルト値:
    • FastLength = 7
    • SlowLength = 7
    • EnableLong = true
    • EnableShort = true
    • CandleType = 12時間の時間軸
  • フィルター:
    • カテゴリ: トレンドフォロー
    • 方向: 両方
    • インジケーター: Moving averages
    • ストップ: なし
    • 複雑さ: 基本
    • 時間軸: 任意
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;

/// <summary>
/// Double smoothed moving average crossover strategy.
/// </summary>
public class MaByMaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _minSpreadPercent;
	private readonly StrategyParam<int> _cooldownBars;

	private bool _isInitialized;
	private bool _wasFastBelowSlow;
	private int _cooldownRemaining;

	public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
	public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public decimal MinSpreadPercent { get => _minSpreadPercent.Value; set => _minSpreadPercent.Value = value; }
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }

	public MaByMaStrategy()
	{
		_fastLength = Param(nameof(FastLength), 7)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA Length", "Period for fast EMA", "Indicator");

		_slowLength = Param(nameof(SlowLength), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA Length", "Period for slow EMA", "Indicator");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");

		_minSpreadPercent = Param(nameof(MinSpreadPercent), 0.003m)
			.SetDisplay("Minimum Spread %", "Minimum normalized spread between fast and slow EMA values", "Filters");

		_cooldownBars = Param(nameof(CooldownBars), 6)
			.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_isInitialized = false;
		_wasFastBelowSlow = false;
		_cooldownRemaining = 0;
	}

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

		var fastMa = new ExponentialMovingAverage { Length = FastLength };
		var slowMa = new ExponentialMovingAverage { Length = SlowLength };

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

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

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

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var spreadPercent = slowValue != 0m ? Math.Abs(fastValue - slowValue) / slowValue : 0m;
		if (!_isInitialized)
		{
			_wasFastBelowSlow = fastValue < slowValue;
			_isInitialized = true;
			return;
		}

		var isFastBelowSlow = fastValue < slowValue;
		if (_cooldownRemaining == 0 && spreadPercent >= MinSpreadPercent)
		{
			if (_wasFastBelowSlow && !isFastBelowSlow && Position <= 0)
			{
				if (Position < 0)
					BuyMarket();

				BuyMarket();
				_cooldownRemaining = CooldownBars;
			}
			else if (!_wasFastBelowSlow && isFastBelowSlow && Position >= 0)
			{
				if (Position > 0)
					SellMarket();

				SellMarket();
				_cooldownRemaining = CooldownBars;
			}
		}

		_wasFastBelowSlow = isFastBelowSlow;
	}
}