Estrategia de prueba rectangular
Descripción general
La estrategia de prueba de rectángulo reproduce el experto "RectangleTest" de MetaTrader utilizando el nivel alto de StockSharp API. Detecta rangos laterales en un marco de tiempo intradiario, verifica si dos promedios móviles y el precio actual permanecen dentro del rango detectado y luego intercambia rupturas fuera del rectángulo en la dirección del EMA más rápido. Toda la lógica se ejecuta en velas completadas recibidas de una fuente de velas configurable.
Lógica de trading
- Suscríbase al flujo de velas principal (predeterminado: período de tiempo de 1 hora) e introdúzcalo en los siguientes indicadores:
- ExponentialMovingAverage (EMA) con longitud configurable
EmaPeriod.
- SimpleMovingAverage (SMA) con longitud configurable
SmaPeriod.
- Indicadores más alto y más bajo con longitud
RangeCandles, configurados para leer máximos y mínimos de velas. Proporcionan los límites del rectángulo que emulan los cálculos basados en matrices MetaTrader.
- Una vez que se formen todos los indicadores, calcule la altura del rectángulo en porcentaje con respecto al límite superior. Sólo las velas cuya altura es menor que
RectangleSizePercent se consideran consolidaciones válidas.
- Requiere que EMA, SMA y la vela cercana permanezcan dentro del rectángulo. Esto reproduce el filtro lateral de la versión MQL.
- Configuración breve:
- EMA está por encima del SMA.
- El precio de cierre está por encima de EMA (que coincide con la condición "Preguntar > EMA" de MetaTrader en velas completadas).
- Primero se produce la liquidación opcional de una posición larga existente, después de lo cual se envía una orden de mercado corta.
- Configuración larga:
- EMA está debajo de SMA.
- El precio de cierre está por debajo de EMA (reflejando la regla "Oferta < EMA").
- Los cortos existentes se liquidan antes de abrir los largos.
- Cada entrada registra el precio de entrada esperado y el volumen. Cuando la posición llega a cero, la estrategia compara el precio de salida con el precio de entrada almacenado. Las operaciones perdedoras aumentan el contador de pérdidas diarias, aplicando el filtro
MaxLosingTradesPerDay exactamente igual que el MQL ayudante Loss().
Gestión de dinero y riesgos
- La estrategia puede funcionar de dos modos:
- Modo basado en riesgo (
UseRiskMoneyManagement = true): el volumen de la posición se dimensiona a partir del valor de la cuenta, el RiskPercent y el StopLossPoints configurado. El cálculo utiliza Security.PriceStep, Security.StepPrice y Security.VolumeStep para reflejar la rutina de dimensionamiento del lote MetaTrader.
- Modo de volumen fijo (
UseRiskMoneyManagement = false): las operaciones utilizan el parámetro FixedVolume.
- Después de que la posición neta cambia de estable a distinta de cero,
SetStopLoss y SetTakeProfit registran órdenes de protección usando StopLossPoints y TakeProfitPoints (expresadas en incrementos de precios), haciendo coincidir las distancias SL/TP pasadas a m_trade.Sell/Buy en el experto original.
MaxLosingTradesPerDay detiene nuevas señales durante el resto del día una vez que se ha detectado el número especificado de operaciones perdedoras.
Gestión del tiempo
- Solo se permite operar entre
TradeStartTime y TradeEndTime. El asistente maneja intervalos que abarcan la medianoche y sesiones diurnas.
- Cuando
EnableTimeClose es verdadero, todas las posiciones abiertas se liquidan después de TimeClose, replicando las entradas MetaTrader "TimeCloseTrue" y TimeClose.
- El indicador original creó rectángulos gráficos en el gráfico. StockSharp no crea objetos de dibujo; en cambio, el mismo rango se calcula internamente mediante indicadores más alto/más bajo.
- Las operaciones perdedoras se cuentan utilizando los precios de cierre de la vela de señal. Esto coincide con la intención de
Loss() (contar los pedidos perdidos por día) mientras se mantiene dentro de las abstracciones de alto nivel StockSharp.
- Las características de llenado de pedidos como
ORDER_FILLING_FOK/IOC son manejadas por el entorno de StockSharp, por lo que no se requiere una configuración explícita del modo de llenado.
Parámetros
| Nombre |
Predeterminado |
Descripción |
EmaPeriod |
45 |
Período de la EMA rápida. |
SmaPeriod |
200 |
Periodo de la lentitud SMA. |
RangeCandles |
10 |
Número de velas que forman el rectángulo. |
RectangleSizePercent |
0,5 |
Altura máxima del rectángulo permitida para el comercio. |
StopLossPoints |
250 |
Distancia de stop-loss en pasos de precio. |
TakeProfitPoints |
750 |
Distancia de obtención de beneficios en pasos de precio. |
UseRiskMoneyManagement |
cierto |
Alternar entre volumen fijo y basado en riesgo. |
RiskPercent |
1 |
Porcentaje del capital de la cuenta arriesgado por operación. |
FixedVolume |
1 |
Volumen fijo cuando el tamaño basado en riesgo está deshabilitado. |
MaxLosingTradesPerDay |
1 |
Límite diario de operaciones perdedoras. |
TradeStartTime |
03:00 |
Hora del día en que se permiten las entradas. |
TradeEndTime |
22:50 |
Hora del día a partir de la cual no se generan nuevas entradas. |
EnableTimeClose |
falso |
Permite la liquidación al final del día. |
TimeClose |
23:00 |
Hora del día para cerrar todas las posiciones. |
CandleType |
velas de 1 hora |
Fuente de datos de velas primaria. |
Trazar
Si hay un área de gráfico disponible, la estrategia dibuja las velas de precios, las operaciones rápidas EMA, lentas SMA y propias para visualizar rupturas de rango y el momento de las operaciones.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Rectangle breakout strategy: detects tight consolidation ranges and trades breakouts
/// using EMA/SMA trend direction as a filter.
/// </summary>
public class RectangleTestStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<int> _rangeCandles;
private readonly StrategyParam<decimal> _rectangleSizePercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
public int SmaPeriod
{
get => _smaPeriod.Value;
set => _smaPeriod.Value = value;
}
public int RangeCandles
{
get => _rangeCandles.Value;
set => _rangeCandles.Value = value;
}
public decimal RectangleSizePercent
{
get => _rectangleSizePercent.Value;
set => _rectangleSizePercent.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public RectangleTestStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetDisplay("Fast EMA Period", "Length of the fast EMA", "Indicators");
_smaPeriod = Param(nameof(SmaPeriod), 50)
.SetDisplay("Slow SMA Period", "Length of the slow SMA", "Indicators");
_rangeCandles = Param(nameof(RangeCandles), 10)
.SetDisplay("Rectangle Candles", "Number of candles for range detection", "Logic");
_rectangleSizePercent = Param(nameof(RectangleSizePercent), 10m)
.SetDisplay("Rectangle Size (%)", "Maximum range height in percent", "Logic");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary candle source", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var sma = new SimpleMovingAverage { Length = SmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, sma, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_highs.Count >= RangeCandles)
{
// Use PREVIOUS window (excluding current candle) for rectangle detection
var highestValue = decimal.MinValue;
var lowestValue = decimal.MaxValue;
var startIdx = _highs.Count - RangeCandles;
for (var i = startIdx; i < _highs.Count; i++)
{
if (_highs[i] > highestValue) highestValue = _highs[i];
if (_lows[i] < lowestValue) lowestValue = _lows[i];
}
if (highestValue > 0m && lowestValue > 0m)
{
var rangePercent = (highestValue - lowestValue) / highestValue * 100m;
if (rangePercent > 0 && rangePercent < RectangleSizePercent)
{
var close = candle.ClosePrice;
// Breakout above rectangle with bullish trend (EMA > SMA)
if (close > highestValue && emaValue > smaValue && Position == 0)
{
BuyMarket();
}
// Breakout below rectangle with bearish trend (EMA < SMA)
else if (close < lowestValue && emaValue < smaValue && Position == 0)
{
SellMarket();
}
}
}
}
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > RangeCandles + 1)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
}
/// <inheritdoc />
protected override void OnReseted()
{
_highs.Clear();
_lows.Clear();
base.OnReseted();
}
}
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.Indicators import ExponentialMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class rectangle_test_strategy(Strategy):
def __init__(self):
super(rectangle_test_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._ema_period = self.Param("EmaPeriod", 20)
self._sma_period = self.Param("SmaPeriod", 50)
self._range_candles = self.Param("RangeCandles", 10)
self._rectangle_size_percent = self.Param("RectangleSizePercent", 10.0)
self._highs = []
self._lows = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def SmaPeriod(self):
return self._sma_period.Value
@SmaPeriod.setter
def SmaPeriod(self, value):
self._sma_period.Value = value
@property
def RangeCandles(self):
return self._range_candles.Value
@RangeCandles.setter
def RangeCandles(self, value):
self._range_candles.Value = value
@property
def RectangleSizePercent(self):
return self._rectangle_size_percent.Value
@RectangleSizePercent.setter
def RectangleSizePercent(self, value):
self._rectangle_size_percent.Value = value
def OnReseted(self):
super(rectangle_test_strategy, self).OnReseted()
self._highs = []
self._lows = []
def OnStarted2(self, time):
super(rectangle_test_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
sma = SimpleMovingAverage()
sma.Length = self.SmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, sma, self._process_candle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent))
def _process_candle(self, candle, ema_value, sma_value):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_value)
sma_val = float(sma_value)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
range_candles = self.RangeCandles
if len(self._highs) >= range_candles:
start_idx = len(self._highs) - range_candles
highest = max(self._highs[start_idx:])
lowest = min(self._lows[start_idx:])
if highest > 0 and lowest > 0:
range_pct = (highest - lowest) / highest * 100.0
rect_size = float(self.RectangleSizePercent)
if 0 < range_pct < rect_size:
# Breakout above rectangle with bullish trend
if close > highest and ema_val > sma_val and self.Position == 0:
self.BuyMarket()
# Breakout below rectangle with bearish trend
elif close < lowest and ema_val < sma_val and self.Position == 0:
self.SellMarket()
self._highs.append(high)
self._lows.append(low)
while len(self._highs) > range_candles + 1:
self._highs.pop(0)
self._lows.pop(0)
def CreateClone(self):
return rectangle_test_strategy()