Esta estratégia replica o MetaTrader 4 especialista open_close.mq4. Ele funciona em um único instrumento e compara a abertura e o fechamento da última vela com a anterior. Quando nenhuma posição está ativa, ele desvanece movimentos fortes de uma barra (padrões de intervalo e reversão). Durante uma negociação, ele fecha a posição quando o padrão é revertido ou quando um limite de proteção contra perdas flutuantes é violado.
Lógica de negociação
Regras de entrada
Negocia somente quando a vela anterior tiver sido processada (a guarda Volume[0] == 1 original).
Entrada longa: a vela atual abre acima da abertura anterior e fecha abaixo do fechamento anterior. A estratégia compra o volume configurado no mercado.
Entrada curta: a vela atual abre abaixo da abertura anterior e fecha acima do fechamento anterior. A estratégia vende a descoberto no mercado.
Apenas uma posição pode estar ativa por vez. Novos sinais são ignorados até que a posição aberta seja fechada.
Regras de saída
Proteção contra riscos: o PnL flutuante é medido a partir do preço médio de entrada. Se a perda não realizada exceder MaximumRisk × Portfolio.CurrentValue, a estratégia fecha imediatamente a posição. A versão original do MQL usava AccountMargin, que é aproximado aqui com a melhor avaliação de portfólio disponível.
Reversão de padrão:
As posições longas fecham quando a próxima vela continua descendente (open < previous open e close < previous close).
As posições curtas fecham quando a próxima vela continua ascendente (open > previous open e close > previous close).
Dimensionamento de posições
O tamanho do pedido padrão é derivado de MaximumRisk. A estratégia multiplica o valor da conta disponível por MaximumRisk e divide o resultado por 1000, imitando o cálculo de MetaTrader de AccountFreeMargin * MaximumRisk / 1000.
Se as informações da conta não estiverem disponíveis, o parâmetro substituto InitialVolume será usado.
Após mais de uma negociação consecutiva perdida, o tamanho do lote é reduzido em volume × losses / DecreaseFactor, reproduzindo o loop MetaTrader ao longo do histórico de negociações fechadas.
Um volume negociável mínimo de 0.1 lotes é aplicado antes de alinhar a quantidade à etapa de volume do instrumento e aos limites de troca.
Parâmetros
Nome
Tipo
Padrão
Descrição
InitialVolume
decimal
0.1
Tamanho do lote substituto usado quando as informações sobre patrimônio não estão disponíveis.
MaximumRisk
decimal
0.3
Fração do valor da conta que controla o tamanho da posição e a perda flutuante máxima tolerada.
DecreaseFactor
decimal
100
Fator de redução aplicado após mais de uma negociação consecutiva com perdas.
CandleType
DataType
15m período de tempo
Série de velas usada para avaliar o padrão.
Notas de implementação
A estratégia assina a série de velas selecionada e processa apenas velas finalizadas, correspondendo à condição Volume[0] > 1 no especialista original.
O PnL flutuante é estimado a partir da posição atual da estratégia e do último preço de fechamento porque StockSharp não expõe as métricas AccountProfit e AccountMargin de MetaTrader.
As perdas consecutivas são rastreadas por meio de negociações preenchidas, permitindo que DecreaseFactor se comporte como o loop original ao longo do histórico de negociações.
O alinhamento de volume respeita Security.VolumeStep, MinVolume e MaxVolume para permanecer compatível com os requisitos de troca.
Os gráficos são preenchidos com velas e negociações próprias quando uma área do gráfico está disponível para depuração visual.
Dicas de uso
Escolha um intervalo de vela que corresponda ao usado em MetaTrader ao calibrar o especialista original.
Ajuste MaximumRisk e DecreaseFactor para ajustar a agressividade da regra de dimensionamento de lote.
Como a estratégia é contrária, ela tem melhor desempenho em instrumentos que apresentam freqüentes sobreextensões de barra única e movimentos de snap-back.
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()