La Estrategia MO Bidir Hedge es una StockSharp versión del MetaTrader 4 asesores expertos mo_bidir_v0_1. El robot original fue diseñado para el gráfico de cinco minutos y siempre mantuvo una exposición al mercado cubierta: cada nueva barra abría una posición larga y corta con distancias predefinidas de stop-loss y take-profit. La versión StockSharp reproduce este comportamiento utilizando velas terminadas, asistentes de órdenes de alto nivel y parámetros de riesgo explícitos medidos en puntos de instrumentos.
Lógica de trading
Suscríbase al tipo de vela configurado (período de tiempo de cinco minutos de forma predeterminada) y procese solo velas terminadas.
Tan pronto como se cierre una vela, inspeccione las patas internas del seto. Si algún tramo permanece abierto, la estrategia espera a que se activen las órdenes de protección y no abre posiciones adicionales.
Cuando no haya tramos activos, envíe una orden de compra de mercado y una orden de venta de mercado del mismo tamaño. Cada orden ejecutada se convierte en un tramo de cobertura independiente rastreado por la estrategia.
Después de completar cada entrada, los umbrales de stop-loss y take-profit se calculan multiplicando las distancias de puntos configuradas por el paso de precio del instrumento (o el incremento mínimo de precio cuando el paso no está disponible).
En cada vela finalizada posterior, la estrategia verifica los máximos y mínimos de las velas:
Los tramos largos se cierran mediante una venta de mercado cuando el mínimo supera el nivel de parada; si no se detiene, un máximo que alcanza el objetivo cierra el tramo para obtener ganancias.
Los tramos cortos se cierran mediante una compra de mercado cuando el máximo toca el stop; de lo contrario, un mínimo que alcance el objetivo genera ganancias.
Cuando ambos umbrales caen dentro de la misma vela, se prioriza el stop-loss porque su toque habría cerrado la posición primero en la implementación de MetaTrader.
Una vez que todos los tramos están cerrados por sus niveles de protección, la estrategia prepara inmediatamente el siguiente par cubierto en el siguiente cierre de vela.
Este flujo de trabajo mantiene la paridad con la lógica MT4 y se basa exclusivamente en las API StockSharp de alto nivel (BuyMarket/SellMarket) y el procesamiento basado en velas exigido por las pautas de conversión.
Parámetros
Parámetro
Descripción
TradeVolume
Tamaño del pedido aplicado a ambos lados de la cobertura. Debe ser positivo.
StopLossPoints
Distancia desde el precio de entrada hasta el stop de protección medida en puntos del instrumento. Utilice 0 para desactivar la parada.
TakeProfitPoints
Distancia objetivo desde el precio de entrada en puntos del instrumento. Utilice 0 para desactivar el objetivo de ganancias.
CandleType
Plazo utilizado para detectar nuevas barras. El valor predeterminado es un período de tiempo de cinco minutos.
Todas las distancias basadas en puntos se convierten a precios absolutos multiplicando el valor configurado por el instrumento PriceStep. Si el paso no está definido, se utiliza el incremento de precio mínimo; cuando ninguno de los valores está disponible, los niveles de protección permanecen inactivos.
Gestión del riesgo
Ambos lados de la cobertura utilizan el mismo volumen fijo y dependen de órdenes de protección simétricas.
Las distancias de stop-loss y take-profit reflejan los valores predeterminados de MetaTrader (80 y 750 puntos respectivamente), preservando la relación "8 pips frente a 75 pips" en un símbolo de divisas de 5 dígitos.
Cada tramo se cierra con una orden de mercado, liberando instantáneamente el margen y permitiendo que el tramo restante continúe sin gestión hasta que se alcance su propio nivel de protección.
Notas de implementación
La estrategia procesa estrictamente velas terminadas para cumplir con las reglas de conversión de todo el proyecto. Los toques de parada o objetivo dentro de la barra se infieren a partir de los extremos de las velas, por lo que las pruebas retrospectivas sin datos de ticks asumirán que la parada se activó antes del objetivo cuando ambos precios aparecieron dentro de la misma barra.
El libro de cobertura interno realiza un seguimiento de las coberturas independientemente de la posición neta de la cartera. Esto refleja el comportamiento de MetaTrader donde las posiciones largas y cortas coexisten simultáneamente.
No se introduce ninguna lógica de seguimiento automatizada, filtros de sesión ni indicadores adicionales: la versión StockSharp sigue siendo intencionalmente tan minimalista como el asesor experto original.
Consejos de uso
Ajuste TradeVolume para que coincida con los tamaños de los contratos de los corredores y asegúrese de que el instrumento admita cobertura de compra/venta simultánea si el entorno lo requiere.
Si necesita valores basados en pips (por ejemplo, 8 pips), multiplique por la cantidad de puntos que representan un pip para el símbolo actual antes de asignar el parámetro.
Combine la estrategia con StockSharp módulos de riesgo o StartProtection si se requieren salvaguardias adicionales a nivel de cartera.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Mo Bidir: EMA crossover with ATR stops.
/// </summary>
public class MoBidirStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public MoBidirStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 25)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow) { _entryPrice = close; SellMarket(); }
}
_prevFast = fastVal; _prevSlow = slowVal;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class mo_bidir_strategy(Strategy):
def __init__(self):
super(mo_bidir_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 10) \
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 25) \
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(mo_bidir_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fv < sv and self._prev_fast >= self._prev_slow) or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fv > sv and self._prev_fast <= self._prev_slow) or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(mo_bidir_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return mo_bidir_strategy()