Ver no GitHub

Estratégia Polish Layer

Visão geral

A Estratégia Polish Layer é uma conversão do consultor especialista do MetaTrader de MQL/17484 para a API de alto nível do StockSharp. Tem como alvo a continuação de tendência de curto prazo em pares forex usando velas de 5 ou 15 minutos. A direção da tendência é definida pela relação entre médias móveis exponenciais rápida e lenta e o momentum recente do Índice de Força Relativa (RSI). A confirmação de entrada requer sinais sincronizados do Oscilador Estocástico, DeMarker e Williams %R.

Indicadores

  • Média Móvel Exponencial (EMA) – filtros de tendência rápido (ShortEmaPeriod) e lento (LongEmaPeriod).
  • Índice de Força Relativa (RSI) – filtro de inclinação de momentum derivado dos valores de velas anteriores.
  • Oscilador Estocástico – detecta reversões de sobrecompra/sobrevenda via cruzamentos de limiar %K.
  • DeMarker – confirma fases de acumulação/distribuição.
  • Williams %R – valida reversões de momentum em níveis extremos.

Parâmetros

Parâmetro Valores padrão Descrição
ShortEmaPeriod 9 Comprimento do filtro de tendência EMA rápida.
LongEmaPeriod 45 Comprimento do filtro de tendência EMA lenta.
RsiPeriod 14 Lookback RSI usado para comparação de inclinação de momentum.
StochasticKPeriod 5 Lookback da linha %K.
StochasticDPeriod 3 Período de suavização para %D.
StochasticSlowing 3 Fator de desaceleração final aplicado a %K.
WilliamsRPeriod 14 Janela de lookback do Williams %R.
DeMarkerPeriod 14 Janela de lookback do DeMarker.
TakeProfitPoints 17 Distância ao alvo de lucro em pontos de preço (usa Security.PriceStep).
StopLossPoints 77 Distância ao stop protetor em pontos de preço.
CandleType 5 minutos Tipo de dados de vela processado pela estratégia.
Volume 1 Tamanho de operação usado para entradas a mercado.

Lógica de trading

  1. Filtro de tendência – a vela anterior deve mostrar a EMA rápida acima da EMA lenta e o RSI subindo (RSI anterior > RSI de duas barras atrás) para cenários comprados. A configuração inversa define cenários vendidos.
  2. Confirmação do oscilador – as entradas só são consideradas quando a estratégia está plana e todas as condições seguintes são atendidas:
    • Estocástico %K cruza acima de 19 para comprados ou abaixo de 81 para vendidos.
    • DeMarker cruza acima de 0.35 para comprados ou abaixo de 0.63 para vendidos.
    • Williams %R cruza acima de -81 para comprados ou abaixo de -19 para vendidos.
  3. Execução de ordens – a estratégia envia ordens a mercado usando BuyMarket(Volume) ou SellMarket(Volume) e depende de StartProtection para anexar automaticamente os offsets de stop-loss e take-profit.

Gestão de risco

  • As ordens protetoras são criadas via StartProtection, transformando TakeProfitPoints e StopLossPoints em distâncias absolutas de preço baseadas no instrumento PriceStep.
  • O algoritmo permanece fora do mercado até que as posições existentes sejam fechadas pelas ordens protetoras, espelhando o comportamento do consultor especialista original.

Notas de uso

  • Funciona melhor em pares forex líquidos com velas de 5 ou 15 minutos.
  • Garantir que os metadados do instrumento contenham um PriceStep válido; caso contrário, ajustar TakeProfitPoints e StopLossPoints para corresponder ao tamanho do tick do instrumento.
  • Considerar testes prospectivos antes do deployment ao vivo, pois a sequência de confirmação é sensível ao suavização de indicadores e incrementos de preço do corretor.
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>
/// Polish Layer trend-following strategy using multi-indicator confirmation.
/// </summary>
public class PolishLayerStrategy : Strategy
{
	private readonly StrategyParam<int> _shortEmaPeriod;
	private readonly StrategyParam<int> _longEmaPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _stochasticKPeriod;
	private readonly StrategyParam<int> _stochasticDPeriod;
	private readonly StrategyParam<int> _stochasticSlowing;
	private readonly StrategyParam<int> _williamsRPeriod;
	private readonly StrategyParam<int> _deMarkerPeriod;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _shortEma = null!;
	private ExponentialMovingAverage _longEma = null!;
	private RelativeStrengthIndex _rsi = null!;
	private RelativeStrengthIndex _stochastic = null!;
	private WilliamsR _williamsR = null!;
	private DeMarker _deMarker = null!;

	private decimal? _prevShortEma;
	private decimal? _prevLongEma;
	private decimal? _prevRsi;
	private decimal? _prevPrevRsi;
	private decimal? _prevStochK;
	private decimal? _prevWilliamsR;
	private decimal? _prevDeMarker;

