La Estrategia del Cangrejo Farhad es una StockSharp versión de alto nivel del MetaTrader asesor experto FarhadCrab1.mq4. El EA original es un sistema de especulación rápido diseñado para el período M1 en GBP/JPY, GBP/USD y EUR/USD. Esta conversión recrea la lógica comercial en C# al combinar filtros de promedio móvil intradía con una red de seguridad de tendencias diarias y una gestión de salida automatizada.
La estrategia analiza el período de tiempo actual a través de un EMA de 9 períodos calculado sobre el precio típico y un SMA de 9 períodos calculado sobre la apertura de la vela. Al mismo tiempo, realiza un seguimiento de una media móvil suavizada (SMMA) de 55 períodos construida a partir de velas diarias. Siempre que los filtros a corto plazo muestran suficiente impulso alcista mientras no hay ninguna posición abierta, se activa una operación larga. Por el contrario, cuando el máximo intradiario permanece por debajo del SMA de aperturas, se abre una operación corta. La SMMA diaria actúa como una capa protectora: cruzar el precio desde abajo obliga a todas las operaciones largas a salir, y cruzar desde arriba cierra las posiciones cortas.
La gestión de salida reproduce el comportamiento del EA original con niveles de obtención de beneficios configurables en pips y paradas finales independientes para posiciones largas y cortas. La lógica de seguimiento sigue la implementación de MetaTrader moviendo el stop solo después de que el mercado avanza la distancia configurada. La estrategia cierra posiciones mediante órdenes de mercado en lugar de órdenes stop pendientes, lo que la hace compatible con el flujo de eventos de alto nivel API.
Características clave
Conjunto de indicadores idéntico al EA: EMA de 9 períodos en el precio típico, SMA de 9 períodos en las aperturas y una SMMA diaria de 55 períodos para la dirección de la tendencia.
Manejo de datos de múltiples períodos de tiempo: se suscribe al período de negociación y a las velas diarias simultáneamente, lo que permite a StockSharp calcular los indicadores requeridos sin almacenamiento en búfer manual.
Salidas configurables: distancias de toma de ganancias simétricas (largas/cortas) y topes dinámicos expresados en pips, al igual que las entradas externas originales.
Interruptor de seguridad diario: replica la regla de EA que cierra posiciones largas cuando el SMMA diario se mueve por encima del cierre diario y posiciones cortas cuando se mueve por debajo.
Protección incorporada: llama a StartProtection() una vez al inicio para proteger las posiciones de acuerdo con las mejores prácticas del marco.
Parámetros
Parámetro
Descripción
Predeterminado
OrderVolume
Volumen comercial aplicado a nuevas órdenes de mercado.
0.1
LongTakeProfitPips
Distancia de obtención de beneficios para posiciones largas, medida en pips.
10
ShortTakeProfitPips
Distancia de obtención de beneficios para posiciones cortas, medida en pips.
10
LongTrailingStopPips
Distancia de trailing stop para operaciones largas. El seguimiento se desactiva cuando se establece en cero.
8
ShortTrailingStopPips
Distancia de trailing stop para operaciones cortas. El seguimiento se desactiva cuando se establece en cero.
8
DailyMaPeriod
Longitud de la media móvil diaria suavizada utilizada para las salidas de protección.
55
CandleType
Periodo de tiempo principal que impulsa los cálculos de la estrategia. El valor predeterminado es velas de 1 minuto.
1m
Todos los parámetros se exponen a través de StrategyParam<T> y se marcan como optimizables cuando tiene sentido, para que puedan ajustarse mediante el optimizador StockSharp.
Reglas de trading
Entradas largas: cuando el mínimo de la vela actual se mantiene por encima del EMA de 9 períodos del precio típico y no hay ninguna posición activa, abra una operación larga.
Entradas cortas: cuando el máximo de la vela actual permanece por debajo del período 9 SMA del precio de apertura y no hay ninguna posición activa, abra una operación corta.
Salida protectora diaria (larga): cierre cualquier posición larga si la SMMA diaria se mueve por encima del cierre diario mientras anteriormente estaba por debajo del cierre anterior.
Salida protectora diaria (corta): cierre cualquier posición corta si la SMMA diaria se mueve por debajo del cierre diario mientras anteriormente estaba por encima del cierre anterior.
Take-profit: cierre la posición una vez que se alcance el objetivo de pip configurado.
Parada de seguimiento: después de que una posición gana la distancia de seguimiento, bloquee las ganancias monitoreando la distancia de retroceso y salga cuando el precio retroceda en esa cantidad.
Notas de implementación
El código se basa exclusivamente en llamadas SubscribeCandles().Bind(...) de alto nivel, lo que elimina cualquier buffer de indicador manual y se mantiene dentro de las pautas del proyecto.
Los pips se calculan a partir del PriceStep del instrumento con el ajuste habitual de estilo MetaTrader para cotizaciones de 3 y 5 dígitos. Esto mantiene el comportamiento coherente con los parámetros basados en puntos de EA.
La gestión de stop-loss y take-profit se realiza internamente cerrando posiciones cuando se cumplen las condiciones, en lugar de registrar órdenes límite/stop. Este enfoque coincide con las salidas instantáneas que se encuentran en el script original y al mismo tiempo sigue siendo compatible con la ejecución de órdenes asincrónicas en StockSharp.
La estrategia restablece su estado dentro de OnReseted, lo que garantiza que las ejecuciones de optimización y los lanzamientos repetidos comiencen desde cero.
Consejos de uso
El EA original se diseñó para pares GBP y EUR altamente volátiles en el marco temporal M1. Se pueden esperar resultados similares al aplicar el mismo marco temporal e instrumentos, pero los parámetros están expuestos para adaptarse a diferentes perfiles de volatilidad.
Debido a que el sistema mantiene solo una posición a la vez, es adecuado para realizar pruebas retrospectivas sencillas y ejecución en vivo sin una compleja pirámide de posiciones.
Los trailingstops se vuelven más efectivos en instrumentos con tendencias suaves. En los mercados de rango, considere reducir la distancia de seguimiento o confiar únicamente en salidas de toma de ganancias.
La salida diaria de SMMA sirve como principal control de riesgos. Para configuraciones orientadas al swing, puede aumentar DailyMaPeriod para que el filtro a largo plazo sea menos reactivo.
Fórmulas comprobables: por cada vela M1 cerrada, typical=(high+low+close)/3. EMA(9) se inicia con la media de los primeros nueve typical y sigue EMA=(8*EMA+2*typical)/10; SMA(9) utiliza los Open. Sin posición ni orden pendiente, comprar si low > EMA, de otro modo vender si high < SMA(Open). SMMA(55) diario se inicia con 55 cierres y sigue (54*SMMA+close)/55. Un cruce diario contrario cierra la posición. El take profit y el trailing utilizan el precio real ejecutado, se comprueban al completar velas M1 y los nuevos stops valen solo a partir de velas posteriores. Reducir DailyMaPeriod para probar la mecánica no valida el valor por defecto 55. El backtest no garantiza rentabilidad.
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()