SuperForexV2 Strategy
Overview
SuperForexV2 is a StockSharp port of the MetaTrader 4 expert advisor SuperForexV2.mq4. The original script combines a short-term
Relative Strength Index (RSI) oscillator with fixed take-profit, stop-loss and trailing stop distances. The C# implementation
rebuilds the same decision process with the high-level StockSharp API: the strategy observes finished candles, reacts to RSI
threshold crossings, and manages a single net position using pip-based risk limits.
Trading Logic
- Indicator pipeline
- Subscribes to the configurable candle series (15-minute bars by default) and feeds every finished bar into an RSI indicator.
- The RSI length is configurable and defaults to the original MT4 value of 4.
- Dynamic position sizing
- Before every entry the strategy derives a working lot size from the current portfolio value divided by
BalanceToVolumeDivider.
- The resulting volume is clamped by
InitialVolume (fallback when the balance is unknown) and MaxVolume, then rounded to the
instrument’s volume step.
- Entry rules
- When there is no open position and RSI falls below
RsiLowerLevel, a market buy order is placed.
- When RSI rises above
RsiUpperLevel, a market sell order is submitted.
- Exit and risk management
- Each position stores absolute stop-loss and take-profit levels computed from the pip-based distances.
- On every finished candle the strategy checks whether the bar touched those levels; if so, it closes the position at market.
- A trailing stop mimics the MT4 logic: once price has advanced by at least
TrailingStopPips, the stop is pulled closer so the
current profit is locked in.
- Positions are also closed whenever the RSI crosses to the opposite extreme (e.g., longs exit when RSI exceeds the upper level).
- Position scope
- The bot mirrors the EA’s “one trade per symbol” behaviour by enforcing a flat book before evaluating new entries.
Parameters
| Name |
Description |
Default |
Notes |
CandleType |
Candle series driving the indicator calculations. |
15m time frame |
Accepts any DataType supported by the connector. |
RsiPeriod |
RSI lookback length. |
4 |
Must be greater than zero. |
RsiUpperLevel |
Overbought threshold used for shorts and long exits. |
62 |
Matches the MT4 input Pos. |
RsiLowerLevel |
Oversold threshold used for longs and short exits. |
42 |
Matches the MT4 input Neg. |
TakeProfitPips |
Take-profit distance expressed in pips. |
109 |
Set to 0 to disable the take-profit. |
StopLossPips |
Stop-loss distance expressed in pips. |
9 |
Set to 0 to disable the stop-loss. |
TrailingStopPips |
Trailing stop distance expressed in pips. |
6 |
Set to 0 to turn off trailing behaviour. |
InitialVolume |
Fallback lot size when the portfolio balance is not available. |
0.1 |
Also used if dynamic sizing yields a non-positive value. |
MaxVolume |
Maximum volume allowed per entry. |
100 |
Prevents the balance-based sizing from overscaling. |
BalanceToVolumeDivider |
Divider applied to the account balance to compute volume. |
10000 |
Replicates the MT4 formula Lots = AccountBalance()/10000. |
Implementation Notes
- Candle processing happens only after
CandleStates.Finished to mirror MT4’s start() tick-end behaviour while avoiding
incomplete data.
- Pip distances are converted into absolute prices using the instrument’s
PriceStep. For 3- and 5-digit Forex symbols the code
multiplies the step by ten so the StockSharp “pip” matches the MetaTrader point definition.
- Stop-loss, take-profit and trailing levels are stored internally and checked against candle highs and lows, because StockSharp
does not automatically manage MT4-style order-level stops.
- The strategy rounds the computed volume to the nearest valid lot while respecting
MinVolume, MaxVolume and VolumeStep
limits exposed by the security.
- Only one net position is allowed at a time; the entry logic exits early if the strategy is already long or short.
Differences Compared to the MT4 Version
- The StockSharp port works on finished candles instead of individual ticks, so intrabar stop or target hits are detected on the
next bar close.
- MetaTrader’s
AccountFreeMargin() guard is replaced by a safer balance-derived volume; if the connector cannot provide the
portfolio value the fallback InitialVolume is used instead of aborting.
- Order stop-loss and take-profit values are not sent to the broker. Instead, the strategy closes positions at market once a level
is breached, because high-level StockSharp orders rely on strategy-managed exits.
- The
NumeroMagico input used to filter MT4 orders is unnecessary in StockSharp and has been omitted.
- Logging messages from the original EA are not reproduced; StockSharp’s own logging facilities should be used if further
instrumentation is needed.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// SuperForexV2: RSI threshold reversal with ATR trailing stop.
/// Buys when RSI below lower level, sells when above upper level.
/// </summary>
public class SuperForexV2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _upperLevel;
private readonly StrategyParam<decimal> _lowerLevel;
private decimal _entryPrice;
private decimal _trailStop;
public SuperForexV2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_rsiLength = Param(nameof(RsiLength), 4)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for trailing.", "Indicators");
_upperLevel = Param(nameof(UpperLevel), 62m)
.SetDisplay("RSI Upper", "Overbought for shorts.", "Signals");
_lowerLevel = Param(nameof(LowerLevel), 42m)
.SetDisplay("RSI Lower", "Oversold for longs.", "Signals");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal UpperLevel
{
get => _upperLevel.Value;
set => _upperLevel.Value = value;
}
public decimal LowerLevel
{
get => _lowerLevel.Value;
set => _lowerLevel.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_trailStop = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_trailStop = 0;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
return;
var close = candle.ClosePrice;
// Trailing stop + opposite RSI exit
if (Position > 0)
{
var newTrail = close - atrVal * 1.5m;
if (newTrail > _trailStop)
_trailStop = newTrail;
if (close <= _trailStop || rsiVal > UpperLevel)
{
SellMarket();
_entryPrice = 0;
_trailStop = 0;
}
}
else if (Position < 0)
{
var newTrail = close + atrVal * 1.5m;
if (_trailStop == 0 || newTrail < _trailStop)
_trailStop = newTrail;
if (close >= _trailStop || rsiVal < LowerLevel)
{
BuyMarket();
_entryPrice = 0;
_trailStop = 0;
}
}
// Entry on RSI levels
if (Position == 0)
{
if (rsiVal < LowerLevel)
{
_entryPrice = close;
_trailStop = close - atrVal * 2m;
BuyMarket();
}
else if (rsiVal > UpperLevel)
{
_entryPrice = close;
_trailStop = close + atrVal * 2m;
SellMarket();
}
}
}
}
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 RelativeStrengthIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class super_forex_v2_strategy(Strategy):
"""RSI threshold reversal with ATR trailing stop."""
def __init__(self):
super(super_forex_v2_strategy, self).__init__()
self._rsi_length = self.Param("RsiLength", 4).SetDisplay("RSI Length", "RSI period", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "ATR period for trailing", "Indicators")
self._upper_level = self.Param("UpperLevel", 62.0).SetDisplay("RSI Upper", "Overbought for shorts", "Signals")
self._lower_level = self.Param("LowerLevel", 42.0).SetDisplay("RSI Lower", "Oversold for longs", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(super_forex_v2_strategy, self).OnReseted()
self._entry_price = 0
self._trail_stop = 0
def OnStarted2(self, time):
super(super_forex_v2_strategy, self).OnStarted2(time)
self._entry_price = 0
self._trail_stop = 0
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(rsi, atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
if atr_val <= 0:
return
close = float(candle.ClosePrice)
rsi_val = float(rsi_val)
atr_val = float(atr_val)
upper = self._upper_level.Value
lower = self._lower_level.Value
# Trailing stop + opposite RSI exit
if self.Position > 0:
new_trail = close - atr_val * 1.5
if new_trail > self._trail_stop:
self._trail_stop = new_trail
if close <= self._trail_stop or rsi_val > upper:
self.SellMarket()
self._entry_price = 0
self._trail_stop = 0
elif self.Position < 0:
new_trail = close + atr_val * 1.5
if self._trail_stop == 0 or new_trail < self._trail_stop:
self._trail_stop = new_trail
if close >= self._trail_stop or rsi_val < lower:
self.BuyMarket()
self._entry_price = 0
self._trail_stop = 0
# Entry on RSI levels
if self.Position == 0:
if rsi_val < lower:
self._entry_price = close
self._trail_stop = close - atr_val * 2
self.BuyMarket()
elif rsi_val > upper:
self._entry_price = close
self._trail_stop = close + atr_val * 2
self.SellMarket()
def CreateClone(self):
return super_forex_v2_strategy()