Ver en GitHub

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

  1. 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.
  2. 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.
  3. 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.
  4. 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).
  5. 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();
			}
		}
	}
}