Esta estratégia do StockSharp porta o especialista MetaTrader Exp_SpearmanRankCorrelation_Histogram. Assina uma série de velas configurável, calcula o histograma de correlação de ranque de Spearman para cada barra concluída e reage quando o estado codificado por cor muda. Dependendo do modo de negociação, o algoritmo pode fechar posições opostas, reverter para um novo trade ou aguardar valores extremos antes de agir.
Pipeline do indicador
Um indicador RankCorrelationIndex (correlação de ranque de Spearman escalada para ±100) é alimentado com os preços de fechamento das velas. A janela de lookback é limitada por MaxRange e o padrão é de 14 barras.
A correlação bruta é normalizada para o intervalo [-1, 1]. Quando InvertCorrelation está habilitado, o sinal é invertido para emular o flag direction do MQL.
O valor normalizado é comparado com HighLevel e LowLevel para atribuir um estado de cor:
4 – zona fortemente de alta (value > HighLevel).
3 – zona moderadamente de alta (0 < value ≤ HighLevel).
2 – neutral (value == 0).
1 – zona moderadamente de baixa (LowLevel ≤ value < 0).
0 – zona fortemente de baixa (value < LowLevel).
As cores mais recentes são armazenadas em um buffer estilo série para que o índice 0 represente a vela fechada mais recente, o índice 1 a anterior, e assim por diante.
Fluxo de trabalho de trading
Os sinais são avaliados apenas em velas concluídas (CandleStates.Finished).
O parâmetro SignalBar define qual barra concluída é inspecionada (padrão uma barra atrás). A estratégia também olha para a barra imediatamente mais antiga, replicando a pesquisa de buffer duplo do consultor especialista.
Os interruptores de ordem (AllowBuyEntries, AllowSellEntries, AllowBuyExits, AllowSellExits) decidem se posições longas/curtas podem ser abertas ou fechadas.
Os modos de negociação reproduzem o interruptor do MetaTrader:
Modo 1 – fechar a posição oposta sempre que a cor mais antiga for de alta/baixa (> 2 ou < 2). Se permitido, abrir na nova direção quando a cor recente sair da zona de alta (< 3) ou baixa (> 1).
Modo 2 – reagir apenas a cores extremas. O extremo de alta (4) permite à estratégia fechar posições curtas e opcionalmente abrir longas quando a barra mais nova cair abaixo de 4. O extremo de baixa (0) fecha longas e pode abrir curtas quando a barra mais nova subir acima de 0.
Modo 3 – uma versão mais estrita do Modo 2: posições curtas são fechadas imediatamente em 4, longas em 0, e novos trades são permitidos sob as mesmas condições que o Modo 2.
CancelActiveOrders() é executado antes de enviar novas ordens de mercado para evitar solicitações obsoletas.
Reversões de posição usam o Volume configurado mais a posição atual absoluta para que o trade mude completamente para o lado oposto.
StopLossPoints e TakeProfitPoints opcionais (unidades de preço) habilitam gerenciamento de risco baseado em StartProtection; quando deixados em 0, nenhuma ordem de proteção é gerada.
Parâmetros
Parâmetro
Descrição
CandleType
Período de tempo usado para o indicador e decisões de trading.
RangeLength
Período de lookback nominal de Spearman (limitado por MaxRange).
MaxRange
Limite superior para o comprimento de lookback efetivo; cai para 10 se definido como 0.
HighLevel, LowLevel
Limiares que separam zonas de alta e baixa do histograma.
SignalBar
Número de barras fechadas para pular antes de analisar o histograma.
InvertCorrelation
Inverte o sinal do histograma para corresponder ao comportamento direction=false do MQL.
AllowBuyEntries, AllowSellEntries
Habilitar abertura de posições longas/curtas.
AllowBuyExits, AllowSellExits
Habilitar fechamento automático de posições longas/curtas existentes.
TradeMode
Seleciona a lógica do Modo 1, Modo 2 ou Modo 3 do especialista original.
StopLossPoints, TakeProfitPoints
Distâncias de proteção opcionais em unidades de preço absolutas para StartProtection.
Volume (integrado)
Tamanho base de ordem usado ao abrir ou reverter posições.
Diferenças do especialista MetaTrader
Entradas de gestão de capital (MM, MMMode) e slippage (Deviation_) não são replicados; o dimensionamento de posição depende da propriedade padrão Volume e da configuração do corretor.
As funções auxiliares MQL de TradeAlgorithms.mqh são substituídas por chamadas diretas a BuyMarket/SellMarket após cancelar ordens pendentes.
A dica de desempenho CalculatedBars é desnecessária no StockSharp e foi omitida.
O flag direction é representado por InvertCorrelation, que simplesmente espelha o sinal do histograma.
As distâncias de stop-loss e take-profit (StopLoss_, TakeProfit_) são interpretadas como offsets de preço absolutos ao habilitar StartProtection; nenhuma conversão automática de ponto para preço é realizada.
Os tempos de sinal são tratados no fechamento da vela; não há agendamento diferido para a abertura da próxima barra.
Esses ajustes seguem as diretrizes de estratégia de alto nível do StockSharp enquanto preservam a lógica de sinal original.
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 ExpSpearmanRankCorrelationHistogramStrategy : 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 ExpSpearmanRankCorrelationHistogramStrategy()
{
_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 exp_spearman_rank_correlation_histogram_strategy(Strategy):
def __init__(self):
super(exp_spearman_rank_correlation_histogram_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(exp_spearman_rank_correlation_histogram_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(exp_spearman_rank_correlation_histogram_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 exp_spearman_rank_correlation_histogram_strategy()