Ver no GitHub

Estratégia de níveis MartingaleEA-5 (StockSharp)

A Estratégia de níveis MartingaleEA-5 é uma porta direta do MetaTrader consultor especialista 5 "Níveis MartingaleEA-5" para o StockSharp API de alto nível. O sistema supervisiona uma posição existente e constrói uma grade média de cinco etapas sempre que o mercado se move contra ela. Toda a lógica é executada em velas finalizadas, o que mantém o comportamento reproduzível tanto em testes históricos quanto em negociações ao vivo.

Lógica de negociação

  1. Monitoramento da exposição existente – a estratégia espera que uma posição inicial longa ou curta esteja presente. Você pode abrir a primeira negociação manualmente ou através de qualquer outra estratégia.
  2. Detecção de movimento adverso – em cada vela concluída, a estratégia mede o quão longe o preço atual se afastou da entrada de pior preço do grupo ativo (compra mais alta ou venda mais baixa).
  3. Martingale acréscimos – se a perda flutuante do grupo for negativa e o movimento adverso ultrapassar as distâncias cumulativas configuradas, a estratégia envia ordens de mercado extras. Cada pedido adicional multiplica o anterior por VolumeMultiplier. Podem ser configurados até cinco níveis; o parâmetro MaxAdditions limita quantos deles são realmente usados.
  4. Visualização de lucros e perdas – enquanto um grupo está aberto, a estratégia soma continuamente o PnL não realizado para essa direção. Quando o total atingir TakeProfitCurrency ou cair abaixo de StopLossCurrency, todas as ordens desse lado serão fechadas com uma ordem de mercado e os contadores de martingale serão zerados.
  5. Normalização de volume – todo volume de pedido passa pelos VolumeStep, MinVolume e MaxVolume do instrumento para evitar o envio de quantidades não executáveis.

Parâmetros

Nome Descrição Padrão
EnableMartingale Ativa ou desativa a lógica de média e liquidação. true
VolumeMultiplier Fator aplicado ao volume do pedido anterior ao adicionar um novo nível. 2.0
MaxAdditions Número máximo de passos de martingale por direção (até cinco). 4
Level1DistancePips Distância adversa inicial (em pips) antes de abrir a segunda ordem. 300
Level2DistancePips Distância adicional necessária para o terceiro pedido. 400
Level3DistancePips Distância adicional necessária para o quarto pedido. 500
Level4DistancePips Distância adicional necessária para o quinto pedido. 600
Level5DistancePips Distância adicional necessária para o sexto pedido (se permitido). 700
TakeProfitCurrency Lucro não realizado (moeda da conta) que fecha todo o grupo. 200
StopLossCurrency Perda não realizada (moeda da conta) que força uma saída de emergência. -500
CandleType Prazo usado para avaliações (velas padrão de 1 minuto). TimeFrame(1m)

Conversão de pip – cada distância é multiplicada pela etapa de preço do instrumento (PriceStep ou MinPriceStep). Para símbolos cotados em pips fracionários, ajuste os valores de acordo.

