Ver en GitHub

DLMv FX Fish Grid Strategy

Overview

The DLMv FX Fish Grid Strategy replicates the behaviour of the original MetaTrader expert advisor built around the "FX Fish 2MA" oscillator. The strategy evaluates the Fisher Transform of price, smooths it with a moving average and opens positions when the oscillator crosses its smoothed baseline on the appropriate side of zero. Position management mimics the grid-like behaviour of the source EA: additional entries are spaced by a configurable distance, pending limit orders can be layered, and protective automation handles risk controls.

Trading Logic

  1. Indicator calculation
    • Highest and lowest prices over CalculatePeriod candles define the rolling range.
    • A Fisher Transform is applied to the selected price (AppliedPrice), using the same 0.67 smoothing factor as the MT5 indicator.
    • A simple moving average (MaPeriod) of the Fisher value provides the signal baseline.
  2. Signal generation
    • Long signal: current and previous Fisher values are below zero while the oscillator crosses above its moving average (previous value below average, current value above).
    • Short signal: current and previous Fisher values are above zero while the oscillator crosses below the moving average (previous value above average, current value below).
    • Signals can be inverted by enabling ReverseSignals.
  3. Order execution
    • When a buy (or sell) signal appears, the strategy can optionally close existing opposite exposure (CloseOpposite).
    • Additional entries are allowed until the total count reaches MaxTrades. Every new entry must respect the minimum spacing given by DistancePips from the latest filled trade.
    • Optional limit orders (SetLimitOrders) place resting bids/asks at the configured spacing, replicating the staged grid from the original EA.
  4. Risk management
    • Fixed stop-loss, take-profit and trailing stop values are applied via StartProtection, all defined in pips.
    • TimeLiveSeconds closes all exposure when a trade has been open longer than the allowed lifetime.
    • Trading can be disabled during Fridays (TradeOnFriday = false). When disabled the strategy closes positions and cancels pending orders as soon as a Friday candle arrives.

Parameters

Parameter Description
OrderVolume Order size for each entry (lots).
StopLossPips Distance of the protective stop-loss from the entry. Set to 0 to disable.
TakeProfitPips Distance of the take-profit level. Set to 0 to disable.
TrailingStopPips Trailing stop distance (0 disables trailing).
TrailingStepPips Step by which the trailing stop is tightened.
MaxTrades Maximum number of simultaneous trades per direction. 0 removes the limit.
DistancePips Minimum distance between consecutive entries and for the optional grid orders.
TradeOnFriday When false, the strategy stops trading on Fridays and liquidates exposure.
TimeLiveSeconds Maximum time (seconds) that positions may remain open before being force-closed.
ReverseSignals Invert long/short conditions.
SetLimitOrders Enable additional resting limit orders at DistancePips.
CloseOpposite Close opposite exposure before entering a new trade.
CalculatePeriod Lookback for the Fisher Transform range.
MaPeriod Period of the moving average applied to the Fisher value.
AppliedPrice Price source used in the Fisher Transform (close, open, high, low, median, typical, weighted).
CandleType Data type / timeframe of the candles processed by the strategy.

Notes

  • The stop-loss, take-profit and trailing stop distances are converted from pips to absolute price offsets using Security.PriceStep * 10, matching the five-digit pip logic of the MQL version.
  • Limit orders are automatically cancelled when signals flip, trading is paused, or lifetime/Friday protections trigger.
  • The Fisher Transform avoids repeated value lookups, instead storing the previous oscillator and baseline readings for precise cross detection.
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// DLMv FX Fish Grid strategy. Uses Highest/Lowest range with Fisher transform crossover.
/// </summary>
public class DlmvFxFishGridStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;

	private decimal? _prevFish;
	private decimal _prevValue;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int Period
	{
		get => _period.Value;
		set => _period.Value = value;
	}

	public DlmvFxFishGridStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_period = Param(nameof(Period), 20)
			.SetGreaterThanZero()
			.SetDisplay("Period", "Lookback period for high/low range", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFish = null;
		_prevValue = 0m;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevFish = null;
		_prevValue = 0m;

		var highest = new Highest { Length = Period };
		var lowest = new Lowest { Length = Period };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(highest, lowest, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, highest);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var range = high - low;
		var midPrice = (candle.HighPrice + candle.LowPrice) / 2m;

		var normalized = range != 0m ? (midPrice - low) / range : 0.5m;
		var value = 0.66m * (normalized - 0.5m) + 0.67m * _prevValue;
		value = Math.Min(Math.Max(value, -0.999m), 0.999m);

		var ratio = (double)((1m + value) / (1m - value));
		var fish = 0.5m * (decimal)Math.Log(ratio);

		_prevValue = value;

		if (_prevFish == null)
		{
			_prevFish = fish;
			return;
		}

		// Fisher crosses zero from below → buy
		if (_prevFish.Value < 0m && fish >= 0m && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Fisher crosses zero from above → sell
		else if (_prevFish.Value > 0m && fish <= 0m && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevFish = fish;
	}
}