Ver no GitHub

Estratégia de Parabolic SAR EA

Visão Geral

A Estratégia de Parabolic SAR EA é a conversão de alto nível do StockSharp do consultor especializado MetaTrader Parabolic SAR EA.mq5 localizado em MQL/23039. O script MQL original reage às reversões do Parabolic SAR em um período configurável, abrindo posições de mercado com distâncias de stop-loss e take-profit fixas expressas em "pips" do MetaTrader (com suporte a pip fracional). O port em C# assina candles, vincula o indicador ParabolicSar integrado e reproduz o mesmo processo de decisão barra a barra respeitando as melhores práticas do StockSharp.

Lógica de Negociação

  1. Preparação de dados
    • A estratégia assina o tipo de candle selecionado pelo usuário (candles de 30 minutos por padrão) e vincula um indicador Parabolic SAR configurado com passo de aceleração e valores máximos ajustáveis.
    • O valor SAR é calculado para cada candle e entregue à estratégia através do callback de alto nível Bind.
  2. Geração de sinais
    • Sinal de compra: quando o valor do Parabolic SAR do candle finalizado está estritamente abaixo da mínima do candle.
    • Sinal de venda: quando o valor do Parabolic SAR do candle finalizado está estritamente acima da máxima do candle.
    • Os sinais são avaliados apenas em candles completados (CandleStates.Finished) para corresponder ao processamento de nova barra do MQL.
  3. Gestão de posição
    • A exposição oposta é zerada antes de uma nova entrada aumentando o tamanho da ordem de mercado solicitada com o valor absoluto da posição atual, replicando a sequência MetaTrader ClosePosition mais OpenPosition.
    • Cada entrada recalcula os níveis de stop-loss e take-profit de proteção usando as mesmas regras de conversão pip-a-preço do MetaTrader (instrumentos de 3/5 dígitos recebem um multiplicador ×10 para o PriceStep).
  4. Saídas de proteção
    • Em cada candle finalizado a estratégia verifica se a máxima/mínima viola o nível de stop-loss ou take-profit armazenado. Se acionado, a posição é fechada com uma ordem de mercado e os alvos correspondentes são limpos.
    • A lógica de proteção é acionada antes de novos sinais na mesma barra, refletindo o comportamento original do Consultor Especialista onde ordens stop são do lado do corretor.

Notas sobre Indicador e Dados

  • Usa o indicador ParabolicSar integrado do StockSharp com parâmetros SarStep e SarMaximum.
  • A assinatura de candles é tratada através de SubscribeCandles sem adicionar o indicador a Strategy.Indicators, conforme exigido pelas diretrizes do projeto.
  • A negociação só é permitida quando IsFormedAndOnlineAndAllowTrading() reporta true, garantindo que dados ao vivo estejam presentes e o conector permita o envio de ordens.

Parâmetros

Nome Padrão Descrição
TradeVolume 1 Tamanho da ordem de mercado em lotes. Atualizar o valor também atualiza Strategy.Volume.
StopLossPips 50 Distância de stop-loss em pips do MetaTrader. Um pip equivale a PriceStep × 10 para instrumentos com 3 ou 5 decimais, caso contrário apenas PriceStep. Definir como 0 para desativar.
TakeProfitPips 50 Distância de take-profit em pips do MetaTrader usando as mesmas regras de conversão do stop-loss. Definir como 0 para desativar.
SarStep 0.02 Passo de aceleração usado pelo indicador Parabolic SAR.
SarMaximum 0.2 Fator de aceleração máximo para Parabolic SAR.
CandleType 30m timeframe Tipo de candle usado para cálculos. Suporta qualquer DataType derivado de TimeFrame.

Gestão de Risco e Comportamento

  • Stop-loss e take-profit são recalculados após cada execução e armazenados internamente; nenhuma ordem pendente é registrada na bolsa.
  • Se ambos os níveis de proteção forem acionados dentro de um único candle, a verificação do stop-loss é acionada primeiro, replicando o tratamento conservador da lógica MQL fonte.
  • Quando o conector não reporta um PriceStep válido, a conversão recorre a 0.0001 para evitar níveis de proteção de distância zero.
  • Nenhuma média ou pirâmide é realizada; a estratégia opera com uma única posição líquida, invertendo a direção quando o Parabolic SAR cruza o preço.

Notas de Conversão

  • MetaTrader InpBarCurrent equivale a 1, significando que o EA avalia o candle finalizado anterior. O port do StockSharp atinge o mesmo resultado processando apenas candles Finished no callback Bind.
  • O consultor especializado original usava CheckVolumeValue para validar lotes e restrições do corretor. O StockSharp delega essas verificações ao conector, enquanto o parâmetro TradeVolume ainda impõe um requisito de volume positivo.
  • A implementação Python é intencionalmente omitida, atendendo aos requisitos da tarefa.
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>
/// Parabolic SAR EA strategy using EMA crossover as proxy.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class ParabolicSarEaStrategy : 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 ParabolicSarEaStrategy()
	{
		_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;
	}
}