Open Close es un port del asesor experto MetaTrader 5 Open Close.mq5 (ticket 23090). La estrategia observa la relación entre las aperturas y cierres de las dos velas finalizadas más recientes. Opera una sola posición a la vez: cuando la vela más nueva se revierte en relación con la anterior, la estrategia entra; y cuando ambas velas apuntan en la misma dirección, sale. La versión en C# reproduce el dimensionamiento adaptativo de lotes original que reduce la exposición tras una racha de operaciones perdedoras.
Lógica de la estrategia
Filtro de patrón de velas
La estrategia trabaja exclusivamente con velas completadas suministradas por el parámetro configurable CandleType.
Mantiene una ventana deslizante de las dos últimas velas finalizadas (denominadas previous y older).
El patrón compara tanto las aperturas como los cierres de estas velas:
Si no hay posición abierta y aparece el patrón de reversión alcista, la estrategia envía una orden de compra de mercado.
Si no hay posición abierta y aparece el patrón de reversión bajista, envía una orden de venta de mercado.
Solo se permite una posición. Las señales opuestas se ignoran hasta que se cierra la operación activa.
Reglas de salida
Cuando se mantiene una posición larga, la estrategia sale si las dos velas rastreadas se mueven más bajas (previous.Open < older.Open y previous.Close < older.Close).
Cuando se mantiene una posición corta, el desencadenante de salida es simétrico (previous.Open > older.Open y previous.Close > older.Close).
No hay órdenes de stop-loss o take-profit en el asesor original, por lo que el port depende únicamente de la relación de velas para cerrar operaciones.
Dimensionamiento de posición y manejo de racha de pérdidas
El volumen de la orden se determina principalmente por MaximumRiskPercent – la fracción deseada del valor de la cartera invertida por operación. El tamaño bruto es Portfolio.CurrentValue × MaximumRiskPercent ÷ referencePrice usando el último cierre como proxy de precio.
Si la valoración de la cartera o el precio no están disponibles, el parámetro FallbackVolume actúa como valor predeterminado seguro.
Tras cada operación completamente cerrada, el PnL realizado se almacena. La racha de pérdidas consecutivas se cuenta durante los últimos HistoryDays días.
Cuando la racha es mayor a una operación, el tamaño de la siguiente orden se reduce en volume × losses ÷ DecreaseFactor, imitando la lógica de MT5.
El volumen final respeta el paso de volumen del instrumento así como los límites mínimos y máximos de volumen.
Notas adicionales de implementación
La estrategia reacciona solo a CandleStates.Finished, asegurando que el patrón use datos de mercado completos.
Las comprobaciones de entrada y salida ocurren al cierre de la vela más nueva. En MetaTrader, la orden se envía a la apertura de la siguiente barra; la diferencia es insignificante para marcos temporales más altos pero debe considerarse para intervalos muy cortos.
Las métricas de cartera en StockSharp aproximan la información de cuenta de MetaTrader. Ajuste MaximumRiskPercent o FallbackVolume si el broker usa multiplicadores de contrato diferentes.
Parámetros
Parámetro
Tipo
Predeterminado
Descripción
MaximumRiskPercent
decimal
0.02
Fracción del valor de cartera usada para dimensionar una nueva posición (0.02 = 2%).
DecreaseFactor
decimal
3
Divisor aplicado al tamaño del lote tras operaciones perdedoras consecutivas. Valores mayores suavizan la reducción.
HistoryDays
int
60
Número de días de calendario escaneados al contar la última racha de pérdidas.
FallbackVolume
decimal
0.1
Volumen de orden usado cuando no se puede realizar el cálculo basado en riesgo.
CandleType
DataType
TimeFrame(15m)
Serie de velas que proporciona los valores de apertura/cierre para la generación de señales.
Diferencias con la versión MetaTrader
Las comprobaciones de margen de cuenta dependen del Portfolio.CurrentValue de StockSharp; MetaTrader usaba AccountFreeMargin. El comportamiento coincide con la regla de riesgo original solo cuando ambas plataformas informan valoraciones similares.
El historial de operaciones se recopila de las propias ejecuciones de la estrategia en lugar del historial de toda la terminal. Asegúrese de que la estrategia funcione suficiente tiempo para acumular estadísticas de racha.
El port mantiene el modelo de posición única (sin pirámide) y refleja la falta original de órdenes protectoras. Agregue stops externamente si es necesario para el control de riesgo.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Open Close strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class OpenClose23090Strategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public OpenClose23090Strategy()
{
_fastPeriod = Param(nameof(FastPeriod), 15).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 60).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class open_close23090_strategy(Strategy):
def __init__(self):
super(open_close23090_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 60) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(open_close23090_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(open_close23090_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return open_close23090_strategy()