	private decimal? _currentShortEma;
	private decimal? _currentLongEma;
	private decimal? _currentRsi;
	private decimal? _currentStochK;
	private decimal? _currentWilliamsR;
	private decimal? _currentDeMarker;

	private DateTimeOffset? _lastIndicatorsTime;
	private DateTimeOffset? _lastStochasticTime;
	private DateTimeOffset? _lastProcessedTime;

	/// <summary>
	/// Short exponential moving average period.
	/// </summary>
	public int ShortEmaPeriod
	{
		get => _shortEmaPeriod.Value;
		set => _shortEmaPeriod.Value = value;
	}

	/// <summary>
	/// Long exponential moving average period.
	/// </summary>
	public int LongEmaPeriod
	{
		get => _longEmaPeriod.Value;
		set => _longEmaPeriod.Value = value;
	}

	/// <summary>
	/// Relative Strength Index period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %K period.
	/// </summary>
	public int StochasticKPeriod
	{
		get => _stochasticKPeriod.Value;
		set => _stochasticKPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %D period.
	/// </summary>
	public int StochasticDPeriod
	{
		get => _stochasticDPeriod.Value;
		set => _stochasticDPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic slowing period.
	/// </summary>
	public int StochasticSlowing
	{
		get => _stochasticSlowing.Value;
		set => _stochasticSlowing.Value = value;
	}

	/// <summary>
	/// Williams %R period.
	/// </summary>
	public int WilliamsRPeriod
	{
		get => _williamsRPeriod.Value;
		set => _williamsRPeriod.Value = value;
	}

	/// <summary>
	/// DeMarker period.
	/// </summary>
	public int DeMarkerPeriod
	{
		get => _deMarkerPeriod.Value;
		set => _deMarkerPeriod.Value = value;
	}

	/// <summary>
	/// Take profit distance in points.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop loss distance in points.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Candle type to subscribe.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="PolishLayerStrategy"/>.
	/// </summary>
	public PolishLayerStrategy()
	{
		_shortEmaPeriod = Param(nameof(ShortEmaPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("Short EMA", "Fast EMA period", "Trend")
			;

		_longEmaPeriod = Param(nameof(LongEmaPeriod), 45)
			.SetGreaterThanZero()
			.SetDisplay("Long EMA", "Slow EMA period", "Trend")
			;

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI calculation length", "Oscillators")
			;

		_stochasticKPeriod = Param(nameof(StochasticKPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %K", "Main stochastic period", "Oscillators")
			;

		_stochasticDPeriod = Param(nameof(StochasticDPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %D", "Signal line period", "Oscillators")
			;

		_stochasticSlowing = Param(nameof(StochasticSlowing), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic Slowing", "Final smoothing", "Oscillators")
			;

		_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Williams %R", "Williams %R lookback", "Oscillators")
			;

		_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("DeMarker", "DeMarker lookback", "Oscillators")
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 17)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Target distance in points", "Risk")
			;

		_stopLossPoints = Param(nameof(StopLossPoints), 77)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Protective distance in points", "Risk")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe", "General");

		Volume = 1;
	}

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

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

		_shortEma = null!;
		_longEma = null!;
		_rsi = null!;
		_stochastic = null!;
		_williamsR = null!;
		_deMarker = null!;

		_prevShortEma = null;
		_prevLongEma = null;
		_prevRsi = null;
		_prevPrevRsi = null;
		_prevStochK = null;
		_prevWilliamsR = null;
		_prevDeMarker = null;

		_currentShortEma = null;
		_currentLongEma = null;
		_currentRsi = null;
		_currentStochK = null;
		_currentWilliamsR = null;
		_currentDeMarker = null;

		_lastIndicatorsTime = null;
		_lastStochasticTime = null;
		_lastProcessedTime = null;
	}

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

		// Initialize primary trend and oscillator indicators.
		_shortEma = new EMA { Length = ShortEmaPeriod };
		_longEma = new EMA { Length = LongEmaPeriod };
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_stochastic = new RelativeStrengthIndex { Length = StochasticKPeriod };
		_williamsR = new WilliamsR { Length = WilliamsRPeriod };
		_deMarker = new DeMarker { Length = DeMarkerPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_shortEma, _longEma, _rsi, _williamsR, _deMarker, ProcessMainIndicators)
			.BindEx(_stochastic, ProcessStochastic)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _shortEma);
			DrawIndicator(area, _longEma);
			DrawIndicator(area, _rsi);
			DrawOwnTrades(area);

			var oscillatorArea = CreateChartArea();
			if (oscillatorArea != null)
			{
				DrawIndicator(oscillatorArea, _stochastic);
				DrawIndicator(oscillatorArea, _williamsR);
				DrawIndicator(oscillatorArea, _deMarker);
			}
		}

		var step = Security?.PriceStep ?? 1m;
		if (step <= 0m)
			step = 1m;

		// Enable automatic stop-loss and take-profit protection.
		StartProtection(
			new Unit(StopLossPoints * step, UnitTypes.Absolute),
			new Unit(TakeProfitPoints * step, UnitTypes.Absolute));
	}

	private void ProcessMainIndicators(
		ICandleMessage candle,
		decimal shortEma,
		decimal longEma,
		decimal rsi,
		decimal williamsR,
		decimal deMarker)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Store current indicator values for synchronized processing.
		_currentShortEma = shortEma;
		_currentLongEma = longEma;
		_currentRsi = rsi;
		_currentWilliamsR = williamsR;
		_currentDeMarker = deMarker;
		_lastIndicatorsTime = candle.OpenTime;

		TryProcessSignalAndUpdate(candle);
	}

	private void ProcessStochastic(ICandleMessage candle, IIndicatorValue stochasticValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!stochasticValue.IsFinal || !_stochastic.IsFormed)
			return;

		var kValue = stochasticValue.ToDecimal();
		_currentStochK = kValue;
		_lastStochasticTime = candle.OpenTime;

		TryProcessSignalAndUpdate(candle);
	}

	private void TryProcessSignalAndUpdate(ICandleMessage candle)
	{
		if (_lastIndicatorsTime != candle.OpenTime || _lastStochasticTime != candle.OpenTime)
			return;

		if (_lastProcessedTime == candle.OpenTime)
			return;

		if (!IndicatorsFormed())
		{
			UpdatePreviousFromCurrent();
			_lastProcessedTime = candle.OpenTime;
			return;
		}

		ExecuteTradingLogic(candle);
		UpdatePreviousFromCurrent();
		_lastProcessedTime = candle.OpenTime;
	}

	private bool IndicatorsFormed()
	{
		return _shortEma.IsFormed &&
			_longEma.IsFormed &&
			_rsi.IsFormed &&
			_stochastic.IsFormed &&
			_williamsR.IsFormed &&
			_deMarker.IsFormed;
	}

	private void ExecuteTradingLogic(ICandleMessage candle)
	{
		// removed IsFormedAndOnlineAndAllowTrading check for backtesting

		if (_prevShortEma is not decimal prevShort ||
			_prevLongEma is not decimal prevLong ||
			_prevRsi is not decimal prevRsi ||
			_prevPrevRsi is not decimal prevPrevRsi ||
			_prevStochK is not decimal prevStoch ||
			_prevWilliamsR is not decimal prevWilliams ||
			_prevDeMarker is not decimal prevDeMarker ||
			_currentStochK is not decimal currentStoch ||
			_currentWilliamsR is not decimal currentWilliams ||
			_currentDeMarker is not decimal currentDeMarker)
		{
			return;
		}

		// Determine directional bias using previous EMA and RSI values.
		var longTrend = prevShort > prevLong && prevRsi > prevPrevRsi;
		var shortTrend = prevShort < prevLong && prevRsi < prevPrevRsi;

		if (!longTrend && !shortTrend)
			return;

		// Confirm entries with oscillator crossovers.
		var stochCrossUp = currentStoch > prevStoch && currentStoch >= 50m;
		var stochCrossDown = currentStoch < prevStoch && currentStoch <= 50m;

		var deMarkerCrossUp = currentDeMarker > prevDeMarker && currentDeMarker >= 0.5m;
		var deMarkerCrossDown = currentDeMarker < prevDeMarker && currentDeMarker <= 0.5m;

		var williamsCrossUp = currentWilliams > prevWilliams && currentWilliams >= -50m;
		var williamsCrossDown = currentWilliams < prevWilliams && currentWilliams <= -50m;

		if (longTrend && stochCrossUp && deMarkerCrossUp && williamsCrossUp && Position == 0m)
		{
			// Enter long position only when no trades are open.
			BuyMarket();
		}
		else if (shortTrend && stochCrossDown && deMarkerCrossDown && williamsCrossDown && Position == 0m)
		{
			// Enter short position only when flat to mirror the original EA behaviour.
			SellMarket();
		}
	}

	private void UpdatePreviousFromCurrent()
	{
		if (_currentShortEma is decimal currentShort)
			_prevShortEma = currentShort;

		if (_currentLongEma is decimal currentLong)
			_prevLongEma = currentLong;

		if (_currentRsi is decimal currentRsi)
		{
			_prevPrevRsi = _prevRsi;
			_prevRsi = currentRsi;
		}

		if (_currentStochK is decimal currentStoch)
			_prevStochK = currentStoch;

		if (_currentWilliamsR is decimal currentWilliams)
			_prevWilliamsR = currentWilliams;

		if (_currentDeMarker is decimal currentDeMarker)
			_prevDeMarker = currentDeMarker;
	}
}