Ver en GitHub

Estrategia de modo de precio de método cruzado MA

Descripción general

La estrategia MA Cross Method PriceMode es una adaptación directa StockSharp del MetaTrader 4 experto "MA_cross_Method_PriceMode". Combina dos medias móviles configurables y reacciona cada vez que la media rápida cruza la media lenta. Ambas líneas exponen las entradas originales de MetaTrader: período, método de suavizado (SMA, EMA, SMMA, LWMA), precio aplicado (cierre, apertura, máximo, mínimo, mediana, típico, ponderado) y desplazamiento horizontal. La estrategia funciona con cualquier instrumento que proporcione velas regulares basadas en el tiempo.

Indicadores

  • Promedio móvil rápido: longitud, método y fuente de precio configurables. El parámetro de cambio MetaTrader se reproduce almacenando en el buffer los valores del indicador completados y leyendo las barras del valor FirstShift.
  • Promedio móvil lento: longitud, método y fuente de precio configurables con la misma emulación de turno mediante almacenamiento en búfer.

Lógica de trading

  1. La estrategia se suscribe al tipo de vela seleccionado y procesa solo velas terminadas para evitar el repintado dentro de la barra.
  2. Para cada barra cerrada, alimenta ambas medias móviles con sus respectivos precios aplicados.
  3. Cuando ambos promedios producen valores finales, la estrategia evalúa dos condiciones:
    • Cruz alcista – la MA rápida estaba por debajo o igual a la MA lenta en la barra anterior y se mueve por encima de ella en la barra actual.
    • Cruz bajista – la MA rápida estaba por encima o igual a la MA lenta en la barra anterior y se mueve por debajo de ella en la barra actual.
  4. En un cruce alcista, la estrategia compra OrderVolume contratos. Si hay una posición corta abierta, el tamaño de la orden aumenta automáticamente para cubrir la posición corta y establecer la nueva exposición larga.
  5. En un cruce bajista, la estrategia vende OrderVolume contratos. Si una posición larga está abierta, el tamaño de la orden aumenta para cerrarla antes de establecer la posición corta.
  6. Se invoca StartProtection() para que se puedan agregar StockSharp módulos de protección si se desea (por ejemplo, asistentes de parada de pérdidas o de equilibrio).

Parámetros

Nombre Descripción Predeterminado
FirstPeriod Período de la media móvil rápida. 3
SecondPeriod Período de la media móvil lenta. 13
FirstMethod Método de suavizado utilizado para la media móvil rápida (Simple, Exponential, Smoothed, LinearWeighted). Simple
SecondMethod Método de suavizado utilizado para la media móvil lenta. LinearWeighted
FirstPriceMode Precio aplicado para la media móvil rápida (Close, Open, High, Low, Median, Typical, Weighted). Close
SecondPriceMode Precio aplicado para la media móvil lenta. Median
FirstShift Desplazamiento horizontal (en barras) aplicado a la media móvil rápida. 0
SecondShift Desplazamiento horizontal (en barras) aplicado a la media móvil lenta. 0
OrderVolume Volumen de orden base utilizado para nuevas posiciones. 0.1
CandleType Tipo de vela/plazo de tiempo procesado por la estrategia. velas de 5 minutos

Diferencias en comparación con la versión MQL

  • La iteración de la orden MetaTrader (OrdersTotal, OrderSelect, OrderClose) se reemplaza por el uso directo de la propiedad StockSharp Strategy.Position y las órdenes de mercado dimensionadas para revertir la exposición cuando sea necesario.
  • El indicador de "nueva barra" MetaTrader no es necesario: ProcessCandle se ejecuta exactamente una vez por vela terminada, lo que garantiza el mismo comportamiento una vez por barra sin sondeo a nivel de tick.
  • El manejo de turnos MA se implementa con buffers compactos que contienen los últimos valores shift + 2 para cada promedio. Esto refleja el desplazamiento del indicador sin depender de referencias anteriores prohibidas del indicador (GetValue).
  • La estrategia es independiente del corredor; Se pueden adjuntar ayudantes de gestión de riesgos a través de StartProtection() en lugar de los argumentos fijos de parada/límite MetaTrader.

