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
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.
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.
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.
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;
}
}
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class dlmv_fx_fish_grid_strategy(Strategy):
def __init__(self):
super(dlmv_fx_fish_grid_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._period = self.Param("Period", 14) \
.SetDisplay("Period", "Channel period", "Indicators")
self._prev_high = None
self._prev_low = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def Period(self):
return self._period.Value
def OnReseted(self):
super(dlmv_fx_fish_grid_strategy, self).OnReseted()
self._prev_high = None
self._prev_low = None
def OnStarted2(self, time):
super(dlmv_fx_fish_grid_strategy, self).OnStarted2(time)
self._prev_high = None
self._prev_low = None
highest = Highest()
highest.Length = self.Period
lowest = Lowest()
lowest.Length = self.Period
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(highest, lowest, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, highest)
self.DrawIndicator(area, lowest)
self.DrawOwnTrades(area)
def _on_process(self, candle, high_value, low_value):
if candle.State != CandleStates.Finished:
return
hv = float(high_value)
lv = float(low_value)
close = float(candle.ClosePrice)
if self._prev_high is None or self._prev_low is None:
self._prev_high = hv
self._prev_low = lv
return
if close > self._prev_high and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif close < self._prev_low and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_high = hv
self._prev_low = lv
def CreateClone(self):
return dlmv_fx_fish_grid_strategy()