Ver no GitHub

Estratégia de mandíbula de atirador

A Sniper Jaw Strategy transporta o MetaTrader 4 consultor especialista SniperJawEA.mq4 para a estratégia de alto nível de StockSharp API. O sistema analisa o indicador Bill Williams' Alligator no preço médio da vela. Uma negociação só é iniciada quando as três médias móveis suavizadas (mandíbula, dentes e lábios) são empilhadas em ordem estrita de alta ou baixa e todas elas avançam na mesma direção em comparação com a vela finalizada anterior.

Lógica de negociação

  1. Alligator reconstrução – três instâncias SmoothedMovingAverage calculam a mandíbula, os dentes e os lábios na mediana da vela (High + Low) / 2. Cada linha pode ser deslocada para frente em seu próprio número de barras para espelhar a plotagem de MetaTrader.
  2. Confirmação de tendência – uma tendência longa é produzida quando os valores deslocados satisfazem jaw < teeth < lips e cada linha é mais alta do que na vela anterior. Uma tendência curta precisa de jaw > teeth > lips com todas as três linhas se movendo para baixo em comparação com a barra anterior.
  3. Gerenciamento de entradas – a estratégia abre apenas uma posição por vez. Quando UseEntryToExit está ativado e um novo sinal oposto é acionado, a exposição atual é nivelada primeiro e a nova ordem é enviada no próximo sinal.
  4. Saídas de proteção – as distâncias de stop-loss e take-profit são definidas em pips e convertidas usando o título PriceStep. Ambas as posições longas e curtas são supervisionadas em cada vela finalizada e fechadas quando um dos limites é atingido.
  5. Aceleração de sinal – o EA original evitou entradas duplicadas verificando o carimbo de data/hora da barra. A porta armazena o último horário da vela de sinal e pula ordens adicionais durante a mesma barra.

Parâmetros

Parâmetro Padrão Descrição
OrderVolume 0.1 Tamanho da negociação em lotes ou contratos repassados para BuyMarket/SellMarket.
EnableTrading true Chave mestre que permite desabilitar novas entradas mantendo ativa a gestão de riscos.
UseEntryToExit true Fecha uma posição existente antes de armar um sinal oposto. Espelha o sinalizador "Entrada para Saída" do EA.
StopLossPips 20 Distância do stop de proteção ao preço de entrada. Zero desativa a parada.
TakeProfitPips 50 Distância da meta de lucro ao preço de entrada. Zero desativa o alvo.
MinimumBars 60 Número necessário de velas concluídas antes que o primeiro sinal seja avaliado.
JawPeriod / TeethPeriod / LipsPeriod 13 / 8 / 5 Comprimento das médias móveis suavizadas formando as linhas Alligator.
JawShift / TeethShift / LipsShift 8 / 5 / 3 Deslocamento direto (em barras) usado para alinhar os buffers Alligator com a versão MetaTrader.
CandleType 1 hour time frame Assinatura da série de velas primárias. Ajuste para corresponder ao gráfico usado em MetaTrader.

