Ver no GitHub

Estratégia Twenty 200 Pips

Visão geral

A estratégia replica o especialista original 20/200 pips do MQL5. Examina velas horárias e compara dois preços de abertura históricos (Open[t1] e Open[t2]). Quando a diferença entre essas aberturas excede um delta configurável durante uma hora específica, a estratégia entra em uma única negociação para a sessão e depende de níveis fixos de take-profit e stop-loss.

Lógica de negociação

  1. Assinar velas horárias (configurável) e alimentar o preço de abertura em dois indicadores Shift para recuperar as aberturas nos índices necessários.
  2. Durante cada vela concluída, redefinir a flag "pode negociar" assim que a hora atual for maior que a hora de negociação configurada. Isso espelha o reset diário no consultor especialista original.
  3. Quando a hora coincide com a hora de negociação configurada e nenhuma posição está aberta, comparar os preços de abertura armazenados:
    • Se Open[t1] > Open[t2] + delta, enviar uma ordem de venda de mercado.
    • Se Open[t1] + delta < Open[t2], enviar uma ordem de compra de mercado.
  4. Após enviar uma ordem, a estratégia proíbe novas entradas até o próximo reset diário. As ordens de take-profit e stop-loss de proteção são gerenciadas via StartProtection.

Parâmetros

  • TakeProfit – distância em pontos de preço para a ordem de take-profit (padrão 200 pontos).
  • StopLoss – distância em pontos de preço para a ordem de stop-loss (padrão 2000 pontos).
  • TradeHour – hora do dia em que a verificação de entrada é realizada (padrão 18).
  • FirstOffset – índice do preço de abertura mais antigo (corresponde a Open[t1] no script MQL, padrão 7).
  • SecondOffset – índice do preço de abertura mais recente (Open[t2], padrão 2).
  • DeltaPoints – diferença mínima em pontos entre as duas aberturas para acionar uma negociação (padrão 70).
  • Volume – tamanho da ordem usado para entradas de mercado (padrão 0.1).
  • CandleType – período de tempo usado para cálculos (padrão velas de 1 hora).

Notas de implementação

  • Os indicadores Shift são processados manualmente para acessar preços de abertura históricos sem manter coleções personalizadas.
  • A estratégia chama StartProtection uma vez durante OnStarted para emular os níveis de stop-loss/take-profit definidos no especialista MQL.
  • Comentários em inglês são incluídos diretamente no código para facilitar a manutenção e revisão.
  • Apenas uma negociação por dia é permitida porque _canTrade é limpo logo após uma ordem ser colocada e só é restaurado depois que a hora de negociação configurada passar.

Uso

  1. Anexar a estratégia a um instrumento e configurar os parâmetros de acordo com o instrumento alvo.
  2. Certificar-se de que o instrumento tem um PriceStep válido; ele é usado para converter parâmetros baseados em pontos em distâncias de preço absolutas.
  3. Iniciar a estratégia. Ela esperará até a hora configurada e agirá na próxima vela concluída se as condições de preço de abertura forem atendidas.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Daily breakout strategy derived from the "20/200 pips" MQL5 expert.
/// Compares open prices from different bar offsets and trades the breakout.
/// </summary>
public class Twenty200PipsStrategy : Strategy
{
	private readonly StrategyParam<int> _takeProfit;
	private readonly StrategyParam<int> _stopLoss;
	private readonly StrategyParam<int> _firstOffset;
	private readonly StrategyParam<int> _secondOffset;
	private readonly StrategyParam<int> _deltaPoints;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _opens = new();
	private decimal _pointValue;

	public int TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	public int StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	public int FirstOffset
	{
		get => _firstOffset.Value;
		set => _firstOffset.Value = value;
	}

	public int SecondOffset
	{
		get => _secondOffset.Value;
		set => _secondOffset.Value = value;
	}

	public int DeltaPoints
	{
		get => _deltaPoints.Value;
		set => _deltaPoints.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public Twenty200PipsStrategy()
	{
		_takeProfit = Param(nameof(TakeProfit), 200)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (points)", "Take profit distance in points", "Risk")
			.SetOptimize(50, 500, 50);

		_stopLoss = Param(nameof(StopLoss), 2000)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (points)", "Stop loss distance in points", "Risk")
			.SetOptimize(200, 4000, 100);

		_firstOffset = Param(nameof(FirstOffset), 7)
			.SetGreaterThanZero()
			.SetDisplay("First Offset", "Older bar index", "Signal")
			.SetOptimize(1, 12, 1);

		_secondOffset = Param(nameof(SecondOffset), 2)
			.SetGreaterThanZero()
			.SetDisplay("Second Offset", "Newer bar index", "Signal")
			.SetOptimize(1, 6, 1);

		_deltaPoints = Param(nameof(DeltaPoints), 1)
			.SetGreaterThanZero()
			.SetDisplay("Delta (points)", "Minimum difference between opens", "Signal")
			.SetOptimize(10, 200, 10);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_opens.Clear();
		_pointValue = 0m;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_pointValue = Security?.PriceStep ?? 1m;
		if (_pointValue <= 0m)
			_pointValue = 1m;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}

		StartProtection(
			takeProfit: new Unit(TakeProfit * _pointValue, UnitTypes.Absolute),
			stopLoss: new Unit(StopLoss * _pointValue, UnitTypes.Absolute),
			useMarketOrders: true);
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_opens.Add(candle.OpenPrice);

		var maxOffset = Math.Max(FirstOffset, SecondOffset);
		if (_opens.Count <= maxOffset)
			return;

		// Keep buffer limited
		if (_opens.Count > maxOffset + 100)
			_opens.RemoveRange(0, _opens.Count - maxOffset - 50);

		if (Position != 0)
			return;

		var openFirst = _opens[_opens.Count - 1 - FirstOffset];
		var openSecond = _opens[_opens.Count - 1 - SecondOffset];
		var threshold = DeltaPoints * _pointValue;

		if (openFirst > openSecond + threshold)
		{
			SellMarket();
		}
		else if (openFirst + threshold < openSecond)
		{
			BuyMarket();
		}
	}
}