The Farhad Crab Strategy is a StockSharp high-level port of the MetaTrader expert advisor FarhadCrab1.mq4. The original EA is a fast scalping system designed for the M1 timeframe on GBP/JPY, GBP/USD, and EUR/USD. This conversion recreates the trading logic in C# by combining intraday moving-average filters with a daily trend safety net and automated exit management.
The strategy analyses the current timeframe through a 9-period EMA calculated on the typical price and a 9-period SMA calculated on the candle open. At the same time it keeps track of a 55-period smoothed moving average (SMMA) built from daily candles. Whenever the short-term filters show sufficient momentum to the upside while no position is open, a long trade is triggered. Conversely, when the intraday high remains under the SMA of opens, a short trade is opened. The daily SMMA acts as a protective overlay: crossing the price from below forces all long trades to exit, and crossing from above closes short positions.
Exit management reproduces the original EA's behaviour with configurable take-profit levels in pips and independent trailing stops for long and short positions. The trailing logic follows the MetaTrader implementation by moving the stop only after the market advances by the configured distance. The strategy closes positions via market orders rather than pending stop orders, making it compatible with the high-level API event flow.
Key Features
Indicator set identical to the EA – 9-period EMA on typical price, 9-period SMA on opens, and a daily 55-period SMMA for trend direction.
Multi-timeframe data handling – subscribes to the trading timeframe and to daily candles simultaneously, letting StockSharp compute the required indicators without manual buffering.
Configurable exits – symmetric take-profit distances (long/short) and trailing stops expressed in pips, just like the original external inputs.
Daily safety switch – replicates the EA's rule that closes longs when the daily SMMA moves above the daily close and shorts when it moves below.
Built-in protection – calls StartProtection() once at startup to guard positions according to framework best practices.
Parameters
Parameter
Description
Default
OrderVolume
Trade volume applied to new market orders.
0.1
LongTakeProfitPips
Take-profit distance for long positions, measured in pips.
10
ShortTakeProfitPips
Take-profit distance for short positions, measured in pips.
10
LongTrailingStopPips
Trailing stop distance for long trades. Trailing is disabled when set to zero.
8
ShortTrailingStopPips
Trailing stop distance for short trades. Trailing is disabled when set to zero.
8
DailyMaPeriod
Length of the daily smoothed moving average used for protective exits.
55
CandleType
Primary timeframe that drives the strategy calculations. Defaults to 1 minute candles.
1m
All parameters are exposed through StrategyParam<T> and marked as optimisable where it makes sense, so they can be tuned via the StockSharp optimiser.
Trading Rules
Long entries: When the current candle low stays above the 9-period EMA of the typical price and no position is active, open a long trade.
Short entries: When the current candle high remains below the 9-period SMA of the open price and no position is active, open a short trade.
Daily protective exit (long): Close any long position if the daily SMMA moves above the daily close while it was previously below the previous close.
Daily protective exit (short): Close any short position if the daily SMMA moves below the daily close while it was previously above the previous close.
Take-profit: Close the position once the configured pip target is reached.
Trailing stop: After a position gains the trailing distance, lock in profits by monitoring the retracement distance and exit when the price moves back by that amount.
Implementation Notes
The code relies exclusively on high-level SubscribeCandles().Bind(...) calls, eliminating any manual indicator buffers and staying within the project's guidelines.
Pips are calculated from the instrument's PriceStep with the usual MetaTrader-style adjustment for 3- and 5-digit quotes. This keeps the behaviour consistent with the EA's point-based parameters.
Stop-loss and take-profit management is performed internally by closing positions when conditions are met, rather than registering limit/stop orders. This approach matches the instantaneous exits found in the original script while remaining compatible with asynchronous order execution in StockSharp.
The strategy resets its state inside OnReseted, ensuring that optimisation runs and repeated launches start from a clean slate.
Usage Tips
The original EA was tailored for highly volatile GBP and EUR pairs on the M1 timeframe. Results may differ significantly even when the timeframe and instruments are the same; parameters are provided to explore different volatility profiles.
Because the system keeps only one position at a time, it is suitable for straightforward backtesting and live execution without complex position pyramiding.
Trailing stops become more effective on instruments with smooth trends. On ranging markets consider reducing the trailing distance or relying solely on take-profit exits.
The daily SMMA exit serves as the primary risk control. For swing-oriented setups you can increase DailyMaPeriod to make the long-term filter less reactive.
Testable indicator and exit rules: For each finished M1 candle the typical price is (high+low+close)/3. The 9-period EMA of typical prices is seeded with their first nine-value SMA and thereafter updated by EMA=(8*EMA+2*typical)/10. The separate 9-period SMA uses candle opens. With no position or pending order, buy if low > EMA, otherwise sell if high < SMA(Open). The daily SMMA(55) is seeded from 55 completed daily closes and updated as (54*SMMA+close)/55; its crossing of the daily close closes the adverse position. Take-profit and trailing-stop exits use finished M1 candle extremes and the actual entry fill price, not the signal close. Trailing levels become active only after the position moves by the configured trailing distance and are checked against subsequent bars, without inferring the unknown intrabar path. A reduced DailyMaPeriod is a separate mechanics test, not evidence that the 55-day default was covered. Backtests do not demonstrate future returns.
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()