Notas e recomendações

  • A implementação reflete o EA original, incluindo sua suposição de que apenas uma cesta direcional está ativa por vez. A abertura de posições simultaneamente em ambas as direções fará com que cada lado seja gerenciado de forma independente.
  • Como a estratégia reage apenas no fechamento da vela, escolha um período de tempo que corresponda à capacidade de resposta desejada. Prazos mais baixos emulam mais de perto o comportamento do nível de tick.
  • As técnicas Martingale amplificam o risco. Sempre faça backtest com modelos realistas de slippage e comissão e defina níveis de stop conservadores antes de ativar a estratégia em mercados reais.
  • A estratégia ainda não cria uma porta Python. Somente a implementação de alto nível do C# é incluída conforme solicitado.
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>
/// Martingale averaging strategy converted from "MartingaleEA-5 Levels".
/// Opens initial position on simple momentum, then averages down with
/// increasing lot sizes up to 5 levels. Closes when floating profit
/// reaches target or stop threshold.
/// </summary>
public class MartingaleEa5LevelsStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<decimal> _volumeMultiplier;
	private readonly StrategyParam<int> _maxAdditions;
	private readonly StrategyParam<decimal> _takeProfitPercent;
	private readonly StrategyParam<decimal> _stopLossPercent;

	private SimpleMovingAverage _sma;
	private decimal? _prevClose;
	private decimal? _prevMa;

	private readonly List<(decimal price, decimal vol)> _entries = new();
	private int _additions;
	private decimal _lastVolume;
	private Sides? _activeSide;
	private int _candleCount;
	private int _lastOrderCandle;

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

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	public decimal VolumeMultiplier
	{
		get => _volumeMultiplier.Value;
		set => _volumeMultiplier.Value = value;
	}

	public int MaxAdditions
	{
		get => _maxAdditions.Value;
		set => _maxAdditions.Value = value;
	}

	public decimal TakeProfitPercent
	{
		get => _takeProfitPercent.Value;
		set => _takeProfitPercent.Value = value;
	}

	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}

	public MartingaleEa5LevelsStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_maPeriod = Param(nameof(MaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "SMA period for entry signal", "Indicators");

		_volumeMultiplier = Param(nameof(VolumeMultiplier), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Volume Multiplier", "Multiplier for each martingale level", "Money Management");

		_maxAdditions = Param(nameof(MaxAdditions), 4)
			.SetDisplay("Max Additions", "Maximum martingale additions", "Money Management");

		_takeProfitPercent = Param(nameof(TakeProfitPercent), 0.5m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit %", "Floating profit % to close group", "Risk");

		_stopLossPercent = Param(nameof(StopLossPercent), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss %", "Floating loss % to close group", "Risk");
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_sma = default;
		_prevClose = null;
		_prevMa = null;
		_entries.Clear();
		_additions = 0;
		_lastVolume = 0;
		_activeSide = null;
		_candleCount = 0;
		_lastOrderCandle = 0;
	}

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

		_sma = new SimpleMovingAverage { Length = MaPeriod };
		_prevClose = null;
		_prevMa = null;
		_entries.Clear();
		_additions = 0;
		_lastVolume = 0;
		_activeSide = null;
		_candleCount = 0;
		_lastOrderCandle = 0;

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

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

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

		if (!_sma.IsFormed)
		{
			_prevClose = candle.ClosePrice;
			_prevMa = smaValue;
			return;
		}

		_candleCount++;

		var close = candle.ClosePrice;
		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Cooldown: allow at most one order per 100 candles
		var cooldownPassed = (_candleCount - _lastOrderCandle) >= 100;

		// Check martingale closure first
		if (_entries.Count > 0)
		{
			var floatingPnl = CalculateFloatingPnl(close);
			var totalCost = CalculateTotalCost();

			if (totalCost > 0)
			{
				var pnlPercent = floatingPnl / totalCost * 100m;

				if (cooldownPassed && (pnlPercent >= TakeProfitPercent || pnlPercent <= -StopLossPercent))
				{
					// Close entire position
					if (Position > 0)
						SellMarket(Position);
					else if (Position < 0)
						BuyMarket(Math.Abs(Position));

					_lastOrderCandle = _candleCount;
					_entries.Clear();
					_additions = 0;
					_lastVolume = 0;
					_activeSide = null;

					_prevClose = close;
					_prevMa = smaValue;
					return;
				}
			}

			// Check for martingale additions
			if (cooldownPassed && _additions < MaxAdditions)
			{
				var avgPrice = CalculateAvgPrice();
				var adversePercent = _activeSide == Sides.Buy
					? (avgPrice - close) / avgPrice * 100m
					: (close - avgPrice) / avgPrice * 100m;

				// Add at each 0.3% adverse move beyond previous level
				var threshold = 0.3m * (_additions + 1);
				if (adversePercent >= threshold)
				{
					var nextVol = _lastVolume * VolumeMultiplier;
					if (nextVol < 1) nextVol = 1;

					if (_activeSide == Sides.Buy)
					{
						BuyMarket(nextVol);
						_entries.Add((close, nextVol));
					}
					else
					{
						SellMarket(nextVol);
						_entries.Add((close, nextVol));
					}

					_lastVolume = nextVol;
					_additions++;
					_lastOrderCandle = _candleCount;
				}
			}
		}

		// Initial entry signal: MA crossover
		if (cooldownPassed && _prevClose != null && _prevMa != null && _activeSide == null)
		{
			var buySignal = _prevClose.Value < _prevMa.Value && close > smaValue;
			var sellSignal = _prevClose.Value > _prevMa.Value && close < smaValue;

			if (buySignal)
			{
				BuyMarket(volume);
				_entries.Clear();
				_entries.Add((close, volume));
				_additions = 0;
				_lastVolume = volume;
				_activeSide = Sides.Buy;
				_lastOrderCandle = _candleCount;
			}
			else if (sellSignal)
			{
				SellMarket(volume);
				_entries.Clear();
				_entries.Add((close, volume));
				_additions = 0;
				_lastVolume = volume;
				_activeSide = Sides.Sell;
				_lastOrderCandle = _candleCount;
			}
		}

		_prevClose = close;
		_prevMa = smaValue;
	}

	private decimal CalculateFloatingPnl(decimal currentPrice)
	{
		var pnl = 0m;
		foreach (var (price, vol) in _entries)
		{
			if (_activeSide == Sides.Buy)
				pnl += (currentPrice - price) * vol;
			else
				pnl += (price - currentPrice) * vol;
		}
		return pnl;
	}

	private decimal CalculateTotalCost()
	{
		var cost = 0m;
		foreach (var (price, vol) in _entries)
			cost += price * vol;
		return cost;
	}

	private decimal CalculateAvgPrice()
	{
		var totalVol = 0m;
		var totalCost = 0m;
		foreach (var (price, vol) in _entries)
		{
			totalVol += vol;
			totalCost += price * vol;
		}
		return totalVol > 0 ? totalCost / totalVol : 0;
	}
}