Raymond Cloudy Day es una estrategia de seguimiento de rupturas que reconstruye la lógica comercial del asesor experto original "Raymond Cloudy Day for EA" MQL5. El algoritmo deriva un conjunto de niveles de referencia de una vela de marco temporal más alto y los utiliza para detectar la reanudación del impulso en el marco temporal de ejecución. El puerto StockSharp mantiene las reglas comerciales originales al tiempo que expone cada componente como parámetros de estrategia configurables.
Datos de mercado
Velas de señal: el período de tiempo en el que se ejecutan las operaciones. La estrategia se suscribe a esta serie para señales de entrada y gestión de posiciones.
Velas pivotantes: el período de tiempo más alto utilizado para calcular los niveles de Raymond. Por defecto, esta es una vela diaria, que reproduce la entrada MQL5 RayMondTimeframe.
Ambas suscripciones se registran automáticamente a través de GetWorkingSecurities, por lo que la estrategia solicita los flujos de datos necesarios tan pronto como se inicia.
Cálculo del nivel de Raymond
Para cada vela pivote terminada, la estrategia almacena los cuatro niveles centrales definidos por el EA original:
La implementación StockSharp mantiene la instantánea más reciente de estos valores y registra cada actualización, lo que permite al usuario monitorear cómo evolucionan los niveles con el tiempo.
Lógica de entrada
Una vez que los niveles de Raymond están disponibles, la estrategia evalúa cada vela de señal terminada:
Configuración larga: si el mínimo de la vela cae por debajo de TPS1 y el cierre regresa por encima del nivel, la estrategia ingresa en una posición larga. Esto refleja la condición EA Low[1] < TPS1 && Close[1] > TPS1 y captura el rechazo alcista del nivel.
Configuración corta: si la vela permanece completamente por encima de TPS1 pero cierra por debajo, la estrategia abre una posición corta (que coincide con la regla original, aunque asimétrica).
Antes de realizar una nueva orden, el algoritmo cancela las órdenes pendientes y, si es necesario, cierra la posición opuesta para que solo quede activa una operación direccional.
Gestión del riesgo
Raymond Cloudy Day utiliza compensaciones protectoras simétricas medidas en garrapatas:
Stop-loss – posicionado ProtectiveOffsetTicks debajo de la entrada larga (o encima de la entrada corta).
Take-profit – posicionado ProtectiveOffsetTicks encima de la entrada larga (o debajo de la entrada corta).
Las compensaciones se multiplican por el PriceStep del instrumento para convertir ticks en distancias de precios absolutas. Cada vela de señal completada activa una verificación que cierra la posición cuando se alcanza cualquiera de los niveles de protección. Cuando la estrategia es plana, el estado de protección interna se restablece para evitar niveles obsoletos.
Parámetros
Nombre
Descripción
Predeterminado
Notas
TradeVolume
Volumen de pedidos utilizado para cada entrada.
1
Sincronizado con la propiedad Volume al inicio.
ProtectiveOffsetTicks
Distancia en ticks tanto para stop-loss como para take-profit.
500
Multiplicado por PriceStep para obtener precios absolutos.
SignalCandleType
Tipo de vela que produce señales comerciales.
1 hour período de tiempo
Se puede establecer en cualquier DataType que represente velas.
PivotCandleType
Mayor plazo para los cálculos del nivel de Raymond.
1 day período de tiempo
Coincide con la entrada RayMondTimeframe del MQL EA.
Todos los parámetros admiten rangos de optimización y metadatos descriptivos para StockSharp Designer.
Notas adicionales
La estrategia requiere que PriceStep esté definido por la seguridad conectada. Si falta, se omiten las entradas comerciales y se registra una advertencia.
La visualización del gráfico suma las velas de ejecución junto con las operaciones ejecutadas. Se pueden agregar dibujos personalizados adicionales si lo desea.
La implementación evita el sondeo directo del valor del indicador y procesa solo velas terminadas, cumpliendo con las pautas del proyecto en AGENTS.md.
Detalles específicos originales del EA conservados
Fórmulas y multiplicadores de nivel Raymond (0.382, 0.618, 1.0).
Lógica de entrada basada en la obtención de beneficios de la primera venta (TPS1).
Compensaciones simétricas de stop-loss y take-profit de 500 puntos convertidas en ticks en el entorno StockSharp.
Con estos componentes, la estrategia StockSharp se comporta de manera idéntica a la fuente EA y al mismo tiempo proporciona una configuración rica y un registro adecuado para futuras investigaciones y automatización.
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>
/// Raymond Cloudy Day strategy.
/// Computes Raymond levels from a higher timeframe and trades pullbacks around the first sell take-profit level.
/// </summary>
public class RaymondCloudyDayStrategy : Strategy
{
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<int> _protectiveOffsetTicks;
private readonly StrategyParam<DataType> _signalCandleType;
private readonly StrategyParam<DataType> _pivotCandleType;
private decimal? _tradeSessionLevel;
private decimal? _extendedBuyLevel;
private decimal? _extendedSellLevel;
private decimal? _takeProfitBuyLevel;
private decimal? _takeProfitSellLevel;
private decimal? _takeProfitBuyLevel2;
private decimal? _takeProfitSellLevel2;
private decimal? _entryPrice;
private decimal? _takePrice;
private decimal? _stopPrice;
/// <summary>
/// Trade volume used for new positions.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Distance in ticks used to build stop-loss and take-profit levels around the entry price.
/// </summary>
public int ProtectiveOffsetTicks
{
get => _protectiveOffsetTicks.Value;
set => _protectiveOffsetTicks.Value = value;
}
/// <summary>
/// Candle type that triggers trade signals.
/// </summary>
public DataType SignalCandleType
{
get => _signalCandleType.Value;
set => _signalCandleType.Value = value;
}
/// <summary>
/// Higher timeframe candle type used to compute Raymond levels.
/// </summary>
public DataType PivotCandleType
{
get => _pivotCandleType.Value;
set => _pivotCandleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public RaymondCloudyDayStrategy()
{
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order volume used for entries", "Trading")
.SetOptimize(0.1m, 5m, 0.1m);
_protectiveOffsetTicks = Param(nameof(ProtectiveOffsetTicks), 500)
.SetGreaterThanZero()
.SetDisplay("Protective Offset (ticks)", "Distance in ticks for stop-loss and take-profit", "Risk Management")
.SetOptimize(50, 1000, 50);
_signalCandleType = Param(nameof(SignalCandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Signal Candle Type", "Candle type used for trade signals", "Data");
_pivotCandleType = Param(nameof(PivotCandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Pivot Candle Type", "Higher timeframe used to compute Raymond levels", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, SignalCandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_tradeSessionLevel = null;
_extendedBuyLevel = null;
_extendedSellLevel = null;
_takeProfitBuyLevel = null;
_takeProfitSellLevel = null;
_takeProfitBuyLevel2 = null;
_takeProfitSellLevel2 = null;
ResetProtection();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = TradeVolume;
var signalSubscription = SubscribeCandles(SignalCandleType);
signalSubscription
.Bind(ProcessBothCandle)
.Start();
var priceArea = CreateChartArea();
if (priceArea != null)
{
DrawCandles(priceArea, signalSubscription);
DrawOwnTrades(priceArea);
}
}
private void ProcessBothCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
ProcessPivotCandle(candle);
ProcessSignalCandle(candle);
}
private void ProcessPivotCandle(ICandleMessage candle)
{
// Skip unfinished candles to keep the level calculation consistent.
if (candle.State != CandleStates.Finished)
return;
var high = candle.HighPrice;
var low = candle.LowPrice;
var open = candle.OpenPrice;
var close = candle.ClosePrice;
var tradeSession = (high + low + open + close) / 4m;
var pivotRange = high - low;
_tradeSessionLevel = tradeSession;
_extendedBuyLevel = tradeSession + 0.382m * pivotRange;
_extendedSellLevel = tradeSession - 0.382m * pivotRange;
_takeProfitBuyLevel = tradeSession + 0.618m * pivotRange;
_takeProfitSellLevel = tradeSession - 0.618m * pivotRange;
_takeProfitBuyLevel2 = tradeSession + pivotRange;
_takeProfitSellLevel2 = tradeSession - pivotRange;
LogInfo($"Updated Raymond levels from {candle.OpenTime:u}. TradeSS={tradeSession}, ETB={_extendedBuyLevel}, ETS={_extendedSellLevel}, TPB1={_takeProfitBuyLevel}, TPS1={_takeProfitSellLevel}.");
}
private void ProcessSignalCandle(ICandleMessage candle)
{
// Manage exits first so protective logic reacts even when trading is disabled.
if (candle.State != CandleStates.Finished)
return;
ManageOpenPosition(candle);
//if (!IsFormedAndOnlineAndAllowTrading())
// return;
if (_takeProfitSellLevel is not decimal triggerLevel)
return;
var low = candle.LowPrice;
var close = candle.ClosePrice;
// Replicate the original EA condition around the TPS1 level.
if (Position <= 0 && low < triggerLevel && close > triggerLevel)
{
EnterLong(close);
}
else if (Position >= 0 && low > triggerLevel && close < triggerLevel)
{
EnterShort(close);
}
}
private void EnterLong(decimal closePrice)
{
var priceStep = Security?.PriceStep ?? 1m;
if (priceStep <= 0m)
priceStep = 1m;
CancelActiveOrders();
var volume = TradeVolume + Math.Max(0m, -Position);
BuyMarket(volume);
var offset = priceStep * ProtectiveOffsetTicks;
_entryPrice = closePrice;
_takePrice = closePrice + offset;
_stopPrice = closePrice - offset;
LogInfo($"Opened long position at {closePrice}. TP={_takePrice}, SL={_stopPrice}.");
}
private void EnterShort(decimal closePrice)
{
var priceStep = Security?.PriceStep ?? 1m;
if (priceStep <= 0m)
priceStep = 1m;
CancelActiveOrders();
var volume = TradeVolume + Math.Max(0m, Position);
SellMarket(volume);
var offset = priceStep * ProtectiveOffsetTicks;
_entryPrice = closePrice;
_takePrice = closePrice - offset;
_stopPrice = closePrice + offset;
LogInfo($"Opened short position at {closePrice}. TP={_takePrice}, SL={_stopPrice}.");
}
private void ManageOpenPosition(ICandleMessage candle)
{
if (Position == 0)
{
ResetProtection();
return;
}
if (_entryPrice is not decimal entry || _takePrice is not decimal take || _stopPrice is not decimal stop)
return;
if (Position > 0)
{
// Close the long position if price breaches the protective levels.
if (candle.LowPrice <= stop)
{
SellMarket(Position);
ResetProtection();
LogInfo($"Long stop-loss triggered at {stop}.");
return;
}
if (candle.HighPrice >= take)
{
SellMarket(Position);
ResetProtection();
LogInfo($"Long take-profit triggered at {take}.");
return;
}
}
else
{
var volume = Math.Abs(Position);
// Close the short position when stop or take-profit is hit.
if (candle.HighPrice >= stop)
{
BuyMarket(volume);
ResetProtection();
LogInfo($"Short stop-loss triggered at {stop}.");
return;
}
if (candle.LowPrice <= take)
{
BuyMarket(volume);
ResetProtection();
LogInfo($"Short take-profit triggered at {take}.");
}
}
}
private void ResetProtection()
{
_entryPrice = null;
_takePrice = null;
_stopPrice = null;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Math, TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class raymond_cloudy_day_strategy(Strategy):
def __init__(self):
super(raymond_cloudy_day_strategy, self).__init__()
self._trade_volume = self.Param("TradeVolume", 1.0)
self._protective_offset_ticks = self.Param("ProtectiveOffsetTicks", 500)
self._signal_candle_type = self.Param("SignalCandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._pivot_candle_type = self.Param("PivotCandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._trade_session_level = None
self._extended_buy_level = None
self._extended_sell_level = None
self._take_profit_buy_level = None
self._take_profit_sell_level = None
self._take_profit_buy_level2 = None
self._take_profit_sell_level2 = None
self._entry_price = None
self._take_price = None
self._stop_price = None
@property
def TradeVolume(self):
return self._trade_volume.Value
@TradeVolume.setter
def TradeVolume(self, value):
self._trade_volume.Value = value
@property
def ProtectiveOffsetTicks(self):
return self._protective_offset_ticks.Value
@ProtectiveOffsetTicks.setter
def ProtectiveOffsetTicks(self, value):
self._protective_offset_ticks.Value = value
@property
def SignalCandleType(self):
return self._signal_candle_type.Value
@SignalCandleType.setter
def SignalCandleType(self, value):
self._signal_candle_type.Value = value
@property
def PivotCandleType(self):
return self._pivot_candle_type.Value
@PivotCandleType.setter
def PivotCandleType(self, value):
self._pivot_candle_type.Value = value
def OnReseted(self):
super(raymond_cloudy_day_strategy, self).OnReseted()
self._trade_session_level = None
self._extended_buy_level = None
self._extended_sell_level = None
self._take_profit_buy_level = None
self._take_profit_sell_level = None
self._take_profit_buy_level2 = None
self._take_profit_sell_level2 = None
self._reset_protection()
def _reset_protection(self):
self._entry_price = None
self._take_price = None
self._stop_price = None
def OnStarted2(self, time):
super(raymond_cloudy_day_strategy, self).OnStarted2(time)
self.Volume = float(self.TradeVolume)
subscription = self.SubscribeCandles(self.SignalCandleType)
subscription.Bind(self._process_both_candle).Start()
def _process_both_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._process_pivot_candle(candle)
self._process_signal_candle(candle)
def _process_pivot_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
open_p = float(candle.OpenPrice)
close = float(candle.ClosePrice)
trade_session = (high + low + open_p + close) / 4.0
pivot_range = high - low
self._trade_session_level = trade_session
self._extended_buy_level = trade_session + 0.382 * pivot_range
self._extended_sell_level = trade_session - 0.382 * pivot_range
self._take_profit_buy_level = trade_session + 0.618 * pivot_range
self._take_profit_sell_level = trade_session - 0.618 * pivot_range
self._take_profit_buy_level2 = trade_session + pivot_range
self._take_profit_sell_level2 = trade_session - pivot_range
def _process_signal_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._manage_open_position(candle)
if self._take_profit_sell_level is None:
return
trigger_level = self._take_profit_sell_level
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self.Position <= 0 and low < trigger_level and close > trigger_level:
self._enter_long(close)
elif self.Position >= 0 and low > trigger_level and close < trigger_level:
self._enter_short(close)
def _enter_long(self, close_price):
price_step = 1.0
if self.Security is not None and float(self.Security.PriceStep or 0) > 0:
price_step = float(self.Security.PriceStep)
volume = float(self.TradeVolume) + max(0.0, -float(self.Position))
self.BuyMarket(volume)
offset = price_step * float(self.ProtectiveOffsetTicks)
self._entry_price = close_price
self._take_price = close_price + offset
self._stop_price = close_price - offset
def _enter_short(self, close_price):
price_step = 1.0
if self.Security is not None and float(self.Security.PriceStep or 0) > 0:
price_step = float(self.Security.PriceStep)
volume = float(self.TradeVolume) + max(0.0, float(self.Position))
self.SellMarket(volume)
offset = price_step * float(self.ProtectiveOffsetTicks)
self._entry_price = close_price
self._take_price = close_price - offset
self._stop_price = close_price + offset
def _manage_open_position(self, candle):
if self.Position == 0:
self._reset_protection()
return
if self._entry_price is None or self._take_price is None or self._stop_price is None:
return
entry = self._entry_price
take = self._take_price
stop = self._stop_price
if self.Position > 0:
if float(candle.LowPrice) <= stop:
self.SellMarket(float(self.Position))
self._reset_protection()
return
if float(candle.HighPrice) >= take:
self.SellMarket(float(self.Position))
self._reset_protection()
return
else:
volume = abs(float(self.Position))
if float(candle.HighPrice) >= stop:
self.BuyMarket(volume)
self._reset_protection()
return
if float(candle.LowPrice) <= take:
self.BuyMarket(volume)
self._reset_protection()
def CreateClone(self):
return raymond_cloudy_day_strategy()