Ver no GitHub

Grade Adaptável MT4 (StockSharp Porta)

Visão geral

A estratégia recria o consultor especialista "Adaptive Grid Mt4" para StockSharp de alto nível API. Ele descarta uma grade simétrica de ordens de compra e venda em torno do fechamento da vela atual. As distâncias da grade são derivadas do Average True Range (ATR) e são, portanto, adaptáveis à volatilidade do mercado. Cada ordem pendente expira após um número configurável de velas, mantendo o carteira de pedidos organizada em mercados laterais.

Quando uma ordem de entrada é preenchida, a estratégia registra imediatamente as ordens correspondentes de take-profit e stop-loss a preços calculados do instantâneo ATR que produziu a grade. As ordens de proteção são individuais com a entrada preenchida e persistem até serem executadas ou cancelado manualmente.

Parâmetros

Parâmetro Descrição
GridLevels Número de ordens stop acima e abaixo do mercado. Equivalente à entrada nGrid do EA.
TimerBars Número de velas concluídas após as quais qualquer entrada pendente é cancelada (MT4 nBars).
PriceOffsetMultiplier Multiplicador ATR aplicado ao deslocamento inicial do preço atual (Poffset).
GridStepMultiplier Multiplicador ATR usado para o espaçamento entre níveis de grade consecutivos (Pstep).
StopLossMultiplier Multiplicador ATR que define a distância do stop-loss associado a cada ordem (StopLoss).
TakeProfitMultiplier Multiplicador ATR que define a distância do take-profit (TakeProfit).
AtrPeriod período ATR médio. Espelha o valor codificado de 14 do script.
OrderVolume Volume utilizado para todas as ordens pendentes (MT4 Lot).
CandleType Período que impulsiona o recálculo da grade (Wtf).

Lógica de negociação

  1. Assine velas do CandleType configurado e alimente um ATR(14).
  2. Em cada vela acabada:
    • Avançar o contador de barras interno e cancelar pedidos de grade pendentes que excederam TimerBars.
    • Ignore o processamento adicional se ATR não estiver formado, se alguma ordem de grade ainda estiver ativa ou se a estratégia já mantiver uma posição.
    • Calcule o deslocamento do rompimento, o espaçamento da grade, as distâncias de stop-loss e de take-profit como valores ATR * multiplier.
    • Coloque GridLevels pares de ordens de compra e venda em torno do fechamento da vela, normalizando os preços com Security.ShrinkPrice para respeitar o tamanho do tick do instrumento.
  3. Quando uma entrada for preenchida, remova-a da lista da grade rastreada e gere as ordens de proteção correspondentes:
    • As entradas longas recebem um stop-loss de SellStop e um take-profit de SellLimit.
    • As entradas curtas recebem um stop-loss de BuyStop e um take-profit de BuyLimit.
  4. As ordens de proteção são monitoradas via OnOrderChanged para que as entradas concluídas ou canceladas sejam removidas do rastreamento listas.

Notas

  • A grade só é reconstruída quando não há posições abertas e todas as ordens de grade existentes expiraram, correspondendo à lógica What() de o original EA.
  • Os preços são calculados a partir do fechamento da vela, em vez do tick bruto Bid/Ask. Isso mantém a implementação orientada por velas enquanto produz o mesmo layout simétrico em todo o mercado.
  • O instantâneo ATR usado para a grade também é usado para ordens de proteção para imitar a parada e o lucro por bilhete de MetaTrader valores.
  • Ainda não há tradução do Python, correspondendo à solicitação.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Adaptive grid strategy using ATR-based breakout levels.
/// Simplified from the "Adaptive Grid Mt4" expert advisor to use market orders.
/// </summary>
public class AdaptiveGridMt4Strategy : Strategy
{
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _breakoutMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private AverageTrueRange _atr;
	private decimal? _prevClose;
	private decimal? _prevAtr;
	private decimal _stopPrice;
	private decimal _takeProfitPrice;

	/// <summary>
	/// ATR averaging period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Breakout threshold in ATR multiples.
	/// </summary>
	public decimal BreakoutMultiplier
	{
		get => _breakoutMultiplier.Value;
		set => _breakoutMultiplier.Value = value;
	}

	/// <summary>
	/// Candle type used to drive calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public AdaptiveGridMt4Strategy()
	{
		_atrPeriod = Param(nameof(AtrPeriod), 20)
		.SetGreaterThanZero()
		.SetDisplay("ATR Period", "Number of candles used for ATR smoothing", "Indicators");

		_breakoutMultiplier = Param(nameof(BreakoutMultiplier), 2.5m)
		.SetGreaterThanZero()
		.SetDisplay("Breakout Multiplier", "ATR multiplier for breakout threshold", "Grid");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
		.SetDisplay("Candle Type", "Candle type used to trigger grid recalculation", "General");
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevClose = null;
		_prevAtr = null;
		_stopPrice = 0;
		_takeProfitPrice = 0;

		_atr = new AverageTrueRange { Length = AtrPeriod };

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

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

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

		if (!_atr.IsFormed || atrValue <= 0)
		{
			_prevClose = candle.ClosePrice;
			_prevAtr = atrValue;
			return;
		}

		// Check protective stops
		if (Position > 0)
		{
			if (_stopPrice > 0 && candle.LowPrice <= _stopPrice)
			{
				SellMarket(Math.Abs(Position));
				_stopPrice = 0;
				_takeProfitPrice = 0;
			}
			else if (_takeProfitPrice > 0 && candle.HighPrice >= _takeProfitPrice)
			{
				SellMarket(Math.Abs(Position));
				_stopPrice = 0;
				_takeProfitPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (_stopPrice > 0 && candle.HighPrice >= _stopPrice)
			{
				BuyMarket(Math.Abs(Position));
				_stopPrice = 0;
				_takeProfitPrice = 0;
			}
			else if (_takeProfitPrice > 0 && candle.LowPrice <= _takeProfitPrice)
			{
				BuyMarket(Math.Abs(Position));
				_stopPrice = 0;
				_takeProfitPrice = 0;
			}
		}

		if (_prevClose is not decimal prevClose || _prevAtr is not decimal prevAtr || prevAtr <= 0)
		{
			_prevClose = candle.ClosePrice;
			_prevAtr = atrValue;
			return;
		}

		var threshold = prevAtr * BreakoutMultiplier;
		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Breakout up
		if (candle.ClosePrice > prevClose + threshold && Position <= 0)
		{
			BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
			_stopPrice = candle.ClosePrice - atrValue * 3;
			_takeProfitPrice = candle.ClosePrice + atrValue * 4;
		}
		// Breakout down
		else if (candle.ClosePrice < prevClose - threshold && Position >= 0)
		{
			SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
			_stopPrice = candle.ClosePrice + atrValue * 3;
			_takeProfitPrice = candle.ClosePrice - atrValue * 4;
		}

		_prevClose = candle.ClosePrice;
		_prevAtr = atrValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_atr = null;
		_prevClose = null;
		_prevAtr = null;
		_stopPrice = 0;
		_takeProfitPrice = 0;

		base.OnReseted();
	}
}