Ver no GitHub

Estratégia de Dois MA Quatro Níveis

Esta estratégia replica o especialista MetaTrader "2MA_4Level" usando a API de alto nível do StockSharp. Ela opera um único instrumento com duas médias móveis suavizadas (SMMA) calculadas sobre o preço mediano e observa cinco zonas relativas de cruzamento entre as curvas rápida e lenta. As entradas só são permitidas quando não há posição aberta, e cada operação é protegida por offsets de stop-loss e take-profit em pips.

Lógica

  • Uma SMMA rápida e uma lenta são calculadas na série de velas selecionada (padrão 50 e 130 períodos).
  • Os valores anteriores e atuais da SMMA na vela completada são avaliados para detectar um cruzamento.
  • O cruzamento é verificado contra cinco limiares construídos a partir da MA lenta:
    • a MA lenta pura (sem offset),
    • MA lenta + pips de MostTopLevel,
    • MA lenta + pips de TopLevel,
    • MA lenta - pips de LowermostLevel,
    • MA lenta - pips de LowerLevel.
  • Quando a MA rápida cruza acima de qualquer limiar, uma posição comprada é aberta (se flat). Um cruzamento abaixo de qualquer limiar abre uma posição vendida.
  • Os níveis de stop-loss e take-profit são anexados através de StartProtection usando o valor de pip do instrumento (Security.PriceStep).

A estratégia nunca piramida posições: uma nova operação só pode ser aberta após o fechamento da anterior por stop ou objetivo.

Parâmetros

Parâmetro Padrão Descrição
FastPeriod 50 Comprimento da média móvel suavizada rápida. Deve ser menor que SlowPeriod.
SlowPeriod 130 Comprimento da média móvel suavizada lenta.
MostTopLevel 500 Offset superior (em pips) para a confirmação altista/baixista mais ampla. Deve ser maior que TopLevel.
TopLevel 250 Offset superior (em pips) para a confirmação altista/baixista secundária.
LowerLevel 250 Offset inferior (em pips) para a confirmação baixista/altista secundária. Deve ser menor que LowermostLevel.
LowermostLevel 500 Offset inferior (em pips) para a confirmação baixista/altista mais ampla.
TakeProfitPips 55 Distância da entrada ao take-profit, expressa em pips.
StopLossPips 260 Distância da entrada ao stop-loss, expressa em pips.
CandleType Período de 15 minutos Série de velas usada para os cálculos de SMMA e processamento de sinais.

Detalhes de implementação

  • O preço mediano ((High + Low) / 2) alimenta ambas as SMMAs, correspondendo à configuração do MT5 que usa PRICE_MEDIAN.
  • O teste de cruzamento compara a última vela completada com a anterior, eliminando qualquer dependência de barras parcialmente formadas.
  • StartProtection conecta o stop-loss e take-profit uma única vez na inicialização, de modo que cada ordem herda automaticamente os limites de risco configurados.
  • A estratégia para durante OnStarted se combinações de parâmetros inválidas forem fornecidas (ex.: FastPeriod >= SlowPeriod).

Notas de uso

  1. Vincule a estratégia a um instrumento com PriceStep definido; caso contrário, a conversão de pip recai para o valor 1.
  2. Adequada para contas de hedge no MT5; no StockSharp comporta-se da mesma forma ao garantir apenas uma posição aberta por vez.
  3. Os hooks de otimização (SetCanOptimize) estão habilitados para ambos os períodos de MA, permitindo executar varreduras de parâmetros diretamente do otimizador StockSharp.
  4. Como a estratégia depende exclusivamente de saídas por stop-loss e take-profit, garanta que as distâncias configuradas estejam alinhadas com a volatilidade do instrumento para evitar exposição prolongada.