Notas de uso

  • Elija la duración de la vela que coincida con el período de tiempo original (por ejemplo, M5 o H1). Se pueden proporcionar períodos de tiempo personalizados editando CandleType en los parámetros de la estrategia.
  • Establecer FirstShift o SecondShift en un valor positivo retrasa el cruce efectivo en esa misma cantidad de barras completadas, al igual que la entrada de desplazamiento horizontal en MetaTrader.
  • El modo de precio Weighted reproduce la fórmula (High + Low + 2 * Close) / 4 de MetaTrader. Los modos mediano y típico siguen las definiciones estándar (High + Low) / 2 y (High + Low + Close) / 3.
  • Dado que cada orden es una orden de mercado, asegúrese de que la configuración de la cuenta tolere el volumen y el deslizamiento solicitados.
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>
/// Moving average crossover strategy converted from the MetaTrader script "MA_cross_Method_PriceMode".
/// Allows selecting the smoothing method, applied price and horizontal shift for each average.
/// </summary>
public class MaCrossMethodPriceModeStrategy : Strategy
{
	private readonly StrategyParam<int> _firstPeriod;
	private readonly StrategyParam<int> _secondPeriod;
	private readonly StrategyParam<MaMethods> _firstMethod;
	private readonly StrategyParam<MaMethods> _secondMethod;
	private readonly StrategyParam<AppliedPriceModes> _firstPriceMode;
	private readonly StrategyParam<AppliedPriceModes> _secondPriceMode;
	private readonly StrategyParam<int> _firstShift;
	private readonly StrategyParam<int> _secondShift;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<DataType> _candleType;

	private DecimalLengthIndicator _firstMa = null!;
	private DecimalLengthIndicator _secondMa = null!;

	private readonly List<decimal> _firstValues = new();
	private readonly List<decimal> _secondValues = new();

	/// <summary>
	/// Initializes a new instance of <see cref="MaCrossMethodPriceModeStrategy"/>.
	/// </summary>
	public MaCrossMethodPriceModeStrategy()
	{
		_firstPeriod = Param(nameof(FirstPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA Period", "Length of the first moving average.", "Indicators")
			
			.SetOptimize(2, 50, 1);

		_secondPeriod = Param(nameof(SecondPeriod), 13)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA Period", "Length of the second moving average.", "Indicators")
			
			.SetOptimize(5, 100, 1);

		_firstMethod = Param(nameof(FirstMethod), MaMethods.Simple)
			.SetDisplay("Fast MA Method", "Smoothing method applied to the first moving average.", "Indicators")
			;

		_secondMethod = Param(nameof(SecondMethod), MaMethods.LinearWeighted)
			.SetDisplay("Slow MA Method", "Smoothing method applied to the second moving average.", "Indicators")
			;

		_firstPriceMode = Param(nameof(FirstPriceMode), AppliedPriceModes.Close)
			.SetDisplay("Fast MA Price", "Price source used for the first moving average.", "Indicators")
			;

		_secondPriceMode = Param(nameof(SecondPriceMode), AppliedPriceModes.Median)
			.SetDisplay("Slow MA Price", "Price source used for the second moving average.", "Indicators")
			;

		_firstShift = Param(nameof(FirstShift), 0)
			.SetNotNegative()
			.SetDisplay("Fast MA Shift", "Horizontal shift (in bars) applied to the first moving average.", "Indicators");

		_secondShift = Param(nameof(SecondShift), 0)
			.SetNotNegative()
			.SetDisplay("Slow MA Shift", "Horizontal shift (in bars) applied to the second moving average.", "Indicators");

		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Base order volume used for new entries.", "Trading")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for price processing.", "General");
	}

	/// <summary>
	/// Period of the first moving average.
	/// </summary>
	public int FirstPeriod
	{
		get => _firstPeriod.Value;
		set => _firstPeriod.Value = value;
	}

	/// <summary>
	/// Period of the second moving average.
	/// </summary>
	public int SecondPeriod
	{
		get => _secondPeriod.Value;
		set => _secondPeriod.Value = value;
	}

	/// <summary>
	/// Smoothing method applied to the first moving average.
	/// </summary>
	public MaMethods FirstMethod
	{
		get => _firstMethod.Value;
		set => _firstMethod.Value = value;
	}

	/// <summary>
	/// Smoothing method applied to the second moving average.
	/// </summary>
	public MaMethods SecondMethod
	{
		get => _secondMethod.Value;
		set => _secondMethod.Value = value;
	}

	/// <summary>
	/// Applied price mode for the first moving average.
	/// </summary>
	public AppliedPriceModes FirstPriceMode
	{
		get => _firstPriceMode.Value;
		set => _firstPriceMode.Value = value;
	}

	/// <summary>
	/// Applied price mode for the second moving average.
	/// </summary>
	public AppliedPriceModes SecondPriceMode
	{
		get => _secondPriceMode.Value;
		set => _secondPriceMode.Value = value;
	}

	/// <summary>
	/// Shift (in bars) applied to the first moving average values.
	/// </summary>
	public int FirstShift
	{
		get => _firstShift.Value;
		set => _firstShift.Value = value;
	}

	/// <summary>
	/// Shift (in bars) applied to the second moving average values.
	/// </summary>
	public int SecondShift
	{
		get => _secondShift.Value;
		set => _secondShift.Value = value;
	}

	/// <summary>
	/// Base order volume used for new positions.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Candle type (timeframe) processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

		_firstMa = null!;
		_secondMa = null!;
		_firstValues.Clear();
		_secondValues.Clear();
	}

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

