Ver no GitHub

Estratégia MARE5.1 com Média Móvel Deslocada

Visão Geral

A Estratégia MARE5.1 com Média Móvel Deslocada é um port direto do assessor especialista original do MetaTrader 5 "MARE5.1" para a API de alto nível do StockSharp. O sistema monitora velas de um minuto (configuráveis) e compara duas médias móveis simples (SMA) que compartilham um deslocamento configurável para frente. A lógica busca padrões de cruzamento confirmados por relações históricas de SMA e a direção da última vela completada.

Lógica de Trading

  • A estratégia usa duas SMAs: uma SMA rápida e uma SMA lenta. Ambas são deslocadas para frente pelo mesmo número de barras, replicando o comportamento do assessor especialista original.
  • Uma posição vendida é aberta quando tudo o seguinte é verdadeiro:
    1. A SMA lenta está pelo menos um passo de preço acima da SMA rápida na vela atual.
    2. Duas velas atrás, a SMA rápida estava pelo menos um passo de preço acima da SMA lenta.
    3. Cinco velas atrás, a SMA rápida estava pelo menos um passo de preço acima da SMA lenta.
    4. A vela completada mais recente (barra anterior) é de baixa.
  • Uma posição comprada é aberta quando o padrão oposto ocorre:
    1. A SMA rápida está pelo menos um passo de preço acima da SMA lenta na vela atual.
    2. Duas velas atrás, a SMA lenta estava pelo menos um passo de preço acima da SMA rápida.
    3. Cinco velas atrás, a SMA lenta estava pelo menos um passo de preço acima da SMA rápida.
    4. A vela completada mais recente (barra anterior) é de alta.
  • Apenas uma posição pode estar aberta de cada vez. O tamanho padrão da ordem vem do parâmetro TradeVolume.
  • O trading só é permitido entre as horas de sessão configuradas (inclusive). Esta janela replica o filtro baseado em horas do assessor especialista original.

Gestão de Risco

A estratégia espelha as distâncias fixas originais de take profit e stop-loss. Elas são definidas em "pips" (pontos ajustados para instrumentos de três e cinco dígitos) e convertidos em unidades de preço absolutas quando a estratégia começa. As ordens de proteção são gerenciadas através de StartProtection com saídas de ordens de mercado.

Indicadores e Dados

  • SMA rápida – comprimento definido por FastPeriod.
  • SMA lenta – comprimento definido por SlowPeriod.
  • Fonte de dados – por padrão velas de um minuto, mas qualquer tipo de vela suportado pelo StockSharp pode ser selecionado através do parâmetro CandleType.

Parâmetros

Nome Valor padrão Descrição
TradeVolume 0.01 Volume da ordem usado para entradas.
TakeProfitPips 35 Distância do take profit em pips ajustados. Definir como zero para desabilitar.
StopLossPips 55 Distância do stop-loss em pips ajustados. Definir como zero para desabilitar.
FastPeriod 14 Período da SMA rápida.
SlowPeriod 79 Período da SMA lenta.
MovingAverageShift 4 Deslocamento para frente (em barras) aplicado a ambas as SMAs.
SessionOpenHour 2 Início da janela de trading permitida (0–23, inclusive).
SessionCloseHour 3 Fim da janela de trading permitida (0–23, inclusive). Deve ser maior que SessionOpenHour.
CandleType Velas de 1 minuto Tipo de dados de velas usado pela estratégia.

