Die Farhad Crab Strategy ist eine StockSharp High-Level-Portierung des MetaTrader Expertenberaters FarhadCrab1.mq4. Das Original EA ist ein schnelles Scalping-System, das für den M1-Zeitrahmen für GBP/JPY, GBP/USD und EUR/USD entwickelt wurde. Bei dieser Konvertierung wird die Handelslogik in C# neu erstellt, indem Filter für den gleitenden Intraday-Durchschnitt mit einem täglichen Trendsicherheitsnetz und automatisiertem Exit-Management kombiniert werden.
Die Strategie analysiert den aktuellen Zeitrahmen anhand eines 9-Perioden-EMA, der auf dem typischen Preis berechnet wird, und eines 9-Perioden-SMA, der auf der Öffnung der Kerze berechnet wird. Gleichzeitig verfolgt es einen geglätteten gleitenden Durchschnitt (SMMA) über 55 Perioden, der aus täglichen Kerzen gebildet wird. Immer wenn die kurzfristigen Filter genügend Aufwärtsdynamik zeigen und keine Position offen ist, wird ein Long-Trade ausgelöst. Wenn umgekehrt das Intraday-Hoch unter SMA der Eröffnungen bleibt, wird ein Short-Trade eröffnet. Der tägliche SMMA fungiert als Schutzschicht: Ein Überschreiten des Preises von unten zwingt alle Long-Trades zum Ausstieg, und ein Überschreiten von oben schließt Short-Positionen.
Das Exit-Management reproduziert das ursprüngliche Verhalten von EA mit konfigurierbaren Take-Profit-Levels in Pips und unabhängigen Trailing Stops für Long- und Short-Positionen. Die Trailing-Logik folgt der MetaTrader-Implementierung, indem sie den Stop erst verschiebt, nachdem der Markt um die konfigurierte Distanz vorgerückt ist. Die Strategie schließt Positionen über Market-Orders und nicht über ausstehende Stop-Orders, wodurch sie mit dem High-Level-Ereignisablauf API kompatibel ist.
Hauptmerkmale
Indikatorsatz identisch mit EA – 9-Perioden-EMA beim typischen Preis, 9-Perioden-SMA bei Eröffnungen und ein täglicher 55-Perioden-SMMA für die Trendrichtung.
Datenverarbeitung in mehreren Zeitrahmen – abonniert gleichzeitig den Handelszeitrahmen und die täglichen Kerzen, sodass StockSharp die erforderlichen Indikatoren ohne manuelle Pufferung berechnen kann.
Konfigurierbare Exits – symmetrische Take-Profit-Abstände (Long/Short) und Trailing-Stops, ausgedrückt in Pips, genau wie die ursprünglichen externen Eingaben.
Täglicher Sicherheitsschalter – repliziert die Regel von EA, die Long-Positionen schließt, wenn der tägliche SMMA über den Tagesschlusskurs steigt, und Short-Positionen, wenn er darunter fällt.
Integrierter Schutz – ruft StartProtection() einmal beim Start auf, um Positionen gemäß den Best Practices des Frameworks zu schützen.
Parameter
Parameter
Beschreibung
Standard
OrderVolume
Auf neue Marktaufträge angewendetes Handelsvolumen.
0.1
LongTakeProfitPips
Take-Profit-Distanz für Long-Positionen, gemessen in Pips.
10
ShortTakeProfitPips
Take-Profit-Distanz für Short-Positionen, gemessen in Pips.
10
LongTrailingStopPips
Trailing-Stop-Distanz für Long-Trades. Das Nachlaufen ist deaktiviert, wenn es auf Null gesetzt ist.
8
ShortTrailingStopPips
Trailing-Stop-Distanz für Short-Trades. Das Nachlaufen ist deaktiviert, wenn es auf Null gesetzt ist.
8
DailyMaPeriod
Länge des täglichen geglätteten gleitenden Durchschnitts, der für Schutzausstiege verwendet wird.
55
CandleType
Primärer Zeitrahmen, der die Strategieberechnungen steuert. Standardmäßig werden 1-Minuten-Kerzen verwendet.
1m
Alle Parameter werden über StrategyParam<T> verfügbar gemacht und dort, wo es sinnvoll ist, als optimierbar markiert, sodass sie über den StockSharp-Optimierer optimiert werden können.
Handelsregeln
Long-Einträge: Wenn das aktuelle Kerzentief über dem 9-Perioden-EMA des typischen Preises bleibt und keine Position aktiv ist, eröffnen Sie einen Long-Trade.
Short-Einträge: Wenn das aktuelle Kerzenhoch unter dem 9-Perioden-SMA des Eröffnungspreises bleibt und keine Position aktiv ist, eröffnen Sie einen Short-Trade.
Täglicher schützender Ausstieg (Long): Schließen Sie jede Long-Position, wenn der tägliche SMMA über den Tagesschlusskurs steigt, während er zuvor unter dem vorherigen Schlusskurs lag.
Täglicher schützender Ausstieg (Short): Schließen Sie jede Short-Position, wenn der tägliche SMMA unter den Tagesschluss fällt, während er zuvor über dem vorherigen Schlusskurs lag.
Take-Profit: Schließen Sie die Position, sobald das konfigurierte Pip-Ziel erreicht ist.
Trailing Stop: Nachdem eine Position die Trailing-Distanz erreicht hat, können Sie Gewinne sichern, indem Sie die Retracement-Distanz überwachen und aussteigen, wenn der Preis um diesen Betrag zurückgeht.
Implementierungshinweise
Der Code basiert ausschließlich auf SubscribeCandles().Bind(...)-Aufrufen auf hoher Ebene, wodurch manuelle Indikatorpuffer entfallen und die Richtlinien des Projekts eingehalten werden.
Pips werden aus dem PriceStep des Instruments mit der üblichen Anpassung im MetaTrader-Stil für 3- und 5-stellige Notierungen berechnet. Dadurch bleibt das Verhalten mit den punktbasierten Parametern von EA konsistent.
Das Stop-Loss- und Take-Profit-Management wird intern durchgeführt, indem Positionen geschlossen werden, wenn die Bedingungen erfüllt sind, und nicht durch die Registrierung von Limit-/Stop-Orders. Dieser Ansatz entspricht den im Originalskript gefundenen sofortigen Exits und bleibt gleichzeitig mit der asynchronen Auftragsausführung in StockSharp kompatibel.
Die Strategie setzt ihren Status innerhalb von OnReseted zurück und stellt so sicher, dass Optimierungsläufe und wiederholte Starts von vorne beginnen.
Nutzungstipps
Der ursprüngliche EA war auf hochvolatile GBP- und EUR-Paare im M1-Zeitrahmen zugeschnitten. Ähnliche Ergebnisse sind zu erwarten, wenn der gleiche Zeitrahmen und die gleichen Instrumente angewendet werden, die Parameter jedoch so ausgelegt sind, dass sie unterschiedlichen Volatilitätsprofilen Rechnung tragen.
Da das System jeweils nur eine Position behält, eignet es sich für einfaches Backtesting und Live-Ausführung ohne komplexe Positionspyramide.
Trailing-Stops werden bei Instrumenten mit glatten Trends effektiver. Erwägen Sie bei schwankenden Märkten eine Reduzierung der Nachlaufdistanz oder die ausschließliche Nutzung von Take-Profit-Exits.
Als primäre Risikokontrolle dient der tägliche SMMA-Exit. Für Swing-orientierte Setups können Sie DailyMaPeriod erhöhen, um den Langzeitfilter weniger reaktiv zu machen.
Prüfbare Formeln: Für jede abgeschlossene M1-Kerze gilt typical=(high+low+close)/3. Der EMA(9) der typischen Preise wird mit dem Mittelwert der ersten neun Werte initialisiert, anschließend EMA=(8*EMA+2*typical)/10. Der SMA(9) basiert auf Open. Ohne Position oder ausstehende Order: Kauf bei low > EMA, sonst Verkauf bei high < SMA(Open). Der Tages-SMMA(55) startet nach 55 abgeschlossenen Tageskerzen und folgt (54*SMMA+close)/55. Ein ungünstiges Kreuzen des Tagesschlusses löst einen Ausstieg aus. Take-Profit und Trailing nutzen den tatsächlichen Ausführungskurs und abgeschlossene M1-Kerzen; neue Trailing-Level gelten erst für spätere Kerzen. Ein verkürzter DailyMaPeriod testet nur die Mechanik und nicht den Default 55. Ein Backtest garantiert keine Rendite.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Farhad Crab uses EMA(9) on typical M1 prices, SMA(9) of M1 opens,
/// and daily SMMA protection with direction-specific take/trailing exits.
/// </summary>
public class FarhadCrabStrategy : Strategy
{
private readonly StrategyParam<int> _dailyMaPeriod;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _longTakeProfitPips;
private readonly StrategyParam<decimal> _shortTakeProfitPips;
private readonly StrategyParam<decimal> _longTrailingStopPips;
private readonly StrategyParam<decimal> _shortTrailingStopPips;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _typicalSeed = new();
private readonly Queue<decimal> _opens = new();
private readonly Queue<decimal> _dailySeed = new();
private decimal? _emaTypical;
private decimal? _dailySmma;
private decimal? _priorSmma;
private decimal? _priorDailyClose;
private decimal? _entryPrice;
private decimal? _trailingLevel;
private decimal _bestPrice;
private Order _pendingOrder;
private Order _entryOrder;
public int DailyMaPeriod { get => _dailyMaPeriod.Value; set => _dailyMaPeriod.Value = value; }
public decimal OrderVolume { get => _orderVolume.Value; set => _orderVolume.Value = value; }
public decimal LongTakeProfitPips { get => _longTakeProfitPips.Value; set => _longTakeProfitPips.Value = value; }
public decimal ShortTakeProfitPips { get => _shortTakeProfitPips.Value; set => _shortTakeProfitPips.Value = value; }
public decimal LongTrailingStopPips { get => _longTrailingStopPips.Value; set => _longTrailingStopPips.Value = value; }
public decimal ShortTrailingStopPips { get => _shortTrailingStopPips.Value; set => _shortTrailingStopPips.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public FarhadCrabStrategy()
{
_dailyMaPeriod = Param(nameof(DailyMaPeriod), 55).SetGreaterThanZero();
_orderVolume = Param(nameof(OrderVolume), 0.1m).SetGreaterThanZero();
_longTakeProfitPips = Param(nameof(LongTakeProfitPips), 10m).SetNotNegative();
_shortTakeProfitPips = Param(nameof(ShortTakeProfitPips), 10m).SetNotNegative();
_longTrailingStopPips = Param(nameof(LongTrailingStopPips), 8m).SetNotNegative();
_shortTrailingStopPips = Param(nameof(ShortTrailingStopPips), 8m).SetNotNegative();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame());
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, TimeSpan.FromDays(1).TimeFrame())];
protected override void OnReseted()
{
base.OnReseted();
ClearState();
}
private void ClearState()
{
_typicalSeed.Clear();
_opens.Clear();
_dailySeed.Clear();
_emaTypical = null;
_dailySmma = null;
_priorSmma = null;
_priorDailyClose = null;
_entryPrice = null;
_trailingLevel = null;
_bestPrice = 0m;
_pendingOrder = null;
_entryOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearState();
if ((Security?.PriceStep ?? 0m) <= 0m)
throw new InvalidOperationException("Farhad Crab requires a positive instrument PriceStep.");
SubscribeCandles(TimeSpan.FromDays(1).TimeFrame()).Bind(ProcessDailyCandle).Start();
SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
}
private decimal PipSize()
{
var step = Security?.PriceStep ?? 0m;
return Security?.Decimals is 3 or 5 ? step * 10m : step;
}
private bool Pending => _pendingOrder is not null &&
_pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);
private void ProcessDailyCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var priorSmma = _dailySmma;
var priorClose = _priorDailyClose;
if (_dailySmma is null)
{
_dailySeed.Enqueue(candle.ClosePrice);
if (_dailySeed.Count == DailyMaPeriod)
_dailySmma = _dailySeed.Average();
}
else
_dailySmma = (_dailySmma.Value * (DailyMaPeriod - 1) + candle.ClosePrice) / DailyMaPeriod;
_priorSmma = priorSmma;
_priorDailyClose = candle.ClosePrice;
if (Pending || Position == 0m || priorSmma is null || priorClose is null || _dailySmma is null)
return;
if (Position > 0m && priorSmma < priorClose && _dailySmma > candle.ClosePrice ||
Position < 0m && priorSmma > priorClose && _dailySmma < candle.ClosePrice)
Exit("Farhad Crab daily safety exit");
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var typical = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m;
if (_emaTypical is null)
{
_typicalSeed.Enqueue(typical);
if (_typicalSeed.Count == 9)
_emaTypical = _typicalSeed.Average();
}
else
_emaTypical = (_emaTypical.Value * 8m + typical * 2m) / 10m;
_opens.Enqueue(candle.OpenPrice);
while (_opens.Count > 9)
_opens.Dequeue();
if (Pending)
return;
if (Position != 0m)
{
HandleRisk(candle);
return;
}
if (_emaTypical is null || _opens.Count < 9)
return;
if (candle.LowPrice > _emaTypical.Value)
Enter(Sides.Buy);
else if (candle.HighPrice < _opens.Average())
Enter(Sides.Sell);
}
private void Enter(Sides side)
{
var volume = OrderVolume;
if (Security?.VolumeStep is decimal step && step > 0m)
volume = decimal.Floor(volume / step) * step;
if (Security?.MinVolume is decimal min && min > 0m)
volume = Math.Max(min, volume);
if (Security?.MaxVolume is decimal max && max > 0m)
volume = Math.Min(max, volume);
if (volume > 0m)
Submit(side, volume, "Farhad Crab entry");
}
private void HandleRisk(ICandleMessage candle)
{
if (_entryPrice is not decimal entry)
return;
var positive = Position > 0m;
var pip = PipSize();
var target = (positive ? LongTakeProfitPips : ShortTakeProfitPips) * pip;
if (target > 0m && (positive ? candle.HighPrice >= entry + target :
candle.LowPrice <= entry - target))
{
Exit("Farhad Crab take profit");
return;
}
if (_trailingLevel is decimal level && (positive ? candle.LowPrice <= level :
candle.HighPrice >= level))
{
Exit("Farhad Crab trailing stop");
return;
}
var distance = (positive ? LongTrailingStopPips : ShortTrailingStopPips) * pip;
if (distance <= 0m)
return;
_bestPrice = positive ? Math.Max(_bestPrice, candle.HighPrice) :
Math.Min(_bestPrice, candle.LowPrice);
var move = positive ? _bestPrice - entry : entry - _bestPrice;
if (move < distance)
return;
var next = positive ? _bestPrice - distance : _bestPrice + distance;
if (_trailingLevel is null || (positive ? next > _trailingLevel.Value : next < _trailingLevel.Value))
_trailingLevel = next;
}
private void Exit(string reason)
{
if (!Pending && Position != 0m)
Submit(Position > 0m ? Sides.Sell : Sides.Buy, Math.Abs(Position), reason);
}
private void Submit(Sides side, decimal volume, string comment)
{
_pendingOrder = new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = side,
Volume = volume,
Comment = comment,
};
if (comment == "Farhad Crab entry")
_entryOrder = _pendingOrder;
RegisterOrder(_pendingOrder);
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade?.Order == _entryOrder && trade.Trade is not null)
{
_entryPrice = trade.Trade.Price;
_bestPrice = _entryPrice.Value;
_trailingLevel = null;
}
else if (Position == 0m)
{
_entryPrice = null;
_trailingLevel = null;
_bestPrice = 0m;
}
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Sides, OrderStates, OrderTypes
from StockSharp.BusinessEntities import Order
from StockSharp.Algo.Strategies import Strategy
class farhad_crab_strategy(Strategy):
"""M1 EMA-typical and SMA-open scalping with daily SMMA safety exits."""
def __init__(self):
super(farhad_crab_strategy, self).__init__()
self._daily_ma_period = self.Param("DailyMaPeriod", 55).SetGreaterThanZero()
self._order_volume = self.Param("OrderVolume", 0.1).SetGreaterThanZero()
self._long_take_profit_pips = self.Param("LongTakeProfitPips", 10.0).SetNotNegative()
self._short_take_profit_pips = self.Param("ShortTakeProfitPips", 10.0).SetNotNegative()
self._long_trailing_stop_pips = self.Param("LongTrailingStopPips", 8.0).SetNotNegative()
self._short_trailing_stop_pips = self.Param("ShortTrailingStopPips", 8.0).SetNotNegative()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1)))
self._clear_state()
@property
def candle_type(self):
return self._candle_type.Value
def _clear_state(self):
self._typical_seed = []
self._opens = []
self._daily_seed = []
self._ema_typical = None
self._daily_smma = None
self._prior_daily_close = None
self._entry_price = None
self._trailing_level = None
self._best_price = 0.0
self._pending_order = None
self._entry_order = None
def OnReseted(self):
super(farhad_crab_strategy, self).OnReseted()
self._clear_state()
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type),
(self.Security, DataType.TimeFrame(TimeSpan.FromDays(1)))]
def OnStarted2(self, time):
super(farhad_crab_strategy, self).OnStarted2(time)
self._clear_state()
if float(self.Security.PriceStep or 0) <= 0:
raise ValueError("Farhad Crab requires a positive instrument PriceStep.")
self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromDays(1))).Bind(self._process_daily).Start()
self.SubscribeCandles(self.candle_type).Bind(self._process_candle).Start()
def _pip(self):
step = float(self.Security.PriceStep)
return step * (10 if self.Security.Decimals in (3, 5) else 1)
def _pending(self):
return (self._pending_order is not None and
self._pending_order.State not in (OrderStates.Done, OrderStates.Failed))
def _process_daily(self, candle):
if candle.State != CandleStates.Finished:
return
prev_smma = self._daily_smma
prev_close = self._prior_daily_close
close = float(candle.ClosePrice)
if self._daily_smma is None:
self._daily_seed.append(close)
if len(self._daily_seed) == self._daily_ma_period.Value:
self._daily_smma = sum(self._daily_seed) / len(self._daily_seed)
else:
n = self._daily_ma_period.Value
self._daily_smma = (self._daily_smma * (n - 1) + close) / n
self._prior_daily_close = close
if self._pending() or self.Position == 0 or prev_smma is None or prev_close is None:
return
if (self.Position > 0 and prev_smma < prev_close and self._daily_smma > close or
self.Position < 0 and prev_smma > prev_close and self._daily_smma < close):
self._exit("Farhad Crab daily safety exit")
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
typical = (float(candle.HighPrice) + float(candle.LowPrice) +
float(candle.ClosePrice)) / 3.0
if self._ema_typical is None:
self._typical_seed.append(typical)
if len(self._typical_seed) == 9:
self._ema_typical = sum(self._typical_seed) / 9
else:
self._ema_typical = (self._ema_typical * 8 + typical * 2) / 10.0
self._opens.append(float(candle.OpenPrice))
if len(self._opens) > 9:
self._opens.pop(0)
if self._pending():
return
if self.Position != 0:
self._handle_risk(candle)
return
if self._ema_typical is None or len(self._opens) < 9:
return
if float(candle.LowPrice) > self._ema_typical:
self._enter(Sides.Buy)
elif float(candle.HighPrice) < sum(self._opens) / 9.0:
self._enter(Sides.Sell)
def _enter(self, side):
volume = float(self._order_volume.Value)
step = float(self.Security.VolumeStep or 0)
if step > 0:
volume = math.floor(volume / step) * step
minimum = float(self.Security.MinVolume or 0)
maximum = float(self.Security.MaxVolume or 0)
if minimum > 0:
volume = max(volume, minimum)
if maximum > 0:
volume = min(volume, maximum)
if volume > 0:
self._submit(side, volume, "Farhad Crab entry")
def _handle_risk(self, candle):
if self._entry_price is None:
return
entry = self._entry_price
long_position = self.Position > 0
pip = self._pip()
target = float(self._long_take_profit_pips.Value if long_position
else self._short_take_profit_pips.Value) * pip
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if target > 0 and (high >= entry + target if long_position
else low <= entry - target):
self._exit("Farhad Crab take profit")
return
level = self._trailing_level
if level is not None and (low <= level if long_position else high >= level):
self._exit("Farhad Crab trailing stop")
return
distance = float(self._long_trailing_stop_pips.Value if long_position
else self._short_trailing_stop_pips.Value) * pip
if distance <= 0:
return
self._best_price = (max(self._best_price, high) if long_position
else min(self._best_price, low))
move = self._best_price - entry if long_position else entry - self._best_price
if move < distance:
return
next_stop = self._best_price - distance if long_position else self._best_price + distance
if level is None or (next_stop > level if long_position else next_stop < level):
self._trailing_level = next_stop
def _exit(self, reason):
if not self._pending() and self.Position != 0:
side = Sides.Sell if self.Position > 0 else Sides.Buy
self._submit(side, abs(float(self.Position)), reason)
def _submit(self, side, volume, comment):
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = Decimal(volume)
order.Comment = comment
self._pending_order = order
if comment == "Farhad Crab entry":
self._entry_order = order
self.RegisterOrder(order)
def OnOwnTradeReceived(self, trade):
super(farhad_crab_strategy, self).OnOwnTradeReceived(trade)
if trade is not None and trade.Order == self._entry_order and trade.Trade is not None:
self._entry_price = float(trade.Trade.Price)
self._best_price = self._entry_price
self._trailing_level = None
elif self.Position == 0:
self._entry_price = None
self._best_price = 0.0
self._trailing_level = None
def CreateClone(self):
return farhad_crab_strategy()