PSAR Estrategia de múltiples plazos
Descripción general
La estrategia replica al asesor experto MetaTrader EA_PSar_002B. Evalúa los valores Parabolic SAR en tres períodos de tiempo (M15, M30 y H1) mientras gestiona posiciones en una secuencia de un minuto. El comercio es direccional: solo puede haber una posición neta activa a la vez y las nuevas operaciones aparecen solo cuando la exposición anterior es plana. El experto original fue diseñado para EURUSD en el gráfico M1 y el puerto mantiene el mismo contexto.
Lógica comercial
- Parabolic SAR filtro de convergencia: los últimos valores SAR de M15, M30 y H1 deben estar dentro de 19 pasos de precio mínimo entre sí. Esto mantiene las tres curvas "apretadas" antes de que se permita una ruptura.
- Entrada larga: debe ocurrir una de las siguientes secuencias:
- Los valores de M15, M30 y H1 SAR están por debajo de sus respectivos mínimos actuales, el H1 anterior SAR estuvo por encima del máximo del H1 anterior y el nuevo H1 SAR cae por debajo del mínimo del H1 actual.
- M15 y H1 SAR están por debajo de sus mínimos actuales, mientras que el M30 anterior SAR estaba por encima del máximo anterior del M30 y el nuevo M30 SAR cae por debajo del mínimo actual del M30.
- M30 y H1 SAR están por debajo de sus mínimos actuales, mientras que el M15 anterior SAR estaba por encima del máximo anterior de M15 y el nuevo M15 SAR cae por debajo del mínimo actual de M15.
- Entrada corta: refleja las condiciones de la configuración larga con máximos y mínimos invertidos.
- Take Profit / Stop Loss – los límites se expresan en puntos (incrementos mínimos de precio). Por defecto, el objetivo equivale a 999 puntos y el stop de protección equivale a 399 puntos, que corresponden a los valores MQL después de normalizar cotizaciones de 4/5 dígitos.
- Salida dinámica: mientras una posición está abierta, se monitorea la M30 SAR.
- Los largos cierran cuando el SAR anterior estaba por debajo del mínimo M1 anterior pero el SAR actual salta por encima del máximo M1 actual.
- Los cortos se cierran cuando el SAR anterior estaba por encima del máximo anterior de M1, pero el SAR actual cae por debajo del mínimo actual de M1.
- Cuando el M30 actual SAR cruza más allá del precio de entrada, el stop se arrastra hasta ese nivel SAR.
gestión del dinero
UseMoneyManagement reproduce el interruptor de administración de dinero del EA. Cuando está deshabilitado, se utiliza el parámetro FixedVolume. Cuando está habilitado, el porcentaje solicitado del capital de la cartera se convierte a un tamaño de "lote" sintético utilizando la misma fórmula que la versión MQL (porcentaje del capital libre dividido por 100.000). El importe se alinea con Security.VolumeStep y se recorta según los límites del corredor (VolumeMin/VolumeMax).
Parámetros
BaseCandleType: período de tiempo utilizado para la gestión comercial (el valor predeterminado es M1).
FastSarCandleType, MediumSarCandleType, SlowSarCandleType: plazos para los filtros SAR (predeterminados: 15 m, 30 m, 60 m).
EnableParabolicFilter – refleja la bandera sar2 de MQL; apagarlo deja de operar por completo.
TakeProfitPoints, StopLossPoints – compensaciones en puntos (incrementos mínimos de precio). El tamaño del pip se deriva de Security.PriceStep y Security.Decimals para manejar correctamente las cotizaciones de divisas de 3/5 dígitos.
UseMoneyManagement, PercentMoneyManagement, FixedVolume: controles de volumen descritos anteriormente.
Notas de conversión
- Sólo se utiliza el StockSharp API de alto nivel. Todas las series de precios se suscriben a través de
SubscribeCandles().Bind(...) y los datos del indicador se reciben a través de enlaces en lugar de buffers manuales.
- Las órdenes de protección se implementan mediante salidas explícitas del mercado, exactamente como el script original que llamaba
OrderClose.
- El coeficiente de dígitos del corredor de MQL se reemplaza por la detección automática del tamaño del pip (
PriceStep × 10 para instrumentos de 3/5 dígitos).
- El EA prohibió operar con símbolos que no sean EURUSD o gráficos que no sean M1 mediante la impresión de mensajes. En StockSharp los registros de estrategia permanecen silenciosos, pero el comportamiento está documentado aquí.
Consejos de uso
- Adjunte la estrategia al EURUSD con velas de un minuto para la suscripción base. Los plazos de los indicadores aún se pueden cambiar si se desea experimentar.
- Asegúrese de que los metadatos de seguridad expongan
PriceStep/Decimals. Sin ellos, las distancias de parada y objetivo vuelven a ser un tamaño unitario de 1.
- Mantenga
EnableParabolicFilter habilitado; es equivalente al interruptor maestro del EA. Deshabilítelo sólo cuando desee intencionalmente que la estrategia permanezca inactiva.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// PSAR Multi Timeframe: EMA crossover with RSI filter and ATR stops.
/// </summary>
public class PsarMultiTimeframeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public PsarMultiTimeframeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 12)
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 26)
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, rsi, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 50) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 50) { _entryPrice = close; SellMarket(); }
}
_prevFast = fastVal; _prevSlow = slowVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
class psar_multi_timeframe_strategy(Strategy):
def __init__(self):
super(psar_multi_timeframe_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 12) \
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 26) \
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(psar_multi_timeframe_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
rv = float(rsi_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fv < sv and self._prev_fast >= self._prev_slow) or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fv > sv and self._prev_fast <= self._prev_slow) or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow and rv > 50:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow and rv < 50:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(psar_multi_timeframe_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return psar_multi_timeframe_strategy()