Arquivos

  • CS/TwoMaFourLevelStrategy.cs – Implementação em C# da lógica de trading.
  • README_ru.md – Documentação em russo.
  • README_zh.md – Documentação em chinês.
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>
/// Two smoothed moving average crossover strategy with level offsets.
/// </summary>
public class TwoMaFourLevelStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _mostTopLevel;
	private readonly StrategyParam<int> _topLevel;
	private readonly StrategyParam<int> _lowerLevel;
	private readonly StrategyParam<int> _lowermostLevel;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<DataType> _candleType;

	private SmoothedMovingAverage _fastMa = null!;
	private SmoothedMovingAverage _slowMa = null!;
	private decimal? _prevFast;
	private decimal? _prevSlow;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int MostTopLevel { get => _mostTopLevel.Value; set => _mostTopLevel.Value = value; }
	public int TopLevel { get => _topLevel.Value; set => _topLevel.Value = value; }
	public int LowerLevel { get => _lowerLevel.Value; set => _lowerLevel.Value = value; }
	public int LowermostLevel { get => _lowermostLevel.Value; set => _lowermostLevel.Value = value; }
	public int TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
	public int StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public TwoMaFourLevelStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Period of the fast smoothed MA", "Moving Averages")
			.SetOptimize(20, 150, 5);

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Period of the slow smoothed MA", "Moving Averages")
			.SetOptimize(60, 300, 5);

		_mostTopLevel = Param(nameof(MostTopLevel), 2)
			.SetGreaterThanZero()
			.SetDisplay("Extreme Upper Level", "Highest positive offset in points", "Levels");

		_topLevel = Param(nameof(TopLevel), 1)
			.SetGreaterThanZero()
			.SetDisplay("Upper Level", "Second positive offset in points", "Levels");

		_lowerLevel = Param(nameof(LowerLevel), 1)
			.SetGreaterThanZero()
			.SetDisplay("Lower Level", "Second negative offset in points", "Levels");

		_lowermostLevel = Param(nameof(LowermostLevel), 2)
			.SetGreaterThanZero()
			.SetDisplay("Extreme Lower Level", "Largest negative offset in points", "Levels");

		_takeProfitPips = Param(nameof(TakeProfitPips), 500)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pips)", "Distance to take profit", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 1000)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Distance to stop loss", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for analysis", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		if (Security != null)
			yield return (Security, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

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

		if (FastPeriod >= SlowPeriod)
		{
			this.LogError("FastPeriod must be less than SlowPeriod.");
			Stop();
			return;
		}

		if (MostTopLevel <= TopLevel)
		{
			this.LogError("MostTopLevel must be greater than TopLevel.");
			Stop();
			return;
		}

		if (LowerLevel >= LowermostLevel)
		{
			this.LogError("LowerLevel must be less than LowermostLevel.");
			Stop();
			return;
		}

		_fastMa = new SmoothedMovingAverage { Length = FastPeriod };
		_slowMa = new SmoothedMovingAverage { Length = SlowPeriod };

		var pip = Security?.PriceStep ?? 1m;

		StartProtection(
			takeProfit: new Unit(TakeProfitPips * pip, UnitTypes.Absolute),
			stopLoss: new Unit(StopLossPips * pip, UnitTypes.Absolute));

		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 fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevFast is null || _prevSlow is null)
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		var pip = Security?.PriceStep ?? 0.05m;
		var signal = GetSignal(fast, slow, _prevFast.Value, _prevSlow.Value, pip);

		if (signal > 0)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		else if (signal < 0)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}

		_prevFast = fast;
		_prevSlow = slow;
	}

	private int GetSignal(decimal fast, decimal slow, decimal prevFast, decimal prevSlow, decimal pip)
	{
		if (IsCrossUp(prevFast, fast, prevSlow, slow, 0m) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, MostTopLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, TopLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, -LowermostLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, -LowerLevel * pip))
		{
			return 1;
		}

		if (IsCrossDown(prevFast, fast, prevSlow, slow, 0m) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, MostTopLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, TopLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, -LowermostLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, -LowerLevel * pip))
		{
			return -1;
		}

		return 0;
	}

	private static bool IsCrossUp(decimal prevFast, decimal fast, decimal prevSlow, decimal slow, decimal offset)
	{
		var prevSlowShifted = prevSlow + offset;
		var slowShifted = slow + offset;
		return prevFast <= prevSlowShifted && fast > slowShifted;
	}

	private static bool IsCrossDown(decimal prevFast, decimal fast, decimal prevSlow, decimal slow, decimal offset)
	{
		var prevSlowShifted = prevSlow + offset;
		var slowShifted = slow + offset;
		return prevFast >= prevSlowShifted && fast < slowShifted;
	}
}