This StockSharp strategy ports the MetaTrader expert Exp_SpearmanRankCorrelation_Histogram. It subscribes to a configurable candle series, calculates the Spearman rank correlation histogram for each completed bar, and reacts when the color-coded state changes. Depending on the trade mode the algorithm can close opposite positions, reverse into a new trade, or wait for extreme values before acting.
Indicator pipeline
A RankCorrelationIndex indicator (Spearman rank correlation scaled to ±100) is fed with candle closing prices. The lookback window is bounded by MaxRange and defaults to 14 bars.
The raw correlation is normalised to the [-1, 1] interval. When InvertCorrelation is enabled the sign is flipped to emulate the MQL direction flag.
The normalised value is compared with HighLevel and LowLevel to assign a colour state:
4 – strong bullish zone (value > HighLevel).
3 – moderate bullish zone (0 < value ≤ HighLevel).
2 – neutral (value == 0).
1 – moderate bearish zone (LowLevel ≤ value < 0).
0 – strong bearish zone (value < LowLevel).
The latest colours are stored in a series-style buffer so that index 0 represents the most recent closed candle, index 1 the previous one, and so on.
Trading workflow
Signals are evaluated only on finished candles (CandleStates.Finished).
The SignalBar parameter defines which completed bar is inspected (default one bar back). The strategy also looks at the immediately older bar, replicating the double-buffer lookup from the expert advisor.
Order toggles (AllowBuyEntries, AllowSellEntries, AllowBuyExits, AllowSellExits) decide whether long/short positions may be opened or closed.
Trade modes reproduce the MetaTrader switch:
Mode 1 – close the opposite position whenever the older colour is bullish/bearish (> 2 or < 2). If allowed, open in the new direction when the recent colour leaves the bullish (< 3) or bearish (> 1) zone.
Mode 2 – react only to extreme colours. Bullish extreme (4) lets the strategy close shorts and optionally open longs when the newer bar drops below 4. Bearish extreme (0) closes longs and can open shorts when the newer bar rises above 0.
Mode 3 – a stricter version of Mode 2: shorts are closed immediately on 4, longs on 0, and new trades are allowed under the same conditions as Mode 2.
CancelActiveOrders() is executed before sending new market orders to avoid stale requests.
Position reversals use the configured Volume plus the absolute current position so that the trade fully flips to the opposite side.
Optional StopLossPoints and TakeProfitPoints (price units) enable StartProtection based risk management; when left at 0 no protective orders are spawned.
Parameters
Parameter
Description
CandleType
Timeframe used for the indicator and trading decisions.
RangeLength
Nominal Spearman lookback period (capped by MaxRange).
MaxRange
Upper bound for the effective lookback length; falls back to 10 if set to 0.
HighLevel, LowLevel
Thresholds that separate bullish and bearish histogram zones.
SignalBar
Number of closed bars to skip before analysing the histogram.
InvertCorrelation
Flips the histogram sign to match the MQL direction=false behaviour.
AllowBuyEntries, AllowSellEntries
Enable opening long/short positions.
AllowBuyExits, AllowSellExits
Enable automatic closure of existing long/short positions.
TradeMode
Selects Mode 1, Mode 2, or Mode 3 logic from the original expert.
StopLossPoints, TakeProfitPoints
Optional protective distances in absolute price units for StartProtection.
Volume (built-in)
Base order size used when opening or reversing positions.
Differences from the MetaTrader expert
Money-management inputs (MM, MMMode) and slippage (Deviation_) are not replicated; position sizing relies on the standard Volume property and the broker configuration.
The MQL helper functions from TradeAlgorithms.mqh are replaced with direct BuyMarket/SellMarket calls after cancelling pending orders.
The CalculatedBars performance hint is unnecessary in StockSharp and has been omitted.
The direction flag is represented by InvertCorrelation, which simply mirrors the histogram sign.
Stop-loss and take-profit distances (StopLoss_, TakeProfit_) are interpreted as absolute price offsets when enabling StartProtection; no automatic point-to-price conversion is performed.
Signal times are handled at the candle close; there is no deferred scheduling to the next bar opening.
These adjustments follow the high-level StockSharp strategy guidelines while preserving the original signal logic.
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()