A Estratégia de Preço Extremo replica o consultor especialista do MetaTrader Price_Extreme_Strategy usando a API de alto nível do StockSharp. O sistema monitora um canal deslizante derivado do maior máximo e do menor mínimo ao longo de um número configurável de velas concluídas. Sinais de rompimento são gerados sempre que a vela de referência selecionada fecha acima do limite superior ou abaixo do limite inferior. A lógica pode opcionalmente ser invertida para transformar condições de rompimento em entradas de contra-tendência.
Esta conversão mantém o fluxo de trabalho de trading orientado a eventos. As ordens são enviadas imediatamente após o fechamento de cada vela finalizada, correspondendo ao comportamento do algoritmo MQL original que reagia no tick de abertura da próxima barra.
Lógica do indicador
O canal de Preço Extremo é reconstruído a cada vela finalizada usando os indicadores Highest e Lowest do StockSharp:
Highest rastreia o máximo dos altos nas últimas N velas.
Lowest rastreia o mínimo dos baixos nas últimas N velas.
Esses buffers emulam o estudo personalizado Price_Extreme_Indicator incluído com o consultor especialista original. O comprimento do indicador é exposto através do parâmetro Level Length.
Um parâmetro separado Signal Shift define qual vela fechada é usada para avaliar a condição de rompimento. Um shift de 1 significa "usar a vela que acabou de fechar" (padrão). Valores maiores permitem aguardar confirmação adicional referenciando barras mais antigas.
Regras de trading
Recalcular os valores do canal superior e inferior para cada vela finalizada.
Recuperar a vela especificada por Signal Shift do buffer de histórico interno.
Gerar intenções direcionais:
Rompimento para cima: o fechamento da vela está acima do valor do canal superior.
Rompimento para baixo: o fechamento da vela está abaixo do valor do canal inferior.
Aplicar inversão opcional com Reverse Signals:
Se desativado, operar na direção do rompimento (comprar no rompimento para cima, vender no rompimento para baixo).
Se ativado, trocar as reações (vender no rompimento para cima, comprar no rompimento para baixo).
Respeitar as permissões Enable Long e Enable Short antes de enviar ordens.
Fechar automaticamente qualquer posição oposta antes de abrir uma nova negociação para que apenas uma posição líquida exista a qualquer momento.
Gestão de risco
A estratégia fornece tratamento de stop-loss e take-profit que espelha os controles baseados em pontos da versão MQL:
Stop Loss e Take Profit são expressos em passos de preço (Security.PriceStep).
Os preços-alvo são recalculados sempre que o tamanho da posição líquida muda.
Se uma vela finalizada ultrapassa os níveis de proteção (mínimo abaixo do stop para posições compradas, máximo acima do stop para posições vendidas, etc.), a posição é fechada por ordem a mercado e os alvos de proteção são limpos.
StartProtection() é ativado durante OnStarted para aproveitar as salvaguardas integradas do StockSharp.
Parâmetros
Parâmetro
Descrição
Padrão
Grupo
LevelLength
Número de velas concluídas consideradas ao calcular o canal extremo.
5
Indicator
SignalShift
Índice da vela fechada usada para validação de rompimento (1 = última vela fechada).
1
Indicator
EnableLong
Permite comprar quando true.
true
Trading
EnableShort
Permite vender quando true.
true
Trading
ReverseSignals
Inverte reações de rompimento (comprar na queda, vender no rompimento).
false
Trading
OrderVolume
Volume enviado com cada ordem a mercado. Deve ser maior que zero.
1
Trading
StopLossPoints
Distância do stop-loss medida em passos de preço. Um valor de 0 desativa o stop.
0
Risk
TakeProfitPoints
Distância do take-profit medida em passos de preço. Um valor de 0 desativa o alvo.
0
Risk
CandleType
Período principal para assinatura de dados.
Velas de 5 minutos
Data
Todos os parâmetros usam StrategyParam<T> com metadados de UI para que possam ser otimizados ou modificados no Designer.
Diretrizes de uso
Anexar a estratégia a um instrumento e definir o Candle Type para corresponder ao período usado na configuração original do MetaTrader.
Ajustar Level Length se um canal de Preço Extremo mais largo ou mais estreito for desejado.
Configurar Signal Shift para controlar quantas velas fechadas aguardar antes de avaliar o rompimento.
Selecionar as direções de negociação desejadas via Enable Long, Enable Short e Reverse Signals.
Definir Order Volume, Stop Loss e Take Profit de acordo com as preferências de risco. Lembre-se que ambos os valores de proteção operam em passos de preço.
Iniciar a estratégia. Velas, bandas do indicador e negociações executadas são plotadas automaticamente quando uma área de gráfico está disponível.
Notas adicionais
A estratégia opera intencionalmente em uma única posição líquida, espelhando a lógica de hedge do especialista MQL ao nivelar o lado oposto antes de entrar em uma nova negociação.
Stops e alvos de proteção são avaliados em velas concluídas. No trading ao vivo, isso aproxima as ordens de proteção do lado do servidor usadas pelo script original.
Nenhuma versão em Python está incluída, conforme solicitado.
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Breakout strategy based on the Price Extreme indicator.
/// </summary>
public class PriceExtremeStrategy : Strategy
{
private readonly StrategyParam<int> _levelLength;
private readonly StrategyParam<int> _signalShift;
private readonly StrategyParam<bool> _enableLong;
private readonly StrategyParam<bool> _enableShort;
private readonly StrategyParam<bool> _reverseSignals;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private readonly List<ICandleMessage> _history = new();
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _prevPosition;
private decimal _entryPrice;
private decimal _prevUpper;
private decimal _prevLower;
/// <summary>
/// Number of candles used to build extreme levels.
/// </summary>
public int LevelLength
{
get => _levelLength.Value;
set => _levelLength.Value = value;
}
/// <summary>
/// Shift in candles used for the breakout signal.
/// </summary>
public int SignalShift
{
get => _signalShift.Value;
set => _signalShift.Value = value;
}
/// <summary>
/// Enable long trades.
/// </summary>
public bool EnableLong
{
get => _enableLong.Value;
set => _enableLong.Value = value;
}
/// <summary>
/// Enable short trades.
/// </summary>
public bool EnableShort
{
get => _enableShort.Value;
set => _enableShort.Value = value;
}
/// <summary>
/// Reverse long and short signals.
/// </summary>
public bool ReverseSignals
{
get => _reverseSignals.Value;
set => _reverseSignals.Value = value;
}
/// <summary>
/// Order volume in lots.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Type of candles used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="PriceExtremeStrategy"/>.
/// </summary>
public PriceExtremeStrategy()
{
_levelLength = Param(nameof(LevelLength), 20)
.SetGreaterThanZero()
.SetDisplay("Level Length", "Number of candles for price extremes", "Indicator")
.SetOptimize(3, 30, 1);
_signalShift = Param(nameof(SignalShift), 1)
.SetGreaterThanZero()
.SetDisplay("Signal Shift", "Closed candles used for breakout", "Indicator");
_enableLong = Param(nameof(EnableLong), true)
.SetDisplay("Enable Long", "Allow buying trades", "Trading");
_enableShort = Param(nameof(EnableShort), true)
.SetDisplay("Enable Short", "Allow selling trades", "Trading");
_reverseSignals = Param(nameof(ReverseSignals), false)
.SetDisplay("Reverse Signals", "Invert breakout direction", "Trading");
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume sent with market orders", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 0)
.SetDisplay("Stop Loss", "Protective stop in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 0)
.SetDisplay("Take Profit", "Profit target in price steps", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_history.Clear();
_highs.Clear();
_lows.Clear();
_prevUpper = 0m;
_prevLower = 0m;
_entryPrice = 0m;
_prevPosition = 0m;
ResetTargets();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(3, UnitTypes.Percent),
stopLoss: new Unit(2, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private bool CanOpenLong => EnableLong && OrderVolume > 0m;
private bool CanOpenShort => EnableShort && OrderVolume > 0m;
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
_history.Add(candle);
var maxHistory = Math.Max(LevelLength + SignalShift + 2, 10);
if (_history.Count > maxHistory)
{
var removeCount = _history.Count - maxHistory;
_history.RemoveRange(0, removeCount);
_highs.RemoveRange(0, removeCount);
_lows.RemoveRange(0, removeCount);
}
if (_highs.Count < LevelLength)
return;
var upper = decimal.MinValue;
var lower = decimal.MaxValue;
for (var i = _highs.Count - LevelLength; i < _highs.Count; i++)
{
if (_highs[i] > upper) upper = _highs[i];
if (_lows[i] < lower) lower = _lows[i];
}
if (_history.Count < SignalShift)
return;
var signalCandle = _history[_history.Count - SignalShift];
var breakoutUp = candle.ClosePrice > _prevUpper && _prevUpper > 0;
var breakoutDown = candle.ClosePrice < _prevLower && _prevLower > 0;
_prevUpper = upper;
_prevLower = lower;
var wantLong = ReverseSignals ? breakoutDown : breakoutUp;
var wantShort = ReverseSignals ? breakoutUp : breakoutDown;
if (wantLong && CanOpenLong && Position == 0)
{
BuyMarket();
}
else if (wantShort && CanOpenShort && Position == 0)
{
SellMarket();
}
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade?.Trade == null) return;
if (Position != 0 && _entryPrice == 0m)
_entryPrice = trade.Trade.Price;
if (Position == 0)
_entryPrice = 0m;
}
private void UpdateTargets()
{
_stopPrice = null;
_takePrice = null;
var step = Security?.PriceStep ?? 0m;
if (step <= 0m || Position == 0m)
return;
if (Position > 0m)
{
if (StopLossPoints > 0)
_stopPrice = _entryPrice - StopLossPoints * step;
if (TakeProfitPoints > 0)
_takePrice = _entryPrice + TakeProfitPoints * step;
}
else if (Position < 0m)
{
if (StopLossPoints > 0)
_stopPrice = _entryPrice + StopLossPoints * step;
if (TakeProfitPoints > 0)
_takePrice = _entryPrice - TakeProfitPoints * step;
}
}
private void ApplyRiskManagement(ICandleMessage candle)
{
if (Position > 0m)
{
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
return;
}
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
}
}
else if (Position < 0m)
{
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
return;
}
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
}
}
}
private void ResetTargets()
{
_stopPrice = null;
_takePrice = null;
_prevPosition = Position;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class price_extreme_strategy(Strategy):
def __init__(self):
super(price_extreme_strategy, self).__init__()
self._level_length = self.Param("LevelLength", 20)
self._signal_shift = self.Param("SignalShift", 1)
self._enable_long = self.Param("EnableLong", True)
self._enable_short = self.Param("EnableShort", True)
self._reverse_signals = self.Param("ReverseSignals", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def LevelLength(self):
return self._level_length.Value
@property
def SignalShift(self):
return self._signal_shift.Value
@property
def EnableLong(self):
return self._enable_long.Value
@property
def EnableShort(self):
return self._enable_short.Value
@property
def ReverseSignals(self):
return self._reverse_signals.Value
def OnStarted2(self, time):
super(price_extreme_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
self.StartProtection(
Unit(3, UnitTypes.Percent),
Unit(2, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
h = float(candle.HighPrice)
l = float(candle.LowPrice)
c = float(candle.ClosePrice)
self._highs.append(h)
self._lows.append(l)
self._history.append(c)
max_hist = max(self.LevelLength + self.SignalShift + 2, 10)
while len(self._history) > max_hist:
self._history.pop(0)
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < self.LevelLength:
return
upper = max(self._highs[-self.LevelLength:])
lower = min(self._lows[-self.LevelLength:])
if len(self._history) < self.SignalShift:
self._prev_upper = upper
self._prev_lower = lower
return
breakout_up = c > self._prev_upper and self._prev_upper > 0
breakout_down = c < self._prev_lower and self._prev_lower > 0
self._prev_upper = upper
self._prev_lower = lower
want_long = breakout_down if self.ReverseSignals else breakout_up
want_short = breakout_up if self.ReverseSignals else breakout_down
if want_long and self.EnableLong and self.Position == 0:
self.BuyMarket()
elif want_short and self.EnableShort and self.Position == 0:
self.SellMarket()
def OnReseted(self):
super(price_extreme_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
def CreateClone(self):
return price_extreme_strategy()