Ver no GitHub

Estratégia do Caranguejo Farhad

Visão geral

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Take-profit: feche a posição assim que a meta de pip configurada for atingida.
  6. 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;
		}
	}
}