Esta estrategia replica el MetaTrader4 experto open_close.mq4. Funciona en un solo instrumento y compara la apertura y el cierre de la última vela con la anterior. Cuando no hay ninguna posición activa, se desvanecen los fuertes movimientos de una barra (patrones de brecha y reversión). Mientras está en una operación, cierra la posición cuando el patrón se invierte o cuando se supera un umbral de protección de pérdidas flotantes.
Lógica de trading
Reglas de entrada
Se comercializa solo cuando se ha procesado la vela anterior (la guardia Volume[0] == 1 original).
Entrada larga: la vela actual se abre por encima de la apertura anterior y cierra por debajo del cierre anterior. La estrategia compra el volumen configurado en el mercado.
Entrada corta: la vela actual se abre por debajo de la apertura anterior y cierra por encima del cierre anterior. La estrategia vende en corto en el mercado.
Sólo una posición puede estar activa en cualquier momento. Las nuevas señales se ignoran hasta que se cierra la posición abierta.
reglas de salida
Protección contra riesgos: el PnL flotante se mide a partir del precio de entrada promedio. Si la pérdida no realizada supera MaximumRisk × Portfolio.CurrentValue, la estrategia cierra inmediatamente la posición. La versión original MQL utilizaba AccountMargin, que aquí se aproxima con la mejor valoración de cartera disponible.
Inversión de patrón:
Las posiciones largas se cierran cuando la siguiente vela continúa hacia abajo (open < previous open y close < previous close).
Las posiciones cortas se cierran cuando la siguiente vela continúa hacia arriba (open > previous open y close > previous close).
Dimensionamiento de posiciones
El tamaño del pedido predeterminado se deriva de MaximumRisk. La estrategia multiplica el valor de la cuenta disponible por MaximumRisk y divide el resultado por 1000, imitando el cálculo MetaTrader de AccountFreeMargin * MaximumRisk / 1000.
Si la información de la cuenta no está disponible, se utiliza el parámetro alternativo InitialVolume.
Después de más de una operación perdedora consecutiva, el tamaño del lote se reduce en volume × losses / DecreaseFactor, reproduciendo el bucle MetaTrader en el historial de operaciones cerradas.
Se aplica un volumen negociable mínimo de 0.1 lotes antes de alinear la cantidad con el paso de volumen del instrumento y los límites de intercambio.
Parámetros
Nombre
Tipo
Predeterminado
Descripción
InitialVolume
decimal
0.1
Tamaño de lote alternativo utilizado cuando la información sobre el patrimonio no está disponible.
MaximumRisk
decimal
0.3
Fracción del valor de la cuenta que controla tanto el tamaño de la posición como la pérdida flotante máxima tolerada.
DecreaseFactor
decimal
100
Factor de reducción aplicado después de más de una operación perdedora consecutiva.
CandleType
DataType
15m período de tiempo
Serie de velas utilizadas para evaluar el patrón.
Notas de implementación
La estrategia se suscribe a la serie de velas seleccionada y procesa solo velas terminadas, que coinciden con la condición Volume[0] > 1 en el experto original.
El PnL flotante se estima a partir de la posición actual de la estrategia y el último precio de cierre porque StockSharp no expone las métricas AccountProfit y AccountMargin de MetaTrader.
Las pérdidas consecutivas se rastrean a través de operaciones completadas, lo que permite que DecreaseFactor se comporte como el bucle original a lo largo del historial de operaciones.
La alineación del volumen respeta Security.VolumeStep, MinVolume y MaxVolume para seguir siendo compatible con los requisitos de intercambio.
Los gráficos se completan con velas y operaciones propias cuando hay un área del gráfico disponible para la depuración visual.
Consejos de uso
Elija un intervalo de vela que coincida con el utilizado en MetaTrader al calibrar el experto original.
Ajuste MaximumRisk y DecreaseFactor para ajustar la agresividad de la regla de tamaño de lote.
Debido a que la estrategia es contraria, funciona mejor en instrumentos que exhiben frecuentes sobreextensiones de una sola barra y movimientos de retroceso.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Contrarian pattern strategy converted from the MetaTrader expert "open_close".
/// Evaluates relationships between consecutive candle opens and closes.
/// Buys when a bearish candle opens above the previous open (fading the move),
/// and sells when a bullish candle opens below the previous open.
/// </summary>
public class OpenCloseStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _ema;
private bool _hasPreviousCandle;
private decimal _previousOpen;
private decimal _previousClose;
public OpenCloseStrategy()
{
_stopLossPoints = Param(nameof(StopLossPoints), 500m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss in absolute points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
.SetNotNegative()
.SetDisplay("Take Profit", "Take profit in absolute points", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time-frame used to evaluate the open/close pattern.", "Data");
Volume = 1;
}
/// <summary>
/// Stop loss distance in absolute points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance in absolute points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle series used to evaluate the pattern.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_hasPreviousCandle = false;
_previousOpen = 0m;
_previousClose = 0m;
_ema = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
_ema = new ExponentialMovingAverage { Length = 20 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
if (tp != null || sl != null)
StartProtection(tp, sl);
base.OnStarted2(time);
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
var open = candle.OpenPrice;
var close = candle.ClosePrice;
if (!_hasPreviousCandle)
{
_previousOpen = open;
_previousClose = close;
_hasPreviousCandle = true;
return;
}
// Exit logic
if (Position > 0)
{
// Close long on bearish continuation
if (open < _previousOpen && close < _previousClose)
SellMarket(Position);
}
else if (Position < 0)
{
// Close short on bullish continuation
if (open > _previousOpen && close > _previousClose)
BuyMarket(Math.Abs(Position));
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_previousOpen = open;
_previousClose = close;
return;
}
// Entry logic
if (Position == 0)
{
// Buy: fade a bearish candle that opened above the previous open
if (open > _previousOpen && close < _previousClose)
{
BuyMarket(Volume);
}
// Sell: fade a bullish candle that opened below the previous open
else if (open < _previousOpen && close > _previousClose)
{
SellMarket(Volume);
}
}
_previousOpen = open;
_previousClose = close;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class open_close_strategy(Strategy):
def __init__(self):
super(open_close_strategy, self).__init__()
self._sl_points = self.Param("StopLossPoints", 500.0).SetNotNegative().SetDisplay("Stop Loss", "Stop loss in absolute points", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 500.0).SetNotNegative().SetDisplay("Take Profit", "Take profit in absolute points", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Time-frame for open/close pattern.", "Data")
self.Volume = 1
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(open_close_strategy, self).OnReseted()
self._has_prev = False
self._prev_open = 0
self._prev_close = 0
def OnStarted2(self, time):
super(open_close_strategy, self).OnStarted2(time)
self._has_prev = False
self._prev_open = 0
self._prev_close = 0
ema = ExponentialMovingAverage()
ema.Length = 20
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(ema, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
tp = Unit(self._tp_points.Value, UnitTypes.Absolute) if self._tp_points.Value > 0 else None
sl = Unit(self._sl_points.Value, UnitTypes.Absolute) if self._sl_points.Value > 0 else None
if tp is not None or sl is not None:
self.StartProtection(tp, sl)
def OnProcess(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
open_price = candle.OpenPrice
close = candle.ClosePrice
if not self._has_prev:
self._prev_open = open_price
self._prev_close = close
self._has_prev = True
return
# Exit logic
if self.Position > 0:
if open_price < self._prev_open and close < self._prev_close:
self.SellMarket(self.Position)
elif self.Position < 0:
if open_price > self._prev_open and close > self._prev_close:
self.BuyMarket(Math.Abs(self.Position))
# Entry logic
if self.Position == 0:
if open_price > self._prev_open and close < self._prev_close:
self.BuyMarket(self.Volume)
elif open_price < self._prev_open and close > self._prev_close:
self.SellMarket(self.Volume)
self._prev_open = open_price
self._prev_close = close
def CreateClone(self):
return open_close_strategy()