Ver no GitHub

MA em estratégia de lucro mínimo Momentum

Visão geral

Esta estratégia replica o consultor especialista MetaTrader 5 MA no Momentum Min Profit.mq5 negociando o cruzamento entre um indicador Momentum e uma média móvel que é calculada no topo da série de momentum. Um sinal de alta aparece quando o momentum ultrapassa sua média, enquanto a barra anterior manteve o momentum abaixo do nível neutro 100. Um sinal de baixa é gerado quando o momentum cruza abaixo da média com a barra anterior acima de 100. A implementação mantém o stop original de capital baseado em dinheiro e a distância fixa de lucro medida em pontos.

Lógica de negociação

  1. Solicite velas definidas por CandleType e insira-as no indicador Momentum.
  2. Suavize o fluxo de impulso com uma média móvel definida por MomentumMovingAverageType e MomentumMovingAveragePeriod.
  3. Detecte cruzamentos usando os valores da barra anterior para evitar sinais duplos.
  4. Recursos opcionais da versão MQL:
    • Inverta a direção dos sinais gerados.
    • Feche a exposição oposta antes de entrar em uma nova negociação ou pule totalmente a entrada.
    • Aplique uma única posição líquida a qualquer momento.
    • Permitir o acionamento na vela atual (em formação) em vez da barra totalmente fechada.
  5. Aplicar gerenciamento de risco:
    • Parada de capital em dinheiro: PnL + Position * (close - PositionPrice) deve permanecer acima de StopLossMoney.
    • Distância de lucro em pontos convertidos por meio de Security.PriceStep.

Parâmetros

Parâmetro Tipo Padrão Descrição
CandleType DataType TimeSpan.FromMinutes(5).TimeFrame() Velas usadas para calcular o momento.
MomentumPeriod int 14 Período de retrospectiva do indicador Momentum.
MomentumMovingAveragePeriod int 6 Comprimento da média móvel aplicada ao momentum.
MomentumMovingAverageType MomentumMovingAverageType Smoothed Algoritmo de média móvel (Simples, Exponencial, Suavizada, Ponderada).
ReverseSignals bool false Espelhe MetaTrader sinais de compra/venda.
CloseOpposite bool true Feche a exposição oposta antes de abrir uma nova posição.
OnlyOnePosition bool true Mantenha uma única posição líquida.
UseCurrentCandle bool false Avalie os sinais na vela em formação atual em vez da barra fechada.
StopLossMoney decimal 15 Rebaixamento de capital permitido antes de fechar todas as negociações.
TakeProfitPoints decimal 460 Meta de lucro em pontos de instrumento (multiplicado por PriceStep).
MomentumReference decimal 100 Nível de impulso neutro copiado da estratégia MQL.

Notas de implementação

  • A média móvel é implementada com LengthIndicator<decimal> instâncias para reutilizar StockSharp classes SMA/EMA/SMMA/WMA integradas.
  • A fila de pedidos original e os filtros de números mágicos são mapeados para StockSharp posições líquidas, portanto, a estratégia envia um único pedido de mercado dimensionado para achatar o lado oposto e abrir a nova exposição quando CloseOpposite estiver ativado.
  • A proteção de ações fecha todas as posições via CloseAll() assim que a perda flutuante ultrapassa o limite, correspondendo exatamente ao comportamento MetaTrader de monitorar a comissão, swap e lucro combinados.
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;

/// <summary>
/// Momentum crossing its own moving average strategy.
/// Converted from MetaTrader 5 (MA on Momentum Min Profit.mq5).
/// </summary>
public class MaOnMomentumMinProfitStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<int> _maPeriod;

	private Momentum _momentum;
	private readonly Queue<decimal> _momentumHistory = new();
	private decimal? _prevMomentum;
	private decimal? _prevSignal;

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

	/// <summary>
	/// Momentum period.
	/// </summary>
	public int MomentumPeriod
	{
		get => _momentumPeriod.Value;
		set => _momentumPeriod.Value = value;
	}

	/// <summary>
	/// Moving average period applied to momentum values.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="MaOnMomentumMinProfitStrategy"/>.
	/// </summary>
	public MaOnMomentumMinProfitStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used for the momentum calculation", "General");

		_momentumPeriod = Param(nameof(MomentumPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Period", "Lookback for the momentum indicator", "Momentum");

		_maPeriod = Param(nameof(MaPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Period of the moving average applied to momentum", "Momentum");
	}

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

		_prevMomentum = null;
		_prevSignal = null;
		_momentumHistory.Clear();

		_momentum = new Momentum { Length = MomentumPeriod };

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

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

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

		_momentumHistory.Enqueue(momentumValue);
		while (_momentumHistory.Count > MaPeriod)
			_momentumHistory.Dequeue();

		if (!_momentum.IsFormed)
			return;

		if (_momentumHistory.Count < MaPeriod)
		{
			_prevMomentum = momentumValue;
			return;
		}

		// Calculate SMA of momentum
		var sum = 0m;
		var history = _momentumHistory.ToArray();
		foreach (var v in history)
			sum += v;
		var signalValue = sum / history.Length;

		if (_prevMomentum is null || _prevSignal is null)
		{
			_prevMomentum = momentumValue;
			_prevSignal = signalValue;
			return;
		}

		var crossUp = _prevMomentum < _prevSignal && momentumValue > signalValue;
		var crossDown = _prevMomentum > _prevSignal && momentumValue < signalValue;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		var minSpread = 0.5m;

		if (crossUp && Math.Abs(momentumValue - signalValue) >= minSpread)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (crossDown && Math.Abs(momentumValue - signalValue) >= minSpread)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevMomentum = momentumValue;
		_prevSignal = signalValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_prevMomentum = null;
		_prevSignal = null;
		_momentum = null;
		_momentumHistory.Clear();

		base.OnReseted();
	}
}