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
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.
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.
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.
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.#####}");
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class lucky_shift_limit_strategy(Strategy):
"""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."""
def __init__(self):
super(lucky_shift_limit_strategy, self).__init__()
self._shift_points = self.Param("ShiftPoints", 3) \
.SetGreaterThanZero() \
.SetDisplay("Shift points", "Minimum price delta between consecutive candles", "Trading")
self._limit_points = self.Param("LimitPoints", 18) \
.SetGreaterThanZero() \
.SetDisplay("Limit points", "Maximum allowed drawdown in percentage", "Risk management")
self._previous_high = None
self._previous_low = None
self._shift_threshold = 0.0
self._limit_threshold = 0.0
self._entry_price = 0.0
self._thresholds_ready = False
self._hold_bars = 0
@property
def ShiftPoints(self):
return self._shift_points.Value
@property
def LimitPoints(self):
return self._limit_points.Value
def OnReseted(self):
super(lucky_shift_limit_strategy, self).OnReseted()
self._previous_high = None
self._previous_low = None
self._shift_threshold = 0.0
self._limit_threshold = 0.0
self._entry_price = 0.0
self._thresholds_ready = False
self._hold_bars = 0
def OnStarted2(self, time):
super(lucky_shift_limit_strategy, self).OnStarted2(time)
tf = DataType.TimeFrame(TimeSpan.FromMinutes(5))
subscription = self.SubscribeCandles(tf)
subscription.Bind(self._process_candle).Start()
def _ensure_thresholds(self, price):
if self._thresholds_ready:
return
if price <= 0:
return
# ShiftPoints=3 -> 0.9% shift threshold, LimitPoints=18 -> 1.8% limit threshold
self._shift_threshold = float(price) * self.ShiftPoints * 0.003
self._limit_threshold = float(price) * self.LimitPoints * 0.01
self._thresholds_ready = True
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._ensure_thresholds(close)
if not self._thresholds_ready:
return
# Count hold bars for position management.
if self.Position != 0:
self._hold_bars += 1
# Entry logic: detect sudden shifts in high/low between consecutive candles.
# Only enter when flat.
if self.Position == 0 and self._previous_high is not None and self._previous_low is not None:
prev_high = self._previous_high
prev_low = self._previous_low
# High jumped up sharply -> sell on expected reversion
if high - prev_high >= self._shift_threshold:
self.SellMarket()
self._entry_price = close
self._hold_bars = 0
# Low dropped sharply -> buy on expected rebound
elif prev_low - low >= self._shift_threshold:
self.BuyMarket()
self._entry_price = close
self._hold_bars = 0
self._previous_high = high
self._previous_low = low
self._try_close_position(close)
def OnOwnTradeReceived(self, trade):
super(lucky_shift_limit_strategy, self).OnOwnTradeReceived(trade)
if self.Position != 0 and self._entry_price == 0:
self._entry_price = float(trade.Trade.Price)
if self.Position == 0:
self._entry_price = 0.0
def _try_close_position(self, current_price):
if self.Position == 0:
return
avg_price = self._entry_price
if avg_price <= 0:
return
# Minimum hold of 5 bars before checking exit.
if self._hold_bars < 5:
return
# Use half of shift threshold as profit target.
profit_target = self._shift_threshold * 0.5
if self.Position > 0:
# Close long on profit or loss cap.
if current_price - avg_price >= profit_target:
self.SellMarket()
self._hold_bars = 0
elif self._limit_threshold > 0 and avg_price - current_price >= self._limit_threshold:
self.SellMarket()
self._hold_bars = 0
elif self.Position < 0:
# Close short on profit or loss cap.
if avg_price - current_price >= profit_target:
self.BuyMarket()
self._hold_bars = 0
elif self._limit_threshold > 0 and current_price - avg_price >= self._limit_threshold:
self.BuyMarket()
self._hold_bars = 0
def CreateClone(self):
return lucky_shift_limit_strategy()