Die Strategie reproduziert den MetaTrader 5-Experten Exp_XFisher_org_v1. Sie handelt Umkehrungen, die an der Fisher-Transformation des Preises erkannt werden, der zusätzlich mit einem konfigurierbaren gleitenden Durchschnitt geglättet wird. Der StockSharp-Port behält die Gegentrend-Natur des ursprünglichen Roboters bei: wenn die Fisher-Kurve nach einem Aufwärtsschwung abwärts dreht, wird eine Long-Position eröffnet, und wenn die Kurve nach einem Abwärtsschwung aufwärts dreht, wird eine Short-Position eröffnet. Bestehende Positionen werden geschlossen, sobald der Indikator in die entgegengesetzte Richtung umkehrt.
Der Hilfsindikator XFisherOrgIndicator in CS/ExpXFisherOrgV1Strategy.cs folgt der MT5-Logik:
Das höchste Hoch und niedrigste Tief über Length abgeschlossene Kerzen nehmen.
Die ausgewählte Preisquelle (siehe Applied Price unten) anhand dieser Extreme in den Bereich 0–1 umrechnen.
Den rekursiven Filter value = (wpr - 0.5) + 0.67 * value[prev] gefolgt von der Fisher-Transformation anwenden
fish = 0.5 * ln((1 + value) / (1 - value)) + 0.5 * fish[prev].
Das Ergebnis mit einem der unterstützten gleitenden Durchschnitte glätten. Der geglättete Fisher-Wert bildet die Hauptlinie; die Signallinie ist einfach der Wert der vorherigen Bar, genau wie in der MQL-Version, wo Puffer #1 eine Einbar-Verschiebung speichert.
Die Konvertierung behält die ursprünglichen Standardwerte (Length = 7, Jurik-Glättung der Länge 5, Phase 15, H4-Kerzen) bei und stellt dieselben Enable/Disable-Schalter für das Öffnen und Schließen von Long/Short-Trades bereit.
Handelsregeln
Long-Einstieg – wenn der Fisher-Wert von SignalBar + 1 Bars vor stieg (Fisher[SignalBar+1] > Fisher[SignalBar+2])
aber der Wert bei SignalBar unter seine verzögerte Kopie kreuzt oder diese berührt (Fisher[SignalBar] <= Fisher[SignalBar+1]).
Short-Einstieg – wenn der Fisher-Wert von SignalBar + 1 Bars vor fiel, aber der Wert bei SignalBar über seine verzögerte Kopie kreuzt.
Positions-Exit – die entgegengesetzte Umkehr schließt eine bestehende Position, bevor ein neuer Trade in Betracht gezogen wird. Ein Long-Exit wird durch dieselbe Bedingung ausgelöst, die einen Short öffnet, und umgekehrt.
Volumen – wird durch OrderVolume gesteuert. Wenn ein Flip von Short zu Long (oder Long zu Short) erforderlich ist, sendet die Strategie eine einzelne Marktorder mit ausreichendem Volumen, um die alte Position zu schließen und die neue in derselben Transaktion zu öffnen.
Alle Berechnungen verwenden ausschließlich abgeschlossene Kerzen. Wenn SignalBar null ist, wird die aktuelle geschlossene Kerze zur Signalauswertung verwendet; positive Werte verschieben das Signal zeitlich zurück, genau wie der MT5-SignalBar-Input.
Parameter
Name
Beschreibung
Standard
OrderVolume
Volumen jeder Marktorder.
1
BuyOpenAllowed / SellOpenAllowed
Öffnen von Long/Short-Trades erlauben.
true
BuyCloseAllowed / SellCloseAllowed
Schließen bestehender Long/Short-Trades erlauben.
true
SignalBar
Verschiebung (in geschlossenen Kerzen) zum Lesen der Fisher-Puffer.
1
Length
Lookback für höchste/niedrigste Preisextrema.
7
SmoothingLength
Periode des Glättungsdurchschnitts.
5
Phase
Jurik-Phase (von anderen Methoden ignoriert).
15
SmoothingMethod
Gleitender Durchschnitt, der auf den Fisher-Output angewendet wird.
Jjma
PriceType
Applied Price, die an den Indikator weitergeleitet wird (Schluss, Eröffnung, Median, etc.).
Close
CandleType
Kerzenserie für die Berechnung (Standard: 4-Stunden-Kerzen).
H4
Glättungsmethoden-Mapping
Der ursprüngliche Indikator stellt eine große Auswahl an Glättungskernen bereit. Der StockSharp-Port ordnet sie zuverlässigen eingebauten Implementierungen zu:
Jjma, Jurx, T3 → JurikMovingAverage (Phasenparameter wird angewendet, wenn die Eigenschaft verfügbar ist).
Sma, Ema, Smma, Lwma → jeweilige StockSharp-Durchschnitte.
Parabolic → durch ExponentialMovingAverage approximiert (nächstes Verhalten unter StockSharp).
Vidya, Ama → KaufmanAdaptiveMovingAverage (adaptives VIDYA-Verhalten wird mit Kaufman AMA modelliert).
Dieses Mapping spiegelt den in anderen Kositsin-Konvertierungen im Repository verwendeten Ansatz und hält die Reaktion der geglätteten Fisher-Linie vergleichbar mit der MT5-Implementierung.
Unterschiede zum MT5-Experten
Geldmanagement – StockSharp-Strategien arbeiten mit expliziten Volumen. Die MM/MarginMode-Inputs von MT5 werden durch einen einzelnen OrderVolume-Parameter ersetzt, damit der Trader die Lotgröße direkt definieren kann.
Ausführungsmodell – Trades werden einmal pro abgeschlossener Kerze über die High-Level-Subscription-API generiert, anstatt bei jedem Tick. Dies vermeidet doppelte Orders und macht den ursprünglichen IsNewBar-Helper überflüssig.
Applied Price-Optionen – alle Preismodi aus SmoothAlgorithms.mqh sind unterstützt, einschließlich TrendFollow- und Demark-Varianten.
Charting – die Strategie zeichnet Kerzen, die geglättete Fisher-Transformation und die ausgeführten Trades im Standardchartbereich.
Dateien
CS/ExpXFisherOrgV1Strategy.cs – Strategieklasse, Indikatorimplementierung und Wertecontainer.
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()