Esta estratégia é um port em C# do especialista MetaTrader 5 Exp_SR-RateIndicator. Reproduz a lógica de trading original usando a API de alto nível do StockSharp e uma implementação personalizada do oscilador SR Rate. O indicador mede o quão longe o preço ponderado da vela está dentro de um canal de suporte/resistência suavizado e pinta um código de cor que destaca leituras extremas.
O algoritmo processa velas concluídas de um período de tempo configurável. Sempre que a cor do oscilador salta para o extremo de alta ou baixa, a estratégia fecha qualquer posição oposta e abre um novo trade na direção do sinal. Níveis de stop loss e take profit protetores são aplicados com as mesmas distâncias em pontos usadas na versão do MetaTrader.
Oscilador SR Rate
O indicador calcula uma banda suavizada gaussiana em torno do preço usando um comprimento de janela configurável:
Para cada barra, o máximo, mínimo e fechamento ponderado são suavizados com pesos gaussianos unilaterais de comprimento seis.
O máximo suavizado mais alto e o mínimo suavizado mais baixo sobre a janela definem um intervalo dinâmico.
O fechamento ponderado suavizado atual é normalizado dentro desse intervalo e mapeado para o intervalo [-100, 100].
O valor final do oscilador é convertido em cinco estados de cor: 0 (fortemente de baixa), 1 (levemente de baixa), 2 (neutro), 3 (levemente de alta) e 4 (fortemente de alta).
Uma cor fortemente de alta (4) indica que o preço atingiu o extremo superior do intervalo, enquanto uma cor fortemente de baixa (0) sinaliza uma visita ao extremo inferior.
Regras de trading
Assinar velas do tipo configurado e calcular o oscilador SR Rate em cada barra concluída.
Deslocar a avaliação de sinais por SignalBar velas fechadas (padrão: uma barra atrás) para imitar o comportamento do Expert Advisor.
Quando a cor deslocada se torna 4 e a cor anterior é menor que 4:
Fechar qualquer posição curta existente se as saídas longas estiverem habilitadas.
Abrir uma nova posição longa se as entradas longas estiverem habilitadas e nenhuma outra posição estiver ativa.
Quando a cor deslocada se torna 0 e a cor anterior é maior que 0:
Fechar qualquer posição longa existente se as saídas curtas estiverem habilitadas.
Abrir uma nova posição curta se as entradas curtas estiverem habilitadas e nenhuma outra posição estiver ativa.
Apenas uma posição pode estar aberta a qualquer momento. Novos sinais são ignorados até que o trade anterior seja fechado.
Níveis opcionais de stop loss e take profit são expressos em pontos de preço e convertidos automaticamente para preços absolutos usando o passo de preço do instrumento.
Parâmetros
Nome
Descrição
OrderVolume
Volume de negociação usado para cada ordem de mercado.
EnableLongEntries
Habilitar/desabilitar abertura de posições longas.
EnableShortEntries
Habilitar/desabilitar abertura de posições curtas.
EnableLongExits
Fechar posições longas quando uma cor fortemente de baixa aparecer.
EnableShortExits
Fechar posições curtas quando uma cor fortemente de alta aparecer.
StopLossPoints
Distância do stop loss em pontos do instrumento (convertido usando o passo de preço).
TakeProfitPoints
Distância do take profit em pontos do instrumento (convertido usando o passo de preço).
SlippagePoints
Slippage máximo tolerado ao fechar posições. Preservado por compatibilidade; nenhum controle explícito de slippage é aplicado pela API de alto nível.
CandleType
Tipo de vela e período de tempo usados para calcular o indicador.
SignalBar
Número de barras fechadas para pular antes de analisar o histograma (padrão 1).
WindowSize
Comprimento da janela deslizante usada pela normalização SR Rate.
HighLevel
Nível do oscilador que define o extremo de alta (padrão +20).
LowLevel
Nível do oscilador que define o extremo de baixa (padrão -20).
Notas
A estratégia funciona com qualquer instrumento que forneça velas OHLC padrão.
Os sinais são processados apenas em velas concluídas; recálculos intrabarra são ignorados, assim como na implementação do MetaTrader.
O tratamento de slippage no especialista original dependia das configurações de execução. As ordens de mercado do StockSharp já respeitam as regras do exchange, portanto o parâmetro SlippagePoints é mantido apenas para fins de documentação.
O indicador armazena apenas a quantidade mínima de histórico necessária para avaliar a janela, evitando uso desnecessário de memória.
A versão Python é omitida intencionalmente de acordo com as diretrizes do projeto.
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;
public class SrRateIndicatorStrategy : 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 SrRateIndicatorStrategy()
{
_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 sr_rate_indicator_strategy(Strategy):
def __init__(self):
super(sr_rate_indicator_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(sr_rate_indicator_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(sr_rate_indicator_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 sr_rate_indicator_strategy()