La Estrategia de Parabolic SAR EA es la conversión de alto nivel de StockSharp del asesor experto de MetaTrader Parabolic SAR EA.mq5 ubicado en MQL/23039. El script MQL original reacciona a las reversiones del Parabolic SAR en un marco temporal configurable, abriendo posiciones de mercado con distancias de stop-loss y take-profit fijas expresadas en "pips" de MetaTrader (soporte de pip fraccional incluido). El port en C# se suscribe a velas, vincula el indicador ParabolicSar integrado y reproduce el mismo proceso de decisión barra a barra respetando las mejores prácticas de StockSharp.
Lógica de Trading
Preparación de datos
La estrategia se suscribe al tipo de vela seleccionado por el usuario (velas de 30 minutos por defecto) y vincula un indicador Parabolic SAR configurado con paso de aceleración y valores máximos ajustables.
El valor SAR se calcula para cada vela y se entrega a la estrategia a través del callback de alto nivel Bind.
Generación de señales
Señal de compra: cuando el valor del Parabolic SAR de la vela terminada está estrictamente por debajo del mínimo de la vela.
Señal de venta: cuando el valor del Parabolic SAR de la vela terminada está estrictamente por encima del máximo de la vela.
Las señales se evalúan solo en velas completadas (CandleStates.Finished) para coincidir con el procesamiento de nueva barra de MQL.
Gestión de posición
La exposición opuesta se aplana antes de una nueva entrada aumentando el tamaño de la orden de mercado solicitada con el valor absoluto de la posición actual, replicando la secuencia de MetaTrader ClosePosition más OpenPosition.
Cada entrada recalcula los niveles de stop-loss y take-profit de protección usando las mismas reglas de conversión pip-a-precio que MetaTrader (los instrumentos de 3/5 dígitos reciben un multiplicador ×10 para el PriceStep).
Salidas de protección
En cada vela terminada la estrategia verifica si el máximo/mínimo viola el nivel de stop-loss o take-profit almacenado. Si se activa, la posición se cierra con una orden de mercado y los objetivos correspondientes se borran.
La lógica de protección se activa antes de las nuevas señales en la misma barra, reflejando el comportamiento original del Asesor Experto donde las órdenes stop son del lado del broker.
Notas sobre Indicador y Datos
Usa el indicador ParabolicSar integrado de StockSharp con parámetros SarStep y SarMaximum.
La suscripción de velas se maneja a través de SubscribeCandles sin agregar el indicador a Strategy.Indicators, según lo requerido por las directrices del proyecto.
El trading solo está permitido cuando IsFormedAndOnlineAndAllowTrading() reporta true, asegurando que los datos en vivo están presentes y el conector permite la presentación de órdenes.
Parámetros
Nombre
Por defecto
Descripción
TradeVolume
1
Tamaño de la orden de mercado en lotes. Actualizar el valor también refresca Strategy.Volume.
StopLossPips
50
Distancia de stop-loss en pips de MetaTrader. Un pip equivale a PriceStep × 10 para instrumentos con 3 o 5 decimales, de lo contrario solo PriceStep. Establecer en 0 para deshabilitar.
TakeProfitPips
50
Distancia de take-profit en pips de MetaTrader usando las mismas reglas de conversión que el stop-loss. Establecer en 0 para deshabilitar.
SarStep
0.02
Paso de aceleración usado por el indicador Parabolic SAR.
SarMaximum
0.2
Factor de aceleración máximo para el Parabolic SAR.
CandleType
30m timeframe
Tipo de vela usado para los cálculos. Soporta cualquier DataType derivado de TimeFrame.
Gestión de Riesgo y Comportamiento
El stop-loss y take-profit se recalculan después de cada ejecución y se almacenan internamente; no se registran órdenes pendientes con el exchange.
Si ambos niveles de protección se activan dentro de una sola vela, la verificación del stop-loss se activa primero, replicando el manejo conservador de la lógica MQL fuente.
Cuando el conector no reporta un PriceStep válido, la conversión recurre a 0.0001 para evitar niveles de protección de distancia cero.
No se realiza promediado ni piramidación; la estrategia opera con una única posición neta, cambiando de dirección cuando el Parabolic SAR cruza el precio.
Notas de Conversión
MetaTrader InpBarCurrent equivale a 1, lo que significa que el EA evalúa la vela terminada anterior. El port de StockSharp logra el mismo resultado procesando solo las velas Finished en el callback Bind.
El asesor experto original usaba CheckVolumeValue para validar lotes y restricciones del broker. StockSharp delega estas verificaciones al conector, mientras que el parámetro TradeVolume aún impone un requisito de volumen positivo.
La implementación de Python se omite intencionalmente, cumpliendo con los requisitos de la tarea.
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()