Ver en GitHub

Estrategia de Cruce de MA 5/8

Descripción general

La estrategia de Cruce de MA 5/8 es un port de StockSharp del asesor experto de MetaTrader "5_8 MACross". Compara una media móvil exponencial (EMA) rápida calculada sobre precios de cierre con una EMA más lenta calculada sobre precios de apertura. El sistema actúa sobre el cruce entre las dos medias y se puede aplicar a cualquier símbolo y marco temporal que proporcione velas estándar basadas en tiempo.

Indicadores

  • EMA rápida – longitud configurable (por defecto 5) calculada desde el precio de cierre de la vela.
  • EMA lenta – longitud configurable (por defecto 8) calculada desde el precio de apertura de la vela.

Lógica de trading

  1. La estrategia procesa solo velas terminadas para evitar datos parciales.
  2. Una entrada larga se genera cuando la EMA rápida estaba por debajo o igual a la EMA lenta en la vela anterior y la cruza por encima en la vela actual.
  3. Una entrada corta se genera cuando la EMA rápida estaba por encima o igual a la EMA lenta en la vela anterior y la cruza por debajo en la vela actual.
  4. Cuando aparece una señal, la estrategia revierte su exposición: cierra cualquier posición abierta y envía una orden de mercado dimensionada para terminar con contratos Volume en la nueva dirección.

Gestión de riesgo

  • Take profit – objetivo opcional expresado en puntos de precio. El tamaño del punto se deriva del paso de precio del instrumento; para cotizaciones de tres y cinco dígitos el valor se multiplica automáticamente por 10 para emular el manejo de pips de MetaTrader.
  • Stop loss – stop protector opcional, también expresado en puntos de precio desde el precio de entrada.
  • Trailing stop – distancia opcional en puntos de precio. Después de que se abre una posición, la estrategia rastrea el máximo más alto (para largos) o el mínimo más bajo (para cortos) y mueve el stop solo en la dirección rentable. Si no se especifica un stop loss inicial, el trailing stop igualmente iniciará protección inmediatamente después de la entrada.
  • Si el take profit o el (trailing) stop se alcanza en un precio de cierre, la posición se cierra a mercado.

Parámetros

Nombre Descripción Valor predeterminado
FastLength Período de la EMA rápida (basada en cierre). 5
SlowLength Período de la EMA lenta (basada en apertura). 8
TakeProfitPoints Distancia del take profit en puntos de precio. 40
StopLossPoints Distancia del stop loss en puntos de precio (0 deshabilita el stop). 0
TrailingStopPoints Distancia del trailing stop en puntos de precio (0 deshabilita el trailing). 0
CandleType Tipo/marco temporal de vela usado para los cálculos. Marco temporal de 1 minuto
Volume Volumen de orden heredado de la clase base Strategy. 0.1

Diferencias comparadas con la versión MQL

  • Las comprobaciones de cobertura específicas de MetaTrader y las llamadas de información de cuenta se omiten porque StockSharp gestiona la contabilidad de posiciones de manera diferente.
  • Las señales se evalúan en velas cerradas en lugar del primer tick de una nueva barra; esto mejora la estabilidad en entornos orientados a eventos.
  • La lógica de trailing usa el máximo/mínimo de la vela para avanzar el stop en lugar del tick actual de bid/ask, proporcionando un comportamiento determinista para el procesamiento histórico.

Notas de uso

  • Configurar Volume en las propiedades de la estrategia para que coincida con el tamaño de lote deseado.
  • Combinar la estrategia con los módulos de protección de StockSharp o filtros adicionales si se requiere gestión de riesgo a nivel de cartera.
  • La estrategia no coloca órdenes pendientes; todas las entradas y salidas se ejecutan con órdenes de mercado generadas por la lógica de cruce y riesgo anterior.
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>
/// 5/8 exponential moving average crossover strategy converted from MetaTrader.
/// Uses a fast EMA on close prices and a slower EMA on open prices with manual stop, take profit, and trailing logic.
/// </summary>
public class FiveEightMaCrossStrategy : Strategy
{
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _fastMa = null!;
	private ExponentialMovingAverage _slowMa = null!;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _isInitialized;

