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
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.
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.
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).
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class parabolic_sar_ea_strategy(Strategy):
def __init__(self):
super(parabolic_sar_ea_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(parabolic_sar_ea_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(parabolic_sar_ea_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return parabolic_sar_ea_strategy()