A Farhad Crab Strategy é uma versão StockSharp de alto nível do MetaTrader consultor especialista FarhadCrab1.mq4. O EA original é um sistema de scalping rápido projetado para o período M1 em GBP/JPY, GBP/USD e EUR/USD. Essa conversão recria a lógica de negociação em C#, combinando filtros de média móvel intradiários com uma rede de segurança de tendência diária e gerenciamento de saída automatizado.
A estratégia analisa o período atual através de um EMA de 9 períodos calculado sobre o preço típico e um SMA de 9 períodos calculado sobre a abertura da vela. Ao mesmo tempo, ele acompanha uma média móvel suavizada de 55 períodos (SMMA) construída a partir de velas diárias. Sempre que os filtros de curto prazo mostram impulso suficiente para cima enquanto nenhuma posição está aberta, uma negociação longa é acionada. Por outro lado, quando a máxima intradiária permanece abaixo de SMA de aberturas, uma negociação curta é aberta. O SMMA diário atua como uma sobreposição protetora: cruzar o preço por baixo força a saída de todas as negociações longas e cruzar por cima fecha as posições curtas.
O gerenciamento de saída reproduz o comportamento original do EA com níveis configuráveis de take-profit em pips e trailing stops independentes para posições longas e curtas. A lógica de trailing segue a implementação MetaTrader movendo o stop somente após o mercado avançar pela distância configurada. A estratégia fecha posições por meio de ordens de mercado em vez de ordens de stop pendentes, tornando-a compatível com o fluxo de eventos de alto nível API.
Principais recursos
Conjunto de indicadores idêntico ao EA – EMA de 9 períodos no preço típico, SMA de 9 períodos em aberturas e um SMMA diário de 55 períodos para direção da tendência.
Tratamento de dados de vários períodos de tempo – assina o período de negociação e as velas diárias simultaneamente, permitindo que StockSharp calcule os indicadores necessários sem buffer manual.
Saídas configuráveis – distâncias de take-profit simétricas (longas/curtas) e trailing stops expressos em pips, assim como as entradas externas originais.
Chave de segurança diária – replica a regra do EA que fecha posições compradas quando o SMMA diário se move acima do fechamento diário e vendas quando se move abaixo.
Proteção integrada – chama StartProtection() uma vez na inicialização para proteger posições de acordo com as práticas recomendadas da estrutura.
Parâmetros
Parâmetro
Descrição
Padrão
OrderVolume
Volume de negociação aplicado a novas ordens de mercado.
0.1
LongTakeProfitPips
Distância de take-profit para posições longas, medida em pips.
10
ShortTakeProfitPips
Distância de take-profit para posições curtas, medida em pips.
10
LongTrailingStopPips
Distância de parada final para negociações longas. O rastreamento é desativado quando definido como zero.
8
ShortTrailingStopPips
Distância de parada móvel para negociações curtas. O rastreamento é desativado quando definido como zero.
8
DailyMaPeriod
Comprimento da média móvel suavizada diária usada para saídas de proteção.
55
CandleType
Prazo principal que orienta os cálculos da estratégia. O padrão é velas de 1 minuto.
1m
Todos os parâmetros são expostos por meio de StrategyParam<T> e marcados como otimizáveis onde faz sentido, para que possam ser ajustados por meio do otimizador StockSharp.
Regras de negociação
Entradas longas: Quando o mínimo da vela atual permanecer acima do período de 9 EMA do preço típico e nenhuma posição estiver ativa, abra uma negociação longa.
Entradas curtas: Quando a máxima atual da vela permanecer abaixo dos 9 períodos SMA do preço de abertura e nenhuma posição estiver ativa, abra uma negociação curta.
Saída de proteção diária (longa): feche qualquer posição longa se o SMMA diário se mover acima do fechamento diário enquanto anteriormente estava abaixo do fechamento anterior.
Saída de proteção diária (curta): Feche qualquer posição curta se o SMMA diário se mover abaixo do fechamento diário enquanto anteriormente estava acima do fechamento anterior.
Take-profit: feche a posição assim que a meta de pip configurada for atingida.
Trailing stop: Depois que uma posição ganha a distância final, garanta os lucros monitorando a distância de retração e saia quando o preço recuar nesse valor.
Notas de implementação
O código depende exclusivamente de chamadas SubscribeCandles().Bind(...) de alto nível, eliminando quaisquer buffers de indicadores manuais e permanecendo dentro das diretrizes do projeto.
Os pips são calculados a partir do PriceStep do instrumento com o ajuste usual no estilo MetaTrader para cotações de 3 e 5 dígitos. Isso mantém o comportamento consistente com os parâmetros baseados em pontos do EA.
A gestão de stop-loss e take-profit é realizada internamente, fechando posições quando as condições são atendidas, em vez de registrar ordens de limite/stop. Esta abordagem corresponde às saídas instantâneas encontradas no script original, permanecendo compatível com a execução assíncrona de pedidos em StockSharp.
A estratégia redefine seu estado dentro de OnReseted, garantindo que as execuções de otimização e lançamentos repetidos comecem do zero.
Dicas de uso
O EA original foi adaptado para pares GBP e EUR altamente voláteis no período M1. Resultados semelhantes podem ser esperados quando se aplicam os mesmos prazos e instrumentos, mas os parâmetros estão expostos para acomodar diferentes perfis de volatilidade.
Como o sistema mantém apenas uma posição por vez, ele é adequado para backtesting direto e execução ao vivo, sem pirâmides complexas de posições.
Os trailing stops tornam-se mais eficazes em instrumentos com tendências suaves. Em mercados variados, considere reduzir a distância de fuga ou confiar apenas em saídas com fins lucrativos.
A saída diária do SMMA serve como controle primário de risco. Para configurações orientadas para swing, você pode aumentar DailyMaPeriod para tornar o filtro de longo prazo menos reativo.
Fórmulas verificáveis: em cada candle M1 concluído, typical=(high+low+close)/3. A EMA(9) começa com a média dos primeiros nove typical e depois EMA=(8*EMA+2*typical)/10; a SMA(9) usa os valores Open. Sem posição ou ordem pendente, compra quando low > EMA, caso contrário vende quando high < SMA(Open). A SMMA(55) diária é inicializada com 55 fechamentos e segue (54*SMMA+close)/55. O cruzamento adverso diário encerra a posição. Take profit e trailing usam o preço real de execução, com verificação em candles M1 concluídos; novos níveis de trailing valem apenas para candles posteriores. Reduzir DailyMaPeriod verifica a mecânica, não o default 55. Backtests não garantem retorno.
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()