Ver no GitHub

Estratégia de CrossoverMA

Visão geral

Esta estratégia é um porte StockSharp do consultor especialista MetaTrader 5 CrossoverMA.mq5. O robô original aguarda um candle cruzar uma média móvel e só abre uma posição quando a média está inclinada na mesma direção que o rompimento. A versão StockSharp mantém o mesmo comportamento aproveitando a API de alto nível para assinaturas de candles, gerenciamento de indicadores e renderização automática de gráficos.

Lógica de trading

  1. Subscrever a série de candles configurada e calcular uma média móvel simples (SMA) sobre o preço de fechamento do candle.
  2. Quando um candle terminado é recebido, medir:
    • As distâncias de abertura e fechamento do candle da SMA.
    • A inclinação da SMA comparando o valor atual com o anterior.
  3. Gerar sinais:
    • Rompimento altista – o candle abre abaixo da SMA, fecha acima dela, e a SMA está subindo. A estratégia fecha qualquer exposição curta e abre/estende uma posição longa.
    • Rompimento baixista – o candle abre acima da SMA, fecha abaixo dela, e a SMA está caindo. A estratégia fecha qualquer exposição longa e abre/estende uma posição curta.
  4. Ignorar sinais duplicados que não mudem o lado da posição atual.

O porte mantém a regra do MetaTrader de que apenas candles terminados são processados e que um candle extra é necessário antes do primeiro trade (para medir a inclinação da SMA).

Parâmetros

Nome Descrição Padrão Notas
Candle Type Período usado para construir candles. Período de 1 minuto Qualquer tipo de dado de candle suportado pelo StockSharp pode ser selecionado.
MA Length Número de candles completados incluídos na SMA. 12 Corresponde ao período padrão do especialista MetaTrader.
Trade Volume Volume de ordem de mercado para entradas. 1 A estratégia fecha a exposição oposta antes de abrir uma nova posição.

Todos os parâmetros estão disponíveis para otimização no StockSharp Designer ou Runner.

Notas de implementação

  • A estratégia depende de SubscribeCandles e Bind para que os valores do indicador sejam transmitidos diretamente ao método de processamento sem gerenciamento manual do histórico.
  • A SMA é armazenada em um campo privado para desenhá-la na área do gráfico quando uma está disponível.
  • Os sinais são processados apenas quando IsFormedAndOnlineAndAllowTrading() retorna true, garantindo que a estratégia respeite o estado global de trading.
  • As reversões de posição seguem o modelo do MetaTrader: fechar a exposição atual primeiro, depois abrir o novo lado com o volume de trade configurado.

Arquivos

  • CS/CrossoverMaStrategy.cs – implementação em C# da estratégia convertida.
  • README.md – documentação em inglês.
  • README_zh.md – documentação em chinês.
  • README_ru.md – documentação em russo.

Diferenças de portabilidade

  • As classes de gerenciamento de dinheiro, trailing stop e outros frameworks do MetaTrader são omitidas porque o StockSharp gerencia o dimensionamento de posições e o risco externamente. O parâmetro Trade Volume substitui as configurações de lote fixo do especialista original.
  • O MetaTrader usava séries de dados separadas para os preços de abertura e fechamento de candles. Os candles do StockSharp já incluem ambos os preços, então nenhum indicador adicional é necessário.
  • A inicialização, validação e gerenciamento do ciclo de vida do indicador são tratados automaticamente pelo StockSharp, removendo o extenso código boilerplate da versão MQL.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class CrossoverMaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public CrossoverMaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}