Notas

  • Os sinais são avaliados em velas completadas. Valores históricos de SMA são armazenados internamente para replicar as comparações baseadas em índice do código MQL original.
  • O valor do passo de preço do instrumento ativo é usado ao comparar diferenças de SMA para garantir que a distância necessária seja pelo menos um tick.
  • Os níveis de stop-loss e take profit dependem do passo de preço do instrumento. Para instrumentos de três e cinco decimais, o tamanho do pip é automaticamente expandido dez vezes, correspondendo ao comportamento do MetaTrader.
  • Nenhum dimensionamento automático de posição é implementado. A estratégia aguarda o fechamento de todas as posições abertas antes de procurar o próximo sinal de entrada.
  • Este repositório contém apenas a implementação em C#; não há port em Python para esta estratégia.
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>
/// MARE5.1 strategy that trades shifted SMA crossovers with time filtering.
/// </summary>
public class Mare51Strategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _movingAverageShift;
	private readonly StrategyParam<int> _sessionOpenHour;
	private readonly StrategyParam<int> _sessionCloseHour;
	private readonly StrategyParam<DataType> _candleType;

	private SMA _fastSma;
	private SMA _slowSma;
	private decimal?[] _fastBuffer;
	private decimal?[] _slowBuffer;
	private ICandleMessage _previousCandle;
	private decimal _pipSize;

	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int MovingAverageShift
	{
		get => _movingAverageShift.Value;
		set => _movingAverageShift.Value = value;
	}

	public int SessionOpenHour
	{
		get => _sessionOpenHour.Value;
		set => _sessionOpenHour.Value = value;
	}

	public int SessionCloseHour
	{
		get => _sessionCloseHour.Value;
		set => _sessionCloseHour.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public Mare51Strategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 0.01m)
			.SetDisplay("Volume", "Default order volume", "Trading")
			.SetGreaterThanZero();

		_takeProfitPips = Param(nameof(TakeProfitPips), 35m)
			.SetDisplay("Take Profit (pips)", "Take profit distance in adjusted pips", "Risk")
			.SetNotNegative();

		_stopLossPips = Param(nameof(StopLossPips), 55m)
			.SetDisplay("Stop Loss (pips)", "Stop loss distance in adjusted pips", "Risk")
			.SetNotNegative();

		_fastPeriod = Param(nameof(FastPeriod), 14)
			.SetDisplay("Fast Period", "Fast SMA period", "Indicators")
			.SetGreaterThanZero();

		_slowPeriod = Param(nameof(SlowPeriod), 20)
			.SetDisplay("Slow Period", "Slow SMA period", "Indicators")
			.SetGreaterThanZero();

		_movingAverageShift = Param(nameof(MovingAverageShift), 1)
			.SetDisplay("MA Shift", "Forward shift applied to both SMAs", "Indicators")
			.SetNotNegative();

		_sessionOpenHour = Param(nameof(SessionOpenHour), 0)
			.SetDisplay("Session Open Hour", "Inclusive start hour for trading", "Session")
			.SetRange(0, 23);

		_sessionCloseHour = Param(nameof(SessionCloseHour), 23)
			.SetDisplay("Session Close Hour", "Inclusive end hour for trading", "Session")
			.SetRange(0, 23);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Candle data type", "Data");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();

		_fastSma = null;
		_slowSma = null;
		_fastBuffer = null;
		_slowBuffer = null;
		_previousCandle = null;
		_pipSize = 0m;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		if (SessionOpenHour >= SessionCloseHour)
			throw new InvalidOperationException("SessionOpenHour must be less than SessionCloseHour.");

		Volume = TradeVolume;

		_fastSma = new SMA { Length = FastPeriod };
		_slowSma = new SMA { Length = SlowPeriod };

		_fastBuffer = new decimal?[MovingAverageShift + 6];
		_slowBuffer = new decimal?[MovingAverageShift + 6];

		_pipSize = CalculatePipSize();
		var takeProfitUnit = TakeProfitPips > 0m
			? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute)
			: new Unit(0m);
		var stopLossUnit = StopLossPips > 0m
			? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute)
			: new Unit(0m);

		StartProtection(stopLossUnit, takeProfitUnit);

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

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

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

		if (_fastBuffer == null || _slowBuffer == null)
			return;

		// Shift raw SMA values so we can later access shifted indexes.
		for (var i = _fastBuffer.Length - 1; i > 0; i--)
		{
			_fastBuffer[i] = _fastBuffer[i - 1];
			_slowBuffer[i] = _slowBuffer[i - 1];
		}

		_fastBuffer[0] = fastValue;
		_slowBuffer[0] = slowValue;

		var previousCandle = _previousCandle;
		_previousCandle = candle;

		//if (!IsFormedAndOnlineAndAllowTrading())
		//	return;

		if (previousCandle == null)
			return;

		if (_fastSma == null || _slowSma == null)
			return;

		if (!_fastSma.IsFormed || !_slowSma.IsFormed)
			return;

		var fast0 = GetShiftedValue(_fastBuffer, 0);
		var fast2 = GetShiftedValue(_fastBuffer, 2);
		var fast5 = GetShiftedValue(_fastBuffer, 5);
		var slow0 = GetShiftedValue(_slowBuffer, 0);
		var slow2 = GetShiftedValue(_slowBuffer, 2);
		var slow5 = GetShiftedValue(_slowBuffer, 5);

		if (fast0 is not decimal f0 || fast2 is not decimal f2 || fast5 is not decimal f5 ||
			slow0 is not decimal s0 || slow2 is not decimal s2 || slow5 is not decimal s5)
		{
			return;
		}

		if (!IsWithinSession(candle.OpenTime))
			return;

		var bearishPrevious = previousCandle.ClosePrice < previousCandle.OpenPrice;
		var bullishPrevious = previousCandle.ClosePrice > previousCandle.OpenPrice;

		var sellSignal = f5 >= s5 && f2 < s2 && f0 < s0 && bearishPrevious;
		var buySignal = f5 <= s5 && f2 > s2 && f0 > s0 && bullishPrevious;

		if (Position != 0)
			return;

		if (sellSignal)
		{
			// Enter short when slow SMA overtakes the fast SMA and previous bars confirm the reversal.
			SellMarket();
		}
		else if (buySignal)
		{
			// Enter long when fast SMA overtakes the slow SMA and previous bars confirm the reversal.
			BuyMarket();
		}
	}

	private decimal? GetShiftedValue(decimal?[] buffer, int index)
	{
		var targetIndex = index + MovingAverageShift;
		if (buffer == null)
			return null;
		if (targetIndex < 0 || targetIndex >= buffer.Length)
			return null;
		return buffer[targetIndex];
	}

	private bool IsWithinSession(DateTimeOffset time)
	{
		var hour = time.Hour;
		return hour >= SessionOpenHour && hour <= SessionCloseHour;
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return 1m;

		var scale = GetDecimalScale(step);
		return (scale == 3 || scale == 5) ? step * 10m : step;
	}

	private static int GetDecimalScale(decimal value)
	{
		var bits = decimal.GetBits(value);
		return (bits[3] >> 16) & 0xFF;
	}
}