		_firstMa = CreateMovingAverage(FirstMethod, FirstPeriod);
		_secondMa = CreateMovingAverage(SecondMethod, SecondPeriod);

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

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

		StartProtection(null, null);
	}

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

		UpdateBuffer(_firstValues, firstDecimal, FirstShift);
		UpdateBuffer(_secondValues, secondDecimal, SecondShift);

		if (!TryGetShiftedValues(_firstValues, FirstShift, out var firstCurrent, out var firstPrevious))
			return;

		if (!TryGetShiftedValues(_secondValues, SecondShift, out var secondCurrent, out _))
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var bullishCross = IsBullishCross(firstPrevious, firstCurrent, secondCurrent);
		var bearishCross = IsBearishCross(firstPrevious, firstCurrent, secondCurrent);

		if (bullishCross && OrderVolume > 0m && Position <= 0m)
		{
			var volumeToBuy = OrderVolume + (Position < 0m ? Math.Abs(Position) : 0m);
			BuyMarket(volumeToBuy);
		}
		else if (bearishCross && OrderVolume > 0m && Position >= 0m)
		{
			var volumeToSell = OrderVolume + (Position > 0m ? Position : 0m);
			SellMarket(volumeToSell);
		}
	}

	private static void UpdateBuffer(List<decimal> buffer, decimal value, int shift)
	{
		buffer.Add(value);

		var maxCount = Math.Max(shift + 2, 2);
		while (buffer.Count > maxCount)
		{
			buffer.RemoveAt(0);
		}
	}

	private static bool TryGetShiftedValues(IReadOnlyList<decimal> buffer, int shift, out decimal current, out decimal previous)
	{
		var currentIndex = buffer.Count - 1 - shift;
		var previousIndex = buffer.Count - 2 - shift;

		if (previousIndex < 0 || currentIndex < 0 || currentIndex >= buffer.Count)
		{
			current = default;
			previous = default;
			return false;
		}

		current = buffer[currentIndex];
		previous = buffer[previousIndex];
		return true;
	}

	private static bool IsBullishCross(decimal previousFast, decimal currentFast, decimal currentSlow)
	{
		return (previousFast <= currentSlow && currentFast > currentSlow)
			|| (previousFast < currentSlow && currentFast >= currentSlow);
	}

	private static bool IsBearishCross(decimal previousFast, decimal currentFast, decimal currentSlow)
	{
		return (previousFast >= currentSlow && currentFast < currentSlow)
			|| (previousFast > currentSlow && currentFast <= currentSlow);
	}

	private static decimal SelectPrice(ICandleMessage candle, AppliedPriceModes mode)
	{
		return mode switch
		{
			AppliedPriceModes.Close => candle.ClosePrice,
			AppliedPriceModes.Open => candle.OpenPrice,
			AppliedPriceModes.High => candle.HighPrice,
			AppliedPriceModes.Low => candle.LowPrice,
			AppliedPriceModes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			AppliedPriceModes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
			AppliedPriceModes.Weighted => (candle.HighPrice + candle.LowPrice + (2m * candle.ClosePrice)) / 4m,
			_ => candle.ClosePrice
		};
	}

	private static DecimalLengthIndicator CreateMovingAverage(MaMethods method, int period)
	{
		return method switch
		{
			MaMethods.Simple => new SMA { Length = period },
			MaMethods.Exponential => new EMA { Length = period },
			MaMethods.Smoothed => new SmoothedMovingAverage { Length = period },
			MaMethods.LinearWeighted => new WeightedMovingAverage { Length = period },
			_ => new SMA { Length = period }
		};
	}

	/// <summary>
	/// Moving average smoothing methods that mirror the MetaTrader inputs.
	/// </summary>
	public enum MaMethods
	{
		Simple,
		Exponential,
		Smoothed,
		LinearWeighted
	}

	/// <summary>
	/// Applied price options equivalent to the MetaTrader constants.
	/// </summary>
	public enum AppliedPriceModes
	{
		Close,
		Open,
		High,
		Low,
		Median,
		Typical,
		Weighted
	}
}