Raymond Cloudy Day é uma estratégia de acompanhamento de breakout que reconstrói a lógica de negociação do consultor especialista "Raymond Cloudy Day for EA" MQL5 original. O algoritmo deriva um conjunto de níveis de referência de uma vela de prazo mais alto e os utiliza para detectar a retomada do impulso no prazo de execução. A porta StockSharp mantém as regras de negociação originais enquanto expõe cada componente como parâmetros de estratégia configuráveis.
Dados de mercado
Velas de sinal – o período em que as negociações são executadas. A estratégia subscreve esta série para sinais de entrada e gestão de posições.
Velas dinâmicas – o período de tempo mais alto usado para calcular os níveis de Raymond. Por padrão, esta é uma vela diária, reproduzindo a entrada MQL5 RayMondTimeframe.
Ambas as assinaturas são registradas automaticamente por meio de GetWorkingSecurities, portanto, a estratégia solicita os fluxos de dados necessários assim que é iniciada.
Cálculo do nível Raymond
Para cada vela pivô finalizada, a estratégia armazena os quatro níveis principais definidos pelo EA original:
A implementação StockSharp mantém o instantâneo mais recente desses valores e registra cada atualização, permitindo ao usuário monitorar como os níveis evoluem ao longo do tempo.
Lógica de entrada
Assim que os níveis de Raymond estiverem disponíveis, a estratégia avalia cada vela de sinalização finalizada:
Configuração longa – Se a mínima da vela cair abaixo de TPS1 e o fechamento retornar acima do nível, a estratégia entra em uma posição longa. Isso reflete a condição EA Low[1] < TPS1 && Close[1] > TPS1 e captura a rejeição de alta do nível.
Configuração curta – Se a vela permanecer totalmente acima de TPS1, mas fechar abaixo dela, a estratégia abre uma posição curta (correspondendo à regra original, embora assimétrica).
Antes de colocar uma nova ordem, o algoritmo cancela quaisquer ordens pendentes e, se necessário, fecha a posição oposta para que apenas uma negociação direcional permaneça ativa.
Gestão de risco
Raymond Cloudy Day usa compensações de proteção simétricas medidas em ticks:
Stop-loss – posicionado ProtectiveOffsetTicks abaixo da entrada longa (ou acima da entrada curta).
Take-profit – posicionado ProtectiveOffsetTicks acima da entrada longa (ou abaixo da entrada curta).
As compensações são multiplicadas pelo PriceStep do instrumento para converter ticks em distâncias de preço absolutas. Cada vela de sinal concluída aciona uma verificação que fecha a posição quando qualquer nível de proteção é atingido. Quando a estratégia é plana, o estado de proteção interna é redefinido para evitar níveis obsoletos.
Parâmetros
Nome
Descrição
Padrão
Notas
TradeVolume
Volume de pedidos usado para cada entrada.
1
Sincronizado com a propriedade Volume na inicialização.
ProtectiveOffsetTicks
Distância em ticks para stop-loss e take-profit.
500
Multiplicado por PriceStep para obter preços absolutos.
SignalCandleType
Tipo de vela que produz sinais de negociação.
1 hour período de tempo
Pode ser definido como qualquer DataType representando velas.
PivotCandleType
Prazo maior para cálculos de nível Raymond.
1 day período de tempo
Corresponde à entrada RayMondTimeframe do MQL EA.
Todos os parâmetros suportam intervalos de otimização e metadados descritivos para o StockSharp Designer.
Notas adicionais
A estratégia requer que PriceStep seja definido pela segurança conectada. Se estiver faltando, as entradas comerciais serão ignoradas e um aviso será registrado.
A visualização do gráfico adiciona as velas de execução às negociações executadas. Desenho personalizado adicional pode ser adicionado, se desejado.
A implementação evita a pesquisa direta de valor do indicador e processa apenas velas concluídas, aderindo às diretrizes do projeto em AGENTS.md.
Especificações originais do EA preservadas
Fórmulas e multiplicadores de nível Raymond (0.382, 0.618, 1.0).
Lógica de entrada baseada no take-profit da primeira venda (TPS1).
Compensações simétricas de stop-loss e take-profit de 500 pontos convertidas em ticks no ambiente StockSharp.
Com esses componentes, a estratégia StockSharp se comporta de forma idêntica à fonte EA, ao mesmo tempo que fornece configuração avançada e registro adequado para pesquisas e automação adicionais.
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()