La estrategia reproduce el experto de MetaTrader 5 Exp_XFisher_org_v1. Opera reversiones detectadas en la transformada de Fisher del precio que además se suaviza con una media móvil configurable. El puerto de StockSharp conserva la naturaleza contratendencia del robot original: cuando la curva de Fisher gira hacia abajo después de una subida se abre una posición larga, y cuando la curva gira hacia arriba después de una bajada se abre una posición corta. Las posiciones existentes se cierran una vez que el indicador revierte en la dirección opuesta.
El indicador auxiliar XFisherOrgIndicator implementado en CS/ExpXFisherOrgV1Strategy.cs sigue la lógica de MT5:
Tomar el máximo más alto y el mínimo más bajo durante Length velas completadas.
Convertir la fuente de precio seleccionada (ver Applied Price a continuación) al rango 0–1 usando esos extremos.
Aplicar el filtro recursivo value = (wpr - 0.5) + 0.67 * value[prev] seguido de la transformada de Fisher
fish = 0.5 * ln((1 + value) / (1 - value)) + 0.5 * fish[prev].
Suavizar el resultado con una de las medias móviles compatibles. El valor de Fisher suavizado forma la línea principal; la línea de señal es simplemente el valor de la barra anterior, exactamente como en la versión MQL donde el búfer #1 almacena un desplazamiento de una barra.
La conversión mantiene los valores predeterminados originales (Length = 7, suavizado Jurik de longitud 5, fase 15, velas H4) y expone los mismos interruptores de habilitación/deshabilitación para abrir y cerrar operaciones largas/cortas.
Reglas de trading
Entrada larga – cuando el valor de Fisher de SignalBar + 1 barras atrás estaba subiendo (Fisher[SignalBar+1] > Fisher[SignalBar+2])
pero el valor en SignalBar cruza por debajo o toca su copia retrasada (Fisher[SignalBar] <= Fisher[SignalBar+1]).
Entrada corta – cuando el valor de Fisher de SignalBar + 1 barras atrás estaba bajando pero el valor en SignalBar cruza por encima
de su copia retrasada.
Salida de posición – la reversión opuesta cierra una posición existente antes de considerar una nueva operación. Una salida larga se activa
por la misma condición que abre un corto, y viceversa.
Volumen – controlado por OrderVolume. Cuando se requiere un giro de corto a largo (o de largo a corto) la estrategia envía
una única orden de mercado con suficiente volumen para cerrar la posición anterior y abrir la nueva en la misma transacción.
Todos los cálculos usan únicamente velas completadas. Si SignalBar es cero se usa la vela cerrada actual para la evaluación de señales;
los valores positivos desplazan la señal hacia atrás en el tiempo exactamente como el input SignalBar de MT5.
Desplazamiento (en velas cerradas) usado para leer los búferes de Fisher.
1
Length
Lookback para los extremos de precio más altos/bajos.
7
SmoothingLength
Período de la media de suavizado.
5
Phase
Fase Jurik (ignorada por otros métodos).
15
SmoothingMethod
Media móvil aplicada a la salida de Fisher.
Jjma
PriceType
Applied price enviada al indicador (cierre, apertura, mediana, etc.).
Close
CandleType
Serie de velas usada para el cálculo (predeterminado: velas de 4 horas).
H4
Mapeo del método de suavizado
El indicador original expone un gran conjunto de kernels de suavizado. El puerto de StockSharp los mapea a implementaciones incorporadas confiables:
Jjma, Jurx, T3 → JurikMovingAverage (parámetro de fase aplicado cuando la propiedad está disponible).
Sma, Ema, Smma, Lwma → respectivas medias móviles de StockSharp.
Parabolic → aproximado por ExponentialMovingAverage (comportamiento más cercano en StockSharp).
Vidya, Ama → KaufmanAdaptiveMovingAverage (el comportamiento adaptativo VIDYA se modela con Kaufman AMA).
Este mapeo refleja el enfoque usado en otras conversiones de Kositsin en el repositorio y mantiene la respuesta de la línea de Fisher suavizada comparable a la implementación de MT5.
Diferencias con el experto de MT5
Gestión del dinero – las estrategias de StockSharp operan en volúmenes explícitos. Los inputs MM/MarginMode de MT5 se reemplazan con un único parámetro OrderVolume para que el trader pueda definir el tamaño del lote directamente.
Modelo de ejecución – las operaciones se generan una vez por vela completada a través de la API de suscripción de alto nivel en lugar de en cada tick. Esto evita órdenes duplicadas y elimina la necesidad del helper IsNewBar original.
Opciones de applied price – todos los modos de precio de SmoothAlgorithms.mqh son compatibles, incluidas las variantes TrendFollow y Demark.
Charting – la estrategia dibuja velas, la transformada de Fisher suavizada y las operaciones ejecutadas en el área de gráfico predeterminada.
Archivos
CS/ExpXFisherOrgV1Strategy.cs – clase de estrategia, implementación del indicador y contenedor de valores.
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;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_x_fisher_org_v1_strategy(Strategy):
"""
Exp XFisher org v1: EMA crossover with SL/TP and cooldown.
Buys when fast EMA crosses above slow EMA, sells on reverse.
"""
def __init__(self):
super(exp_x_fisher_org_v1_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 7) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Trading timeframe", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(exp_x_fisher_org_v1_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(exp_x_fisher_org_v1_strategy, self).OnStarted2(time)
fast = ExponentialMovingAverage()
fast.Length = self._fast_period.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_fast = float(fast_val)
self._prev_slow = float(slow_val)
return
fast_val = float(fast_val)
slow_val = float(slow_val)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 1.0
sl_pts = self._stop_loss_points.Value
tp_pts = self._take_profit_points.Value
if self.Position > 0 and self._entry_price > 0:
if sl_pts > 0 and close <= self._entry_price - sl_pts * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if tp_pts > 0 and close >= self._entry_price + tp_pts * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if sl_pts > 0 and close >= self._entry_price + sl_pts * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if tp_pts > 0 and close <= self._entry_price - tp_pts * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return exp_x_fisher_org_v1_strategy()