Ver no GitHub

Estratégia Abrir Fechar (ID 3996)

Visão geral

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

  1. 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.
  2. 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;
	}
}