	private decimal _pointValue;
	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;
	private decimal _trailDistance;
	private decimal _maxPrice;
	private decimal _minPrice;

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

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

	/// <summary>
	/// Take profit distance expressed in price points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Trailing stop distance expressed in price points.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

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

	/// <summary>
	/// Initialize <see cref="FiveEightMaCrossStrategy"/>.
	/// </summary>
	public FiveEightMaCrossStrategy()
	{
		_fastLength = Param(nameof(FastLength), 8)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA Length", "Length of the EMA calculated on closing prices", "Indicators")
			
			.SetOptimize(3, 20, 1);

		_slowLength = Param(nameof(SlowLength), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA Length", "Length of the EMA calculated on opening prices", "Indicators")
			
			.SetOptimize(5, 30, 1);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 40m)
			.SetDisplay("Take Profit (points)", "Take profit distance expressed in price points", "Risk Management")
			.SetNotNegative()
			
			.SetOptimize(10m, 100m, 10m);

		_stopLossPoints = Param(nameof(StopLossPoints), 0m)
			.SetDisplay("Stop Loss (points)", "Stop loss distance expressed in price points", "Risk Management")
			.SetNotNegative()
			
			.SetOptimize(0m, 100m, 10m);

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 0m)
			.SetDisplay("Trailing Stop (points)", "Trailing stop distance expressed in price points", "Risk Management")
			.SetNotNegative()
			
			.SetOptimize(0m, 100m, 10m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle type used for calculations", "General");

		Volume = 0.1m;
	}

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

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

