Auf GitHub ansehen

Color Momentum AMA-Strategie

Diese Strategie konvertiert den MetaTrader Expert Advisor Exp_ColorMomentum_AMA nach StockSharp. Sie berechnet das Preismomentum über einen konfigurierbaren Zeitraum und glättet es mit dem Kaufman Adaptive Moving Average (AMA). Handelssignale werden generiert, wenn das geglättete Momentum zwei aufeinanderfolgende Anstiege oder Rückgänge zeigt.

Logik

  • Long-Einstieg: Momentum AMA steigt zwei Kerzen hintereinander. Jede bestehende Short-Position wird vor dem Eröffnen einer neuen Long-Position geschlossen.
  • Short-Einstieg: Momentum AMA fällt zwei Kerzen hintereinander. Jede bestehende Long-Position wird vor dem Eröffnen einer neuen Short-Position geschlossen.
  • Entgegengesetzte Signale schließen aktuelle Positionen.

Parameter

  • Kerzentyp
  • Momentum-Periode
  • AMA-Periode
  • Schnelle Periode
  • Langsame Periode
  • Signalkerze
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>
/// Color Momentum AMA strategy.
/// Generates signals based on momentum smoothed by the Kaufman Adaptive Moving Average.
/// A long position is opened after two consecutive rises of the smoothed momentum, a short position is opened after two consecutive falls.
/// Opposite signals close existing positions.
/// </summary>
public class ColorMomentumAmaStrategy : Strategy
{
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<int> _amaPeriod;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<int> _signalCooldownBars;
	private readonly StrategyParam<DataType> _candleType;

	private Momentum _momentum = null!;
	private KaufmanAdaptiveMovingAverage _ama = null!;
	private decimal?[] _buffer = null!;
	private int _cooldownRemaining;

	/// <summary>
	/// Momentum lookback period.
	/// </summary>
	public int MomentumPeriod
	{
		get => _momentumPeriod.Value;
		set => _momentumPeriod.Value = value;
	}

	/// <summary>
	/// AMA smoothing length.
	/// </summary>
	public int AmaPeriod
	{
		get => _amaPeriod.Value;
		set => _amaPeriod.Value = value;
	}

	/// <summary>
	/// Fast period for AMA efficiency ratio.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow period for AMA efficiency ratio.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Number of bars back used for signal calculation.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	/// <summary>
	/// Bars to wait between trading actions.
	/// </summary>
	public int SignalCooldownBars
	{
		get => _signalCooldownBars.Value;
		set => _signalCooldownBars.Value = value;
	}

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

	public ColorMomentumAmaStrategy()
	{
		_momentumPeriod = Param(nameof(MomentumPeriod), 8)
		.SetGreaterThanZero()
		.SetDisplay("Momentum period", "Lookback period for momentum", "Indicator")
		
		.SetOptimize(5, 20, 1);

		_amaPeriod = Param(nameof(AmaPeriod), 9)
		.SetGreaterThanZero()
		.SetDisplay("AMA period", "Smoothing length for AMA", "Indicator")
		
		.SetOptimize(5, 30, 1);

		_fastPeriod = Param(nameof(FastPeriod), 2)
		.SetGreaterThanZero()
		.SetDisplay("Fast period", "Fast period of AMA", "Indicator")
		
		.SetOptimize(2, 10, 1);

		_slowPeriod = Param(nameof(SlowPeriod), 30)
		.SetGreaterThanZero()
		.SetDisplay("Slow period", "Slow period of AMA", "Indicator")
		
		.SetOptimize(20, 60, 5);

		_signalBar = Param(nameof(SignalBar), 2)
		.SetRange(1, 5)
		.SetDisplay("Signal bar", "Bar index used for signals", "Strategy")
		;

		_signalCooldownBars = Param(nameof(SignalCooldownBars), 6)
		.SetGreaterThanZero()
		.SetDisplay("Signal cooldown", "Bars to wait between reversals", "Strategy");

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

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

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

		_buffer = new decimal?[SignalBar + 3];
		_cooldownRemaining = 0;
	}

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

		_momentum = new Momentum { Length = MomentumPeriod };
		_ama = new KaufmanAdaptiveMovingAverage
		{
			Length = AmaPeriod,
			FastSCPeriod = FastPeriod,
			SlowSCPeriod = SlowPeriod
		};
		_buffer = new decimal?[SignalBar + 3];
		_cooldownRemaining = 0;

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

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

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

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		// Update AMA with the momentum value
		var amaResult = _ama.Process(momentumValue, candle.OpenTime, true);
		if (!_ama.IsFormed || amaResult.IsEmpty)
		return;
		var amaValue = amaResult.ToDecimal();

		// Maintain circular buffer of last values for signal evaluation
		for (var i = _buffer.Length - 1; i > 0; i--) _buffer[i] = _buffer[i - 1];
		_buffer[0] = amaValue;

		if (_buffer[SignalBar + 2] == null || _buffer[SignalBar + 1] == null) return;

		var v0 = _buffer[SignalBar]!.Value;
		var v1 = _buffer[SignalBar + 1]!.Value;
		var v2 = _buffer[SignalBar + 2]!.Value;

		// Evaluate trend direction using consecutive values
		var rising = v2 < v1 && v1 < v0;
		var falling = v2 > v1 && v1 > v0;

		if (_cooldownRemaining == 0 && rising && Position <= 0)
		{
			BuyMarket(Volume + Math.Abs(Position));
			_cooldownRemaining = SignalCooldownBars;
		}
		else if (_cooldownRemaining == 0 && falling && Position >= 0)
		{
			SellMarket(Volume + Math.Abs(Position));
			_cooldownRemaining = SignalCooldownBars;
		}
	}
}