Notas de uso

  • A implementação avalia apenas velas finalizadas (CandleStates.Finished) para evitar valores parcialmente formados.
  • Os níveis de parada e meta são rastreados internamente; a estratégia emite ordens de mercado para nivelar a posição quando um nível é violado.
  • A conversão da etapa de preço segue a convenção comum do Forex: símbolos de 5 e 3 decimais tratam um pip como dez etapas de preço.
  • Adicione a estratégia a um esquema juntamente com um conector, um portfólio e uma configuração de segurança. Após iniciar a estratégia, o painel do gráfico exibirá a série de velas e as linhas Alligator reconstruídas para rápida validação visual.
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>
/// Trend-following system converted from the MetaTrader expert advisor "SniperJawEA.mq4".
/// The strategy aligns the Alligator jaw, teeth, and lips smoothed moving averages on the median price.
/// A long signal appears when all three lines stack upward and each line rises compared with the previous candle.
/// A short signal requires the inverse stacking and downward slope. Optional settings mirror the original EA: pip-based
/// stop-loss and take-profit distances plus an "entry-to-exit" switch that liquidates the opposite position before opening a new trade.
/// </summary>
public class SniperJawStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<bool> _enableTrading;
	private readonly StrategyParam<bool> _useEntryToExit;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _minimumBars;
	private readonly StrategyParam<int> _jawPeriod;
	private readonly StrategyParam<int> _jawShift;
	private readonly StrategyParam<int> _teethPeriod;
	private readonly StrategyParam<int> _teethShift;
	private readonly StrategyParam<int> _lipsPeriod;
	private readonly StrategyParam<int> _lipsShift;
	private readonly StrategyParam<DataType> _candleType;

	private SmoothedMovingAverage _jaw;
	private SmoothedMovingAverage _teeth;
	private SmoothedMovingAverage _lips;

	private decimal?[] _jawHistory;
	private decimal?[] _teethHistory;
	private decimal?[] _lipsHistory;

	private decimal _pipSize;
	private decimal? _longStopPrice;
	private decimal? _longTakePrice;
	private decimal? _shortStopPrice;
	private decimal? _shortTakePrice;
	private bool _longExitRequested;
	private bool _shortExitRequested;
	private int _finishedCandles;
	private DateTimeOffset? _lastSignalTime;

	/// <summary>
	/// Initializes <see cref="SniperJawStrategy"/> parameters.
	/// </summary>
	public SniperJawStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Trade size in lots or contracts", "Trading");

		_enableTrading = Param(nameof(EnableTrading), true)
			.SetDisplay("Enable Trading", "Master switch for signal execution", "Trading");

		_useEntryToExit = Param(nameof(UseEntryToExit), true)
			.SetDisplay("Use Entry To Exit", "Close opposite exposure before opening a new trade", "Trading");

		_stopLossPips = Param(nameof(StopLossPips), 20)
			.SetNotNegative()
			.SetDisplay("Stop Loss (pips)", "Protective stop distance converted with the price step", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 50)
			.SetNotNegative()
			.SetDisplay("Take Profit (pips)", "Optional profit target distance; zero disables it", "Risk");

		_minimumBars = Param(nameof(MinimumBars), 1)
			.SetGreaterThanZero()
			.SetDisplay("Minimum Bars", "Required number of finished candles before trading", "Filters");

		_jawPeriod = Param(nameof(JawPeriod), 13)
			.SetGreaterThanZero()
			.SetDisplay("Jaw Period", "Smoothed moving average length for the jaw line", "Alligator");

		_jawShift = Param(nameof(JawShift), 0)
			.SetNotNegative()
			.SetDisplay("Jaw Shift", "Forward shift applied to jaw readings", "Alligator");

		_teethPeriod = Param(nameof(TeethPeriod), 8)
			.SetGreaterThanZero()
			.SetDisplay("Teeth Period", "Smoothed moving average length for the teeth line", "Alligator");

		_teethShift = Param(nameof(TeethShift), 0)
			.SetNotNegative()
			.SetDisplay("Teeth Shift", "Forward shift applied to teeth readings", "Alligator");

		_lipsPeriod = Param(nameof(LipsPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Lips Period", "Smoothed moving average length for the lips line", "Alligator");

		_lipsShift = Param(nameof(LipsShift), 0)
			.SetNotNegative()
			.SetDisplay("Lips Shift", "Forward shift applied to lips readings", "Alligator");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle series used for signals", "Data");
	}

	/// <summary>
	/// Trade volume expressed in lots or contracts.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Master switch for enabling or disabling signal execution.
	/// </summary>
	public bool EnableTrading
	{
		get => _enableTrading.Value;
		set => _enableTrading.Value = value;
	}

	/// <summary>
	/// Close the opposite position before opening a new trade when a fresh signal arrives.
	/// </summary>
	public bool UseEntryToExit
	{
		get => _useEntryToExit.Value;
		set => _useEntryToExit.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in pips; zero disables the protective stop.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in pips; zero disables the target.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Minimum number of finished candles required before the system evaluates signals.
	/// </summary>
	public int MinimumBars
	{
		get => _minimumBars.Value;
		set => _minimumBars.Value = value;
	}

	/// <summary>
	/// Length of the jaw smoothed moving average.
	/// </summary>
	public int JawPeriod
	{
		get => _jawPeriod.Value;
		set => _jawPeriod.Value = value;
	}

	/// <summary>
	/// Forward shift applied to jaw readings when aligning them with candles.
	/// </summary>
	public int JawShift
	{
		get => _jawShift.Value;
		set => _jawShift.Value = value;
	}

	/// <summary>
	/// Length of the teeth smoothed moving average.
	/// </summary>
	public int TeethPeriod
	{
		get => _teethPeriod.Value;
		set => _teethPeriod.Value = value;
	}

	/// <summary>
	/// Forward shift applied to teeth readings when aligning them with candles.
	/// </summary>
	public int TeethShift
	{
		get => _teethShift.Value;
		set => _teethShift.Value = value;
	}

	/// <summary>
	/// Length of the lips smoothed moving average.
	/// </summary>
	public int LipsPeriod
	{
		get => _lipsPeriod.Value;
		set => _lipsPeriod.Value = value;
	}

	/// <summary>
	/// Forward shift applied to lips readings when aligning them with candles.
	/// </summary>
	public int LipsShift
	{
		get => _lipsShift.Value;
		set => _lipsShift.Value = value;
	}

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_jaw = null;
		_teeth = null;
		_lips = null;
		_jawHistory = null;
		_teethHistory = null;
		_lipsHistory = null;

		_pipSize = 0m;
		_longStopPrice = null;
		_longTakePrice = null;
		_shortStopPrice = null;
		_shortTakePrice = null;
		_longExitRequested = false;
		_shortExitRequested = false;
		_finishedCandles = 0;
		_lastSignalTime = null;
	}

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

		_jaw = new SmoothedMovingAverage { Length = JawPeriod };
		_teeth = new SmoothedMovingAverage { Length = TeethPeriod };
		_lips = new SmoothedMovingAverage { Length = LipsPeriod };

		_jawHistory = CreateHistoryBuffer(JawShift);
		_teethHistory = CreateHistoryBuffer(TeethShift);
		_lipsHistory = CreateHistoryBuffer(LipsShift);

		_pipSize = CalculatePipSize();
		_finishedCandles = 0;
		_lastSignalTime = null;

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

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

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		if (trade.Order?.Security != Security)
			return;

		var entryPrice = trade.Trade.Price;

		if (Position > 0)
		{
			_longStopPrice = StopLossPips > 0 ? entryPrice - StopLossPips * _pipSize : (decimal?)null;
			_longTakePrice = TakeProfitPips > 0 ? entryPrice + TakeProfitPips * _pipSize : (decimal?)null;
			_longExitRequested = false;
			_shortExitRequested = false;
			_shortStopPrice = null;
			_shortTakePrice = null;
		}
		else if (Position < 0)
		{
			_shortStopPrice = StopLossPips > 0 ? entryPrice + StopLossPips * _pipSize : (decimal?)null;
			_shortTakePrice = TakeProfitPips > 0 ? entryPrice - TakeProfitPips * _pipSize : (decimal?)null;
			_shortExitRequested = false;
			_longExitRequested = false;
			_longStopPrice = null;
			_longTakePrice = null;
		}
		else
		{
			_longStopPrice = null;
			_longTakePrice = null;
			_shortStopPrice = null;
			_shortTakePrice = null;
			_longExitRequested = false;
			_shortExitRequested = false;
		}
	}

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

		_finishedCandles++;

		if (Position > 0)
		{
			ManageLong(candle);
		}
		else if (Position < 0)
		{
			ManageShort(candle);
		}

		var median = (candle.HighPrice + candle.LowPrice) / 2m;

		var jawValue = _jaw.Process(new DecimalIndicatorValue(_jaw, median, candle.OpenTime) { IsFinal = true });
		var teethValue = _teeth.Process(new DecimalIndicatorValue(_teeth, median, candle.OpenTime) { IsFinal = true });
		var lipsValue = _lips.Process(new DecimalIndicatorValue(_lips, median, candle.OpenTime) { IsFinal = true });

		if (!_jaw.IsFormed || !_teeth.IsFormed || !_lips.IsFormed)
			return;

		var jawCurrent = jawValue.ToDecimal();
		var teethCurrent = teethValue.ToDecimal();
		var lipsCurrent = lipsValue.ToDecimal();

		if (_finishedCandles < MinimumBars)
			return;

		var isUptrend = jawCurrent < teethCurrent && teethCurrent < lipsCurrent;

		var isDowntrend = jawCurrent > teethCurrent && teethCurrent > lipsCurrent;

		if (!EnableTrading)
			return;

		// removed IsOnline guard

		if (isUptrend)
		{
			if (Position < 0 && UseEntryToExit)
			{
				RequestShortExit();
				return;
			}

			if (Position != 0)
				return;

			if (_lastSignalTime == candle.OpenTime)
				return;

			BuyMarket(volume: OrderVolume);
			_lastSignalTime = candle.OpenTime;
		}
		else if (isDowntrend)
		{
			if (Position > 0 && UseEntryToExit)
			{
				RequestLongExit();
				return;
			}

			if (Position != 0)
				return;

			if (_lastSignalTime == candle.OpenTime)
				return;

			SellMarket(volume: OrderVolume);
			_lastSignalTime = candle.OpenTime;
		}
	}

	private void ManageLong(ICandleMessage candle)
	{
		if (_longTakePrice is decimal take && candle.HighPrice >= take)
		{
			RequestLongExit();
			return;
		}

		if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
		{
			RequestLongExit();
		}
	}

	private void ManageShort(ICandleMessage candle)
	{
		if (_shortTakePrice is decimal take && candle.LowPrice <= take)
		{
			RequestShortExit();
			return;
		}

		if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
		{
			RequestShortExit();
		}
	}

	private void RequestLongExit()
	{
		if (_longExitRequested || Position <= 0)
			return;

		_longExitRequested = true;
		SellMarket(volume: Position);
	}

	private void RequestShortExit()
	{
		if (_shortExitRequested || Position >= 0)
			return;

		_shortExitRequested = true;
		BuyMarket(volume: Math.Abs(Position));
	}

	private static decimal?[] CreateHistoryBuffer(int shift)
	{
		var size = Math.Max(shift + 3, 3);
		return new decimal?[size];
	}

	private static void UpdateHistory(decimal?[] buffer, decimal value)
	{
		if (buffer.Length == 0)
			return;

		Array.Copy(buffer, 1, buffer, 0, buffer.Length - 1);
		buffer[^1] = value;
	}

	private static bool TryGetShiftedValue(decimal?[] buffer, int offsetFromEnd, out decimal value)
	{
		value = 0m;

		if (buffer.Length < offsetFromEnd)
			return false;

		var index = buffer.Length - offsetFromEnd;
		if (index < 0)
			return false;

		if (buffer[index] is not decimal stored)
			return false;

		value = stored;
		return true;
	}

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

		var decimals = Security?.Decimals ?? 0;
		if (decimals == 3 || decimals == 5)
			return step * 10m;

		return step;
	}
}