	_fastMa = null!;
	_slowMa = null!;
	_prevFast = 0m;
	_prevSlow = 0m;
	_isInitialized = false;
	_pointValue = 0m;
	ResetPositionState();
	}

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

	_fastMa = new EMA { Length = FastLength };
	_slowMa = new EMA { Length = SlowLength };

	_pointValue = CalculatePointValue();

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

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

	private decimal CalculatePointValue()
	{
	var step = Security?.PriceStep;

	if (step == null || step.Value <= 0m)
	return 1m;

	var stepValue = step.Value;
	var stepDouble = (double)stepValue;

	if (stepDouble <= 0d)
	return stepValue;

	var digitsDouble = Math.Log10(1d / stepDouble);
	var digits = (int)Math.Round(digitsDouble, MidpointRounding.AwayFromZero);
	var multiplier = (digits == 3 || digits == 5) ? 10m : 1m;

	return stepValue * multiplier;
	}

	private void ProcessCandle(ICandleMessage candle)
	{
	// Process only finished candles to avoid repainting.
	if (candle.State != CandleStates.Finished)
	return;

	// Feed indicators with the corresponding price source.
	var fastValue = _fastMa.Process(new DecimalIndicatorValue(_fastMa, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
	var slowValue = _slowMa.Process(new DecimalIndicatorValue(_slowMa, candle.OpenPrice, candle.OpenTime) { IsFinal = true }).ToDecimal();

	if (!_fastMa.IsFormed || !_slowMa.IsFormed)
	{
	_prevFast = fastValue;
	_prevSlow = slowValue;
	return;
	}

	HandleRiskManagement(candle);

	// indicators are processed manually

	if (!_isInitialized)
	{
	_prevFast = fastValue;
	_prevSlow = slowValue;
	_isInitialized = true;
	return;
	}

	var crossUp = _prevFast <= _prevSlow && fastValue > slowValue;
	var crossDown = _prevFast >= _prevSlow && fastValue < slowValue;

	if (crossUp && Position <= 0)
	{
	EnterLong(candle);
	}
	else if (crossDown && Position >= 0)
	{
	EnterShort(candle);
	}

	_prevFast = fastValue;
	_prevSlow = slowValue;
	}

	private void EnterLong(ICandleMessage candle)
	{
	var positionVolume = Position < 0 ? Math.Abs(Position) : 0m;
	var volume = Volume + positionVolume;

	if (volume <= 0m)
	return;

	ResetPositionState();

	// Enter long position with volume including any short covering.
	BuyMarket(volume);

	_entryPrice = candle.ClosePrice;
	_takePrice = TakeProfitPoints > 0m ? _entryPrice + TakeProfitPoints * _pointValue : null;
	_stopPrice = StopLossPoints > 0m ? _entryPrice - StopLossPoints * _pointValue : null;
	_trailDistance = TrailingStopPoints > 0m ? TrailingStopPoints * _pointValue : 0m;
	_maxPrice = candle.HighPrice;
	_minPrice = candle.LowPrice;

	if (_trailDistance > 0m)
	{
	var trailStart = _entryPrice.Value - _trailDistance;
	if (_stopPrice == null || trailStart > _stopPrice.Value)
	_stopPrice = trailStart;
	}
	}

	private void EnterShort(ICandleMessage candle)
	{
	var positionVolume = Position > 0 ? Position : 0m;
	var volume = Volume + positionVolume;

	if (volume <= 0m)
	return;

	ResetPositionState();

	// Enter short position with volume including any long exit.
	SellMarket(volume);

	_entryPrice = candle.ClosePrice;
	_takePrice = TakeProfitPoints > 0m ? _entryPrice - TakeProfitPoints * _pointValue : null;
	_stopPrice = StopLossPoints > 0m ? _entryPrice + StopLossPoints * _pointValue : null;
	_trailDistance = TrailingStopPoints > 0m ? TrailingStopPoints * _pointValue : 0m;
	_maxPrice = candle.HighPrice;
	_minPrice = candle.LowPrice;

	if (_trailDistance > 0m)
	{
	var trailStart = _entryPrice.Value + _trailDistance;
	if (_stopPrice == null || trailStart < _stopPrice.Value)
	_stopPrice = trailStart;
	}
	}

	private void HandleRiskManagement(ICandleMessage candle)
	{
	if (Position > 0 && _entryPrice.HasValue)
	{
	// Update trailing stop using the highest price reached since entry.
	_maxPrice = Math.Max(_maxPrice, candle.HighPrice);

	if (_trailDistance > 0m)
	{
	var trailCandidate = _maxPrice - _trailDistance;
	if (_stopPrice == null || trailCandidate > _stopPrice.Value)
	_stopPrice = trailCandidate;
	}

	if (_takePrice.HasValue && candle.ClosePrice >= _takePrice.Value)
	{
	SellMarket(Math.Abs(Position));
	ResetPositionState();
	return;
	}

	if (_stopPrice.HasValue && candle.ClosePrice <= _stopPrice.Value)
	{
	SellMarket(Math.Abs(Position));
	ResetPositionState();
	return;
	}
	}
	else if (Position < 0 && _entryPrice.HasValue)
	{
	// Update trailing stop using the lowest price reached since entry.
	_minPrice = Math.Min(_minPrice, candle.LowPrice);

	if (_trailDistance > 0m)
	{
	var trailCandidate = _minPrice + _trailDistance;
	if (_stopPrice == null || trailCandidate < _stopPrice.Value)
	_stopPrice = trailCandidate;
	}

	if (_takePrice.HasValue && candle.ClosePrice <= _takePrice.Value)
	{
	BuyMarket(Math.Abs(Position));
	ResetPositionState();
	return;
	}

	if (_stopPrice.HasValue && candle.ClosePrice >= _stopPrice.Value)
	{
	BuyMarket(Math.Abs(Position));
	ResetPositionState();
	}
	}
	}

	private void ResetPositionState()
	{
	_entryPrice = null;
	_stopPrice = null;
	_takePrice = null;
	_trailDistance = 0m;
	_maxPrice = 0m;
	_minPrice = 0m;
	}
}