Auf GitHub ansehen

Exp XFisher org v1 Strategy

Overview

The strategy reproduces the MetaTrader 5 expert Exp_XFisher_org_v1. It trades reversals detected on the Fisher transform of price that is additionally smoothed with a configurable moving average. The StockSharp port keeps the counter-trend nature of the original robot: when the Fisher curve turns downward after an upswing a long position is opened, and when the curve turns upward after a downswing a short position is opened. Existing positions are closed once the indicator reverses in the opposite direction.

The helper indicator XFisherOrgIndicator implemented in CS/ExpXFisherOrgV1Strategy.cs follows the MT5 logic:

  1. Take the highest high and lowest low over Length finished candles.
  2. Convert the selected price source (see Applied Price below) into the 0–1 range using those extremes.
  3. Apply the recursive filter value = (wpr - 0.5) + 0.67 * value[prev] followed by the Fisher transform fish = 0.5 * ln((1 + value) / (1 - value)) + 0.5 * fish[prev].
  4. Smooth the result with one of the supported moving averages. The smoothed Fisher value forms the main line; the signal line is simply the previous bar’s smoothed value, exactly as in the MQL version where buffer #1 stores a one-bar shift.

The conversion keeps the original defaults (Length = 7, Jurik smoothing of length 5, phase 15, H4 candles) and exposes the same enable/disable switches for opening and closing long/short trades.

Trading rules

  • Long entry – when the Fisher value from SignalBar + 1 bars ago was rising (Fisher[SignalBar+1] > Fisher[SignalBar+2]) but the value at SignalBar crosses below or touches its delayed copy (Fisher[SignalBar] <= Fisher[SignalBar+1]).
  • Short entry – when the Fisher value from SignalBar + 1 bars ago was falling but the value at SignalBar crosses above its delayed copy.
  • Position exit – the opposite reversal closes an existing position before considering a new trade. A long exit is triggered by the same condition that opens a short, and vice versa.
  • Volume – controlled by OrderVolume. When a flip from short to long (or long to short) is required the strategy sends a single market order with enough volume to close the old position and open the new one in the same transaction, mimicking the behaviour of the original BuyPositionOpen/SellPositionOpen helpers.

All calculations use finished candles only. If SignalBar is zero the current closed candle is used for signal evaluation; positive values shift the signal back in time exactly like the MT5 SignalBar input.

Parameters

Name Description Default
OrderVolume Volume of every market order. 1
BuyOpenAllowed / SellOpenAllowed Permit opening long/short trades. true
BuyCloseAllowed / SellCloseAllowed Permit closing existing long/short trades. true
SignalBar Shift (in closed candles) used to read the Fisher buffers. 1
Length Lookback for highest/lowest price extremes. 7
SmoothingLength Period of the smoothing average. 5
Phase Jurik phase (ignored by other methods). 15
SmoothingMethod Moving average applied to the Fisher output. Jjma
PriceType Applied price forwarded to the indicator (close, open, median, etc.). Close
CandleType Candle series used for the calculation (default: 4 hour candles). H4

Smoothing method mapping

The original indicator exposes a large set of smoothing kernels. The StockSharp port maps them to reliable built-in implementations:

  • Jjma, Jurx, T3JurikMovingAverage (phase parameter applied when the property is available).
  • Sma, Ema, Smma, Lwma → respective StockSharp moving averages.
  • Parabolic → approximated by ExponentialMovingAverage (closest behaviour under StockSharp).
  • Vidya, AmaKaufmanAdaptiveMovingAverage (the adaptive VIDYA behaviour is modelled with Kaufman AMA).

This mapping mirrors the approach used in other Kositsin conversions inside the repository and keeps the response of the smoothed Fisher line comparable to the MT5 implementation.

Differences from the MT5 expert

  • Money management – StockSharp strategies operate on explicit volumes. The MM/MarginMode inputs from MT5 are replaced with a single OrderVolume parameter so the trader can define the lot size directly.
  • Execution model – trades are generated once per finished candle via the high-level subscription API instead of on every tick. This avoids duplicate orders and removes the need for the original IsNewBar helper.
  • Applied price options – all price modes from SmoothAlgorithms.mqh are supported, including TrendFollow and Demark variants.
  • Charting – the strategy draws candles, the smoothed Fisher transform and the executed trades in the default chart area.

Files

  • CS/ExpXFisherOrgV1Strategy.cs – strategy class, indicator implementation and value container.
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;

/// <summary>
/// Exp XFisher org v1 strategy using EMA crossover as trend filter.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class ExpXFisherOrgV1Strategy : 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;

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ExpXFisherOrgV1Strategy"/> class.
	/// </summary>
	public ExpXFisherOrgV1Strategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 7)
			.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");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

	/// <inheritdoc />
	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;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// EMA crossover
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 80;
		}
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}