PSAR Estratégia de vários prazos
Visão geral
A estratégia replica o consultor especialista MetaTrader EA_PSar_002B. Ele avalia valores Parabolic SAR em três períodos de tempo (M15, M30 e H1) enquanto gerencia posições em um fluxo de um minuto. A negociação é direcional: apenas uma posição líquida pode estar ativa por vez e novas negociações aparecem apenas quando a exposição anterior é estável. O especialista original foi projetado para EURUSD no gráfico M1 e a porta mantém o mesmo contexto.
Lógica de negociação
- Parabolic SAR filtro de convergência – os valores SAR mais recentes de M15, M30 e H1 devem estar dentro de 19 etapas de preço mínimo um do outro. Isso mantém as três curvas “apertadas” antes que um rompimento seja permitido.
- Entrada longa – uma das seguintes sequências deve ocorrer:
- Os valores M15, M30 e H1 SAR estão abaixo de seus respectivos mínimos atuais, o H1 anterior SAR estava acima da máxima H1 anterior e o novo H1 SAR cai abaixo da mínima H1 atual.
- M15 e H1 SAR estão abaixo de seus mínimos atuais, enquanto o M30 anterior SAR estava acima da máxima anterior de M30 e o novo M30 SAR cai abaixo da mínima atual de M30.
- M30 e H1 SAR estão abaixo de seus mínimos atuais, enquanto o M15 anterior SAR estava acima da máxima anterior de M15 e o novo M15 SAR cai abaixo da mínima atual de M15.
- Entrada curta – condições de espelho da configuração longa com altos/baixos invertidos.
- Take Profit/Stop Loss – os limites são expressos em pontos (incrementos mínimos de preço). Por padrão, a meta é igual a 999 pontos e o limite de proteção é igual a 399 pontos, que correspondem aos valores MQL após a normalização das cotações de 4/5 dígitos.
- Saída dinâmica – enquanto uma posição está aberta o M30 SAR é monitorado.
- As posições compradas fecham quando o SAR anterior estava abaixo da mínima M1 anterior, mas o SAR atual salta acima da máxima M1 atual.
- As vendas fecham quando o SAR anterior estava acima da máxima M1 anterior, mas o SAR atual cai abaixo da mínima M1 atual.
- Quando o M30 atual SAR ultrapassa o preço de entrada, o stop é rastreado até esse nível SAR.
Gestão de dinheiro
UseMoneyManagement reproduz a mudança de gerenciamento de dinheiro do EA. Quando desativado, o parâmetro FixedVolume é usado. Quando ativado, a porcentagem solicitada de capital do portfólio é convertida para um tamanho de “lote” sintético usando a mesma fórmula da versão MQL (porcentagem de capital livre dividida por 100.000). O valor é alinhado a Security.VolumeStep e limitado aos limites do corretor (VolumeMin/VolumeMax).
Parâmetros
BaseCandleType – prazo usado para gerenciamento comercial (o padrão é M1).
FastSarCandleType, MediumSarCandleType, SlowSarCandleType – prazos para os filtros SAR (padrões: 15m, 30m, 60m).
EnableParabolicFilter – espelha o sinalizador sar2 de MQL; desligá-lo interrompe completamente a negociação.
TakeProfitPoints, StopLossPoints – compensações em pontos (incrementos mínimos de preço). O tamanho do pip é derivado de Security.PriceStep e Security.Decimals para lidar corretamente com cotações forex de 3/5 dígitos.
UseMoneyManagement, PercentMoneyManagement, FixedVolume – controles de volume descritos acima.
Notas de conversão
- Somente o StockSharp API de alto nível é usado. Todas as séries de preços são assinadas por meio de
SubscribeCandles().Bind(...) e os dados do indicador são recebidos por meio de ligações em vez de buffers manuais.
- As ordens de proteção são implementadas por saídas explícitas do mercado, exatamente como o script original chamado
OrderClose.
- O coeficiente de dígito do corretor de MQL é substituído pela detecção automática do tamanho do pip (
PriceStep × 10 para instrumentos de 3/5 dígitos).
- O EA proibiu a negociação de símbolos não EURUSD ou gráficos não M1 por meio da impressão de mensagens. Em StockSharp os logs de estratégia permanecem silenciosos, mas o comportamento está documentado aqui.
Dicas de uso
- Anexe a estratégia ao EURUSD com velas de um minuto para a assinatura base. Os prazos dos indicadores ainda podem ser alterados se a experimentação for desejada.
- Certifique-se de que os metadados de segurança exponham
PriceStep/Decimals. Sem eles, as distâncias de parada e alvo voltam para um tamanho de unidade de 1.
- Mantenha
EnableParabolicFilter ativado; é equivalente à chave mestre do EA. Desative-o apenas quando desejar intencionalmente que a estratégia permaneça ociosa.
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()