Raymond Cloudy Day is a breakout-following strategy that reconstructs the trading logic of the original "Raymond Cloudy Day for EA" MQL5 expert advisor. The algorithm derives a set of reference levels from a higher timeframe candle and uses them to detect momentum resumption on the execution timeframe. The StockSharp port keeps the original trading rules while exposing each component as configurable strategy parameters.
Market Data
Signal candles – the timeframe on which trades are executed. The strategy subscribes to this series for entry signals and position management.
Pivot candles – the higher timeframe used to compute Raymond levels. By default this is a daily candle, reproducing the MQL5 input RayMondTimeframe.
Both subscriptions are automatically registered through GetWorkingSecurities, so the strategy requests the required data streams as soon as it is started.
Raymond Level Calculation
For every finished pivot candle the strategy stores the four core levels defined by the original EA:
The StockSharp implementation maintains the most recent snapshot of these values and logs every update, allowing the user to monitor how levels evolve over time.
Entry Logic
Once the Raymond levels are available, the strategy evaluates each finished signal candle:
Long setup – If the candle’s low dips below TPS1 and the close returns above the level, the strategy enters a long position. This mirrors the EA condition Low[1] < TPS1 && Close[1] > TPS1 and captures bullish rejection of the level.
Short setup – If the candle remains fully above TPS1 but closes below it, the strategy opens a short position (matching the original albeit asymmetric rule).
Before placing a new order the algorithm cancels any outstanding orders and, if necessary, closes the opposite position so that only one directional trade remains active.
Risk Management
Raymond Cloudy Day uses symmetric protective offsets measured in ticks:
Stop-loss – positioned ProtectiveOffsetTicks below the long entry (or above the short entry).
Take-profit – positioned ProtectiveOffsetTicks above the long entry (or below the short entry).
The offsets are multiplied by the instrument’s PriceStep to convert ticks into absolute price distances. Each completed signal candle triggers a check that closes the position when either protective level is hit. When the strategy is flat the internal protection state is reset to avoid stale levels.
Parameters
Name
Description
Default
Notes
TradeVolume
Order volume used for every entry.
1
Synchronized with the Volume property on start.
ProtectiveOffsetTicks
Distance in ticks for both stop-loss and take-profit.
500
Multiplied by PriceStep to obtain absolute prices.
SignalCandleType
Candle type that produces trade signals.
1 hour time frame
May be set to any DataType representing candles.
PivotCandleType
Higher timeframe for Raymond level calculations.
1 day time frame
Matches the RayMondTimeframe input from the MQL EA.
All parameters support optimization ranges and descriptive metadata for StockSharp Designer.
Additional Notes
The strategy requires PriceStep to be defined by the connected security. If it is missing, trade entries are skipped and a warning is logged.
Chart visualization adds the execution candles together with executed trades. Additional custom drawing can be added if desired.
The implementation avoids direct indicator value polling and processes only finished candles, adhering to the project guidelines in AGENTS.md.
Original EA Specifics Preserved
Raymond level formulas and multipliers (0.382, 0.618, 1.0).
Entry logic based on the first sell take-profit (TPS1).
Symmetric 500-point stop-loss and take-profit offsets converted to ticks in the StockSharp environment.
With these components the StockSharp strategy behaves identically to the source EA while providing rich configuration and logging suitable for further research and automation.
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()