Ver no GitHub

Estratégia de limite de mudança de sorte

A estratégia Lucky Shift Limit é uma conversão direta do MetaTrader 4 consultor especialista Lucky_acnl6p6j89zn91fa.mq4. Ele observa as melhores cotações de compra/venda em tempo real e reage a saltos repentinos medidos em MetaTrader "pontos" (pips). Quando o preço de venda acelera para cima pela distância de mudança configurada, a estratégia atenua o movimento através da venda, enquanto uma queda acentuada na oferta provoca uma compra contrária. Todas as negociações abertas são constantemente monitoradas e fechadas quando se tornam lucrativas ou quando a perda flutuante excede um limite de segurança idêntico à lógica MQ4 original.

Requisitos de dados e execução

  • Dados de mercado – assina apenas cotações de Nível 1; nenhuma vela ou profundidade de mercado são necessárias.
  • Estilo de execução – entradas e saídas dependem de ordens de mercado para imitar as chamadas OrderSend imediatas de MetaTrader.
  • Modo de conta – funciona com contas de hedge e de compensação. Nas contas de compensação, a estratégia acumula exposição em uma única posição e o módulo de saída a nivela.
  • Dimensionamento do volume – o tamanho padrão do pedido vem de Strategy.Volume, mas o auxiliar emula AccountFreeMargin/10000 de MetaTrader quando o valor do portfólio está disponível.

Parâmetros

Nome Padrão Descrição
Shift points 3 Número mínimo de MetaTrader pontos entre pedidos/ofertas consecutivos que aciona um novo pedido. Valores maiores filtram o ruído, valores menores reagem mais rapidamente.
Limit points 18 Excursão adversa máxima permitida para uma negociação aberta. Se o preço se mover contra a posição em tantos pontos, a negociação será fechada à força.

Ambos os parâmetros são expressos em MetaTrader pontos e convertidos internamente em compensações de preço absoluto usando o tamanho do tick do instrumento. Os limites de otimização na UI correspondem aos intervalos práticos da versão MQ4.

Lógica de negociação

  1. Inicialização
    • Converte as configurações baseadas em pontos em distâncias de preços reais usando Security.PriceStep.
    • Redefine cotações de compra/venda em cache e inicia uma assinatura de nível 1 com processamento Bind de alto nível.
  2. Condições de entrada
    • Se o pedido aumentar pelo menos Shift points em comparação com o pedido anterior, a estratégia envia uma ordem de venda a mercado (desvanecendo o pico) com uma nota de registro explicando o gatilho.
    • Se o lance cair pelo menos na mesma distância em relação ao lance anterior, abre-se uma compra no mercado.
    • Os sinais podem disparar várias vezes em sequência, exatamente como o especialista original que não restringiu o número de posições simultâneas.
  3. Gerenciamento de saídas
    • Cada tick de cotação invoca TryClosePosition(). As posições longas são fechadas imediatamente quando o lance está acima da entrada média (lucro realizado) ou quando o pedido é inferior à entrada em Limit points (limite de perda).
    • As posições vendidas refletem essa lógica, fechando em cotações de venda lucrativas ou quando o lance ultrapassa a entrada pelo limite configurado.
    • Todas as saídas usam ordens de mercado para replicar OrderClose e garantir que a posição seja achatada no mesmo tick.
  4. Dimensionamento da posição
    • Calcula o volume padrão do patrimônio do portfólio (equity / 10,000, arredondado para um lote decimal) quando disponível, correspondendo ao auxiliar MQ4 GetLots().
    • Volta para a propriedade da estratégia Volume quando faltam dados de patrimônio.

Notas de implementação

  • Usa apenas APIs StockSharp de alto nível: SubscribeLevel1().Bind(ProcessLevel1) elimina a necessidade de ouvintes de cotação manuais.
  • Nenhuma coleção personalizada é armazenada; os valores de compra/venda anteriores são mantidos em variáveis ​​simples anuláveis, conforme permitido pelas diretrizes.
  • O limite de perda funciona com o tamanho do tick do instrumento, portanto, símbolos exóticos com etapas fracionárias de pip são mapeados automaticamente para o delta de preço correto.
  • As alterações de parâmetros durante o tempo de execução são respeitadas – a estratégia recalcula os limites quando os dados do Nível 1 chegam.
  • As instruções de registro documentam todos os motivos de entrada e saída, o que simplifica o backtesting e o diagnóstico em tempo real.

Dicas de uso

  • Ideal para pares de FX de alta liquidez ou índices onde choques de compra/venda ocorrem com frequência.
  • Considere combinar a estratégia com proteções em nível de portfólio (StartProtection) se forem necessários limites adicionais de stop loss ou drawdown.
  • Aumente Shift points em feeds barulhentos para reduzir o overtrading ou diminua-o para capturar movimentos de ultracurto prazo.
  • A lógica é inerentemente contrária; se o comportamento de fuga for desejado, basta definir Shift points alto o suficiente ou combiná-lo com outro indicador de filtro.
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Candle-based reversion strategy that reacts to sudden price jumps (high/low shifts)
/// and enforces a configurable loss cap. Adapted from a Level1 quote-reversion approach
/// to work with candle data for backtesting.
/// </summary>
public class LuckyShiftLimitStrategy : Strategy
{
	private readonly StrategyParam<int> _shiftPoints;
	private readonly StrategyParam<int> _limitPoints;

	private decimal? _previousHigh;
	private decimal? _previousLow;
	private decimal _shiftThreshold;
	private decimal _limitThreshold;
	private decimal _entryPrice;
	private bool _thresholdsReady;
	private int _holdBars;

	/// <summary>
	/// Minimum price shift (as percentage tenths) required to trigger an entry.
	/// </summary>
	public int ShiftPoints
	{
		get => _shiftPoints.Value;
		set => _shiftPoints.Value = value;
	}

	/// <summary>
	/// Maximum adverse excursion (as percentage) tolerated before force-closing losing trades.
	/// </summary>
	public int LimitPoints
	{
		get => _limitPoints.Value;
		set => _limitPoints.Value = value;
	}

	/// <summary>
	/// Initializes the strategy parameters taken from the original MQ4 expert.
	/// </summary>
	public LuckyShiftLimitStrategy()
	{
		_shiftPoints = Param(nameof(ShiftPoints), 3)
			.SetGreaterThanZero()
			.SetDisplay("Shift points", "Minimum price delta between consecutive candles", "Trading")

			.SetOptimize(1, 20, 1);

		_limitPoints = Param(nameof(LimitPoints), 18)
			.SetGreaterThanZero()
			.SetDisplay("Limit points", "Maximum allowed drawdown in percentage", "Risk management")

			.SetOptimize(5, 80, 5);
	}

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

		_previousHigh = null;
		_previousLow = null;
		_shiftThreshold = 0m;
		_limitThreshold = 0m;
		_entryPrice = 0m;
		_thresholdsReady = false;
		_holdBars = 0;
	}

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

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

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

	private void EnsureThresholds(decimal price)
	{
		if (_thresholdsReady)
			return;

		if (price <= 0m)
			return;

		// ShiftPoints=3 -> 0.9% shift threshold, LimitPoints=18 -> 1.8% limit threshold
		_shiftThreshold = price * ShiftPoints * 0.003m;
		_limitThreshold = price * LimitPoints * 0.01m;
		_thresholdsReady = true;
	}

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

		var high = candle.HighPrice;
		var low = candle.LowPrice;
		var close = candle.ClosePrice;

		EnsureThresholds(close);

		if (!_thresholdsReady)
			return;

		// Count hold bars for position management.
		if (Position != 0)
			_holdBars++;

		// Entry logic: detect sudden shifts in high/low between consecutive candles.
		// Only enter when flat.
		if (Position == 0 && _previousHigh is decimal prevHigh && _previousLow is decimal prevLow)
		{
			// High jumped up sharply -> sell on expected reversion
			if (high - prevHigh >= _shiftThreshold)
			{
				SellMarket();
				_entryPrice = close;
				_holdBars = 0;
				LogInfo($"Sell triggered: high shift {high - prevHigh:0.##} >= {_shiftThreshold:0.##}. Price={close:0.#####}");
			}
			// Low dropped sharply -> buy on expected rebound
			else if (prevLow - low >= _shiftThreshold)
			{
				BuyMarket();
				_entryPrice = close;
				_holdBars = 0;
				LogInfo($"Buy triggered: low shift {prevLow - low:0.##} >= {_shiftThreshold:0.##}. Price={close:0.#####}");
			}
		}

		_previousHigh = high;
		_previousLow = low;

		TryClosePosition(close);
	}

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

		if (Position != 0m && _entryPrice == 0m)
			_entryPrice = trade.Trade.Price;

		if (Position == 0m)
			_entryPrice = 0m;
	}

	private void TryClosePosition(decimal currentPrice)
	{
		if (Position == 0)
			return;

		var avgPrice = _entryPrice;

		if (avgPrice <= 0m)
			return;

		// Minimum hold of 5 bars before checking exit.
		if (_holdBars < 5)
			return;

		// Use half of shift threshold as profit target.
		var profitTarget = _shiftThreshold * 0.5m;

		if (Position > 0)
		{
			// Close long on profit or loss cap.
			if (currentPrice - avgPrice >= profitTarget)
			{
				SellMarket();
				_holdBars = 0;
				LogInfo($"Closed long in profit. Price={currentPrice:0.#####}");
			}
			else if (_limitThreshold > 0m && avgPrice - currentPrice >= _limitThreshold)
			{
				SellMarket();
				_holdBars = 0;
				LogInfo($"Closed long on loss cap. Price={currentPrice:0.#####}");
			}
		}
		else if (Position < 0)
		{
			// Close short on profit or loss cap.
			if (avgPrice - currentPrice >= profitTarget)
			{
				BuyMarket();
				_holdBars = 0;
				LogInfo($"Closed short in profit. Price={currentPrice:0.#####}");
			}
			else if (_limitThreshold > 0m && currentPrice - avgPrice >= _limitThreshold)
			{
				BuyMarket();
				_holdBars = 0;
				LogInfo($"Closed short on loss cap. Price={currentPrice:0.#####}");
			}
		}
	}
}