La Estrategia de Precio Extremo replica el asesor experto de MetaTrader Price_Extreme_Strategy mediante la API de alto nivel de StockSharp. El sistema monitorea un canal deslizante derivado del máximo más alto y el mínimo más bajo durante un número configurable de velas completadas. Se generan señales de ruptura cuando la vela de referencia seleccionada cierra por encima del límite superior o por debajo del límite inferior. La lógica puede invertirse opcionalmente para transformar las condiciones de ruptura en entradas de contra-tendencia.
Esta conversión mantiene el flujo de trabajo de trading orientado a eventos. Las órdenes se envían inmediatamente después del cierre de cada vela finalizada, replicando el comportamiento del algoritmo MQL original que reaccionaba en el tick de apertura de la siguiente barra.
Lógica del indicador
El canal de Precio Extremo se reconstruye en cada vela finalizada usando los indicadores Highest y Lowest de StockSharp:
Highest rastrea el máximo de los altos durante las últimas N velas.
Lowest rastrea el mínimo de los bajos durante las últimas N velas.
Estos búferes emulan el estudio personalizado Price_Extreme_Indicator incluido con el asesor experto original. La longitud del indicador se expone a través del parámetro Level Length.
Un parámetro separado Signal Shift define qué vela cerrada se usa para evaluar la condición de ruptura. Un shift de 1 significa "usar la vela que acaba de cerrar" (por defecto). Valores mayores permiten esperar confirmación adicional haciendo referencia a barras más antiguas.
Reglas de trading
Recalcular los valores del canal superior e inferior para cada vela finalizada.
Recuperar la vela especificada por Signal Shift del búfer de historial interno.
Generar intenciones direccionales:
Ruptura alcista: el cierre de la vela está por encima del valor del canal superior.
Ruptura bajista: el cierre de la vela está por debajo del valor del canal inferior.
Aplicar inversión opcional con Reverse Signals:
Si está desactivado, operar en la dirección de la ruptura (comprar en ruptura alcista, vender en ruptura bajista).
Si está activado, intercambiar las reacciones (vender en ruptura alcista, comprar en ruptura bajista).
Respetar los permisos Enable Long y Enable Short antes de enviar órdenes.
Cerrar automáticamente cualquier posición opuesta antes de abrir una nueva operación para que solo exista una posición neta en todo momento.
Gestión de riesgos
La estrategia proporciona manejo de stop-loss y take-profit que replica los controles basados en puntos de la versión MQL:
Stop Loss y Take Profit se expresan en pasos de precio (Security.PriceStep).
Los precios objetivo se recalculan cuando cambia el tamaño de la posición neta.
Si una vela finalizada supera los niveles de protección (mínimo por debajo del stop para largos, máximo por encima del stop para cortos, etc.), la posición se cierra mediante orden de mercado y los objetivos de protección se borran.
StartProtection() se activa durante OnStarted para aprovechar las salvaguardas integradas de StockSharp.
Parámetros
Parámetro
Descripción
Predeterminado
Grupo
LevelLength
Número de velas completadas consideradas al calcular el canal extremo.
5
Indicator
SignalShift
Índice de la vela cerrada usada para la validación de ruptura (1 = última vela cerrada).
1
Indicator
EnableLong
Permite comprar cuando es true.
true
Trading
EnableShort
Permite vender cuando es true.
true
Trading
ReverseSignals
Invierte las reacciones de ruptura (comprar en bajada, vender en subida).
false
Trading
OrderVolume
Volumen enviado con cada orden de mercado. Debe ser mayor que cero.
1
Trading
StopLossPoints
Distancia del stop-loss medida en pasos de precio. Un valor de 0 desactiva el stop.
0
Risk
TakeProfitPoints
Distancia del take-profit medida en pasos de precio. Un valor de 0 desactiva el objetivo.
0
Risk
CandleType
Marco temporal principal para la suscripción de datos.
Velas de 5 minutos
Data
Todos los parámetros usan StrategyParam<T> con metadatos de UI para que puedan optimizarse o modificarse desde el Designer.
Guía de uso
Adjuntar la estrategia a un instrumento y establecer el Candle Type para que coincida con el marco temporal usado en la configuración original de MetaTrader.
Ajustar Level Length si se desea un canal de Precio Extremo más amplio o más estrecho.
Configurar Signal Shift para controlar cuántas velas cerradas esperar antes de evaluar la ruptura.
Seleccionar las direcciones de operación deseadas mediante Enable Long, Enable Short y Reverse Signals.
Definir Order Volume, Stop Loss y Take Profit según las preferencias de riesgo. Recuerde que ambos valores de protección operan en pasos de precio.
Iniciar la estrategia. Las velas, las bandas del indicador y las operaciones ejecutadas se grafican automáticamente cuando hay un área de gráfico disponible.
Notas adicionales
La estrategia opera intencionalmente sobre una sola posición neta, replicando la lógica de cobertura del experto MQL al aplanar el lado opuesto antes de entrar en una nueva operación.
Los stops y objetivos de protección se evalúan en velas completadas. En trading en vivo, esto aproxima las órdenes de protección del lado del servidor usadas por el script original.
No se incluye versión en Python, según lo 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()