Ver no GitHub

Estratégia de Grade de Ordens Pendentes

Esta estratégia reproduz o comportamento do consultor especialista de grade de ordens pendentes "AntiFragile" do MetaTrader. Ela constrói continuamente uma grade simétrica de ordens de stop em torno do preço de mercado atual e aplica saídas protetoras quando as posições são abertas.

Lógica central

  • Na inicialização, a estratégia armazena em cache o melhor bid e ask de dados de nível 1 / livro de ordens e coloca ordens de compra-stop acima do preço e ordens de venda-stop abaixo do preço.
  • Os preços das ordens são deslocados do mercado pelo parâmetro Distance e cada nível subsequente é espaçado por Spacing (ticks) multiplicado pelo passo de preço do instrumento.
  • Cada novo nível de grade aumenta o volume da ordem em Volume Increase % em relação ao tamanho inicial, implementando o escalonamento estilo martingale da versão MQL.
  • Quando uma ordem é executada, a posição líquida resultante é protegida com ordens de stop-loss e take-profit. A lógica de trailing stop opcional reutiliza o último bid/ask para ajustar o stop quando o lucro não realizado excede a distância de trailing.
  • A grade é reconstruída automaticamente depois que todas as ordens pendentes foram executadas ou canceladas e a posição retorna ao plano.

Parâmetros

  • Starting Volume – tamanho de lote/contrato para a primeira ordem pendente. Ordens subsequentes escalam por Volume Increase %.
  • Volume Increase % – incremento percentual adicionado a cada novo nível de grade (0,1 equivale a +0,1% por nível).
  • Distance – deslocamento de preço absoluto adicionado antes da primeira ordem (interpretado em moeda do instrumento).
  • Spacing (ticks) – número de passos de preço entre ordens de grade consecutivas.
  • Orders per side – número máximo de ordens de grade para comprados e vendidos separadamente.
  • Take Profit (ticks) – distância do alvo de lucro desde a entrada média, expressa em passos de preço.
  • Stop Loss (ticks) – distância do stop desde a entrada média. Definir como zero para desabilitar o stop inicial.
  • Trailing Stop (ticks) – distância de trailing. Definir como zero para desabilitar os ajustes de trailing.
  • Enable Long Grid / Enable Short Grid – interruptores para colocar ordens buy-stop ou sell-stop.

Notas de implementação

  • As estratégias do StockSharp usam posições líquidas, portanto execuções opostas se compensarão mutuamente em vez de criar cestas cobertas como no MT4. A grade permanece simétrica, mas apenas a exposição líquida é rastreada.
  • Os volumes e preços são arredondados para os tamanhos de passo do instrumento antes de enviar as ordens.
  • Os trailing stops são recriados cancelando a ordem de stop anterior e enviando uma nova em um nível mais ajustado quando o lucro excede a distância de trailing.
  • A estratégia requer dados do livro de ordens (SubscribeOrderBook) para impulsionar o rastreamento de preço e a lógica de trailing.

Dicas de uso

  1. Configure Starting Volume e Volume Increase % de forma conservadora; os padrões originais pressupõem o dimensionamento de lote Forex e podem crescer rapidamente.
  2. Certifique-se de que o portfólio suporte ordens de stop para o local de destino. Todas as entradas de grade são ordens stop-market.
  3. Monitore os requisitos de margem porque um grande número de ordens pendentes pode consumir capital reservado.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Pending order grid strategy that mirrors the classic AntiFragile EA behavior.
/// Places layered virtual grid levels above and below the initial price.
/// When price reaches a grid level, a market order is executed.
/// Applies take profit and stop loss management based on entry price.
/// </summary>
public class PendingOrderGridStrategy : Strategy
{
	private readonly StrategyParam<decimal> _gridSpacing;
	private readonly StrategyParam<int> _gridLevels;
	private readonly StrategyParam<decimal> _takeProfitPercent;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<bool> _tradeLong;
	private readonly StrategyParam<bool> _tradeShort;

	private decimal _initialPrice;
	private decimal _entryPrice;
	private bool _initialized;
	private HashSet<int> _triggeredBuyLevels;
	private HashSet<int> _triggeredSellLevels;
	private int _tradeCount;

	/// <summary>
	/// Spacing between grid levels as a percentage of price.
	/// </summary>
	public decimal GridSpacing
	{
		get => _gridSpacing.Value;
		set => _gridSpacing.Value = value;
	}

	/// <summary>
	/// Number of grid levels per side.
	/// </summary>
	public int GridLevels
	{
		get => _gridLevels.Value;
		set => _gridLevels.Value = value;
	}

	/// <summary>
	/// Take profit as percentage of entry price.
	/// </summary>
	public decimal TakeProfitPercent
	{
		get => _takeProfitPercent.Value;
		set => _takeProfitPercent.Value = value;
	}

	/// <summary>
	/// Stop loss as percentage of entry price.
	/// </summary>
	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}

	/// <summary>
	/// Enables buying on grid levels below price.
	/// </summary>
	public bool TradeLong
	{
		get => _tradeLong.Value;
		set => _tradeLong.Value = value;
	}

	/// <summary>
	/// Enables selling on grid levels above price.
	/// </summary>
	public bool TradeShort
	{
		get => _tradeShort.Value;
		set => _tradeShort.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="PendingOrderGridStrategy"/> class.
	/// </summary>
	public PendingOrderGridStrategy()
	{
		_gridSpacing = Param(nameof(GridSpacing), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("Grid Spacing %", "Percentage spacing between grid levels", "Grid");

		_gridLevels = Param(nameof(GridLevels), 3)
			.SetGreaterThanZero()
			.SetDisplay("Grid Levels", "Number of grid levels per side", "Grid");

		_takeProfitPercent = Param(nameof(TakeProfitPercent), 2.0m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit %", "Take profit as percentage of entry", "Risk");

		_stopLossPercent = Param(nameof(StopLossPercent), 3.0m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss %", "Stop loss as percentage of entry", "Risk");

		_tradeLong = Param(nameof(TradeLong), true)
			.SetDisplay("Enable Long", "Enable buy grid levels", "Grid");

		_tradeShort = Param(nameof(TradeShort), true)
			.SetDisplay("Enable Short", "Enable sell grid levels", "Grid");

		_triggeredBuyLevels = new HashSet<int>();
		_triggeredSellLevels = new HashSet<int>();
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_initialPrice = 0m;
		_entryPrice = 0m;
		_initialized = false;
		_triggeredBuyLevels = new HashSet<int>();
		_triggeredSellLevels = new HashSet<int>();
		_tradeCount = 0;
	}

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

		var tf = TimeSpan.FromMinutes(5).TimeFrame();

		SubscribeCandles(tf)
			.Bind(ProcessCandle)
			.Start();
	}

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

		var close = candle.ClosePrice;

		// Initialize grid around the first candle's close price
		if (!_initialized)
		{
			_initialPrice = close;
			_initialized = true;
			_triggeredBuyLevels.Clear();
			_triggeredSellLevels.Clear();
			return;
		}

		// Check if we have a position that needs TP/SL management
		if (Position != 0m && _entryPrice > 0m)
		{
			if (Position > 0m)
			{
				var tpPrice = _entryPrice * (1m + TakeProfitPercent / 100m);
				var slPrice = _entryPrice * (1m - StopLossPercent / 100m);

				if (close >= tpPrice || close <= slPrice)
				{
					SellMarket();
					ResetGrid(close);
					return;
				}
			}
			else if (Position < 0m)
			{
				var tpPrice = _entryPrice * (1m - TakeProfitPercent / 100m);
				var slPrice = _entryPrice * (1m + StopLossPercent / 100m);

				if (close <= tpPrice || close >= slPrice)
				{
					BuyMarket();
					ResetGrid(close);
					return;
				}
			}
		}

		// Check grid levels for new entries
		var spacing = GridSpacing / 100m;

		// Buy levels below initial price
		if (TradeLong)
		{
			for (var i = 1; i <= GridLevels; i++)
			{
				if (_triggeredBuyLevels.Contains(i))
					continue;

				var level = _initialPrice * (1m - i * spacing);

				if (close <= level && Position <= 0m)
				{
					// Close any short first
					if (Position < 0m)
						BuyMarket();

					BuyMarket();
					_triggeredBuyLevels.Add(i);
					_tradeCount++;
					return;
				}
			}
		}

		// Sell levels above initial price
		if (TradeShort)
		{
			for (var i = 1; i <= GridLevels; i++)
			{
				if (_triggeredSellLevels.Contains(i))
					continue;

				var level = _initialPrice * (1m + i * spacing);

				if (close >= level && Position >= 0m)
				{
					// Close any long first
					if (Position > 0m)
						SellMarket();

					SellMarket();
					_triggeredSellLevels.Add(i);
					_tradeCount++;
					return;
				}
			}
		}
	}

	private void ResetGrid(decimal newPrice)
	{
		_initialPrice = newPrice;
		_entryPrice = 0m;
		_triggeredBuyLevels.Clear();
		_triggeredSellLevels.Clear();
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		if (trade?.Trade != null)
			_entryPrice = trade.Trade.Price;
	}
}