This strategy is a high-level C# port of the MetaTrader Expert Advisor located in MQL/919/champion.mq5. The original EA waits for a Relative Strength Index (RSI) signal and places three stop orders in the direction of the anticipated breakout. Every pending order already includes a stop-loss and take-profit and the stop-loss is trailed whenever price moves favourably. The StockSharp version keeps the same behaviour while relying exclusively on high-level API calls (SubscribeCandles, Bind, BuyStop, SellStop, etc.).
The default configuration targets liquid FX instruments where the MetaTrader "point" matches the StockSharp PriceStep (typically 0.0001). The candle type is configurable and the strategy can be applied to any time frame as long as the account provides best bid/ask quotes and, optionally, stop level information.
Strategy logic
Signal generation
An RSI of configurable length is calculated on completed candles.
The previous RSI value (one closed bar ago) is compared against a symmetric threshold (RsiLevel).
RSI < RsiLevel triggers a bullish setup; RSI > 100 - RsiLevel triggers a bearish setup.
Pending order placement
When there are no open positions and no active pending orders managed by the strategy, three identical stop orders are placed in the signalled direction.
Buy stops are placed above the best ask, sell stops below the best bid. The distance respects the server-provided stop level (if available) or the MinOrderDistancePoints fallback.
Order volume is calculated dynamically: available account value divided by BalancePerLot, clamped to the [0.1, 15] lot range and rounded to two decimals. Each pending order receives one third of the computed volume.
Initial protective orders
As soon as the first trade is filled, aggregated protective orders are registered: stop-loss at entry ± StopLossPoints and take-profit at entry ± TakeProfitPoints (MetaTrader points converted to price by PriceStep).
If TakeProfitPoints is zero the take-profit order is disabled.
Trailing stop
While a position is open the stop-loss order is tightened on every level-1 update.
For longs the new stop equals max(entry + spread, bid - StopLoss); for shorts min(entry - spread, ask + StopLoss).
Trailing is activated only when the move exceeds the sum of the broker stop level and the current spread, reproducing the original EA safeguards.
Pending order maintenance
Pending buy stops are moved closer to the market when their activation price is more than RepriceDistancePoints away from the current ask. The same logic applies to sell stops versus the current bid.
Repricing always honours the greater of RepriceDistancePoints and the effective stop level distance.
Position exit
Positions close via the protective stop-loss/take-profit orders or by manual user intervention. When the position size returns to zero the strategy cancels any remaining protective orders and waits for the next RSI signal.
Parameters
Parameter
Description
TakeProfitPoints
MetaTrader points added/subtracted from the fill price to place the take-profit order. Set to 0 to disable the target.
StopLossPoints
MetaTrader points added/subtracted from the fill price to place the stop-loss order and to compute the trailing distance.
Account currency amount considered equivalent to one standard lot when sizing positions.
MinOrderDistancePoints
Fallback minimum distance (in points) between the market price and new stop orders when the trading venue does not report a stop level.
RepriceDistancePoints
Distance (in points) that triggers pending order repricing.
CandleType
Candle data type used for the RSI calculation.
Usage notes
The strategy requires both candle data and level-1 quotes (best bid/ask). Without level-1 updates the trailing logic and pending-order maintenance are disabled.
When the broker exposes a stop-level or stop-distance through level-1 metadata, it is automatically honoured. Otherwise configure MinOrderDistancePoints to match the instrument requirements.
Position sizing falls back to the Strategy.Volume property whenever portfolio information is missing or the computed lot size becomes non-positive.
Three pending orders are always placed together. Cancel unwanted orders manually if partial participation is required; the strategy will continue managing the remaining ones.
Risk management
Stop-loss and take-profit orders are native exchange/broker orders, mirroring the behaviour of the MetaTrader EA. When a position closes the protective orders are cancelled immediately.
The trailing stop only moves in the direction of profit and never loosens the stop-loss. It activates once price has travelled at least (StopLevel + spread) beyond the entry price.
Repricing logic prevents stale pending orders from being left behind after large jumps, reducing the probability of delayed fills.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Champion: RSI breakout with EMA trend filter and ATR trailing.
/// </summary>
public class ChampionStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevRsi;
private decimal _entryPrice;
public ChampionStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_emaLength = Param(nameof(EmaLength), 50)
.SetDisplay("EMA Length", "Trend filter.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = 0;
_entryPrice = 0;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ema, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevRsi == 0 || atrVal <= 0)
{
_prevRsi = rsiVal;
return;
}
var close = candle.ClosePrice;
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 2m || rsiVal > 70)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 2m || rsiVal < 30)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (_prevRsi <= 30 && rsiVal > 30 && close > emaVal)
{
_entryPrice = close;
BuyMarket();
}
else if (_prevRsi >= 70 && rsiVal < 70 && close < emaVal)
{
_entryPrice = close;
SellMarket();
}
}
_prevRsi = rsiVal;
}
}
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 RelativeStrengthIndex, ExponentialMovingAverage, AverageTrueRange
class champion_strategy(Strategy):
def __init__(self):
super(champion_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._ema_length = self.Param("EmaLength", 50) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_rsi = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(champion_strategy, self).OnStarted2(time)
self._prev_rsi = 0.0
self._entry_price = 0.0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self._ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, rsi_val, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_val)
ev = float(ema_val)
av = float(atr_val)
if self._prev_rsi == 0 or av <= 0:
self._prev_rsi = rv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if close >= self._entry_price + av * 3.0 or close <= self._entry_price - av * 2.0 or rv > 70:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 3.0 or close >= self._entry_price + av * 2.0 or rv < 30:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = rv
return
if self.Position == 0:
if self._prev_rsi <= 30 and rv > 30 and close > ev:
self._entry_price = close
self.BuyMarket()
elif self._prev_rsi >= 70 and rv < 70 and close < ev:
self._entry_price = close
self.SellMarket()
self._prev_rsi = rv
def OnReseted(self):
super(champion_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._entry_price = 0.0
def CreateClone(self):
return champion_strategy()