Raymond Cloudy Day ist eine Ausbruchsverfolgungsstrategie, die die Handelslogik des ursprünglichen Expertenberaters „Raymond Cloudy Day für EA“ MQL5 rekonstruiert. Der Algorithmus leitet eine Reihe von Referenzniveaus von einer Kerze mit einem höheren Zeitrahmen ab und verwendet sie, um die Wiederaufnahme der Dynamik im Ausführungszeitrahmen zu erkennen. Der StockSharp-Port behält die ursprünglichen Handelsregeln bei und stellt jede Komponente als konfigurierbare Strategieparameter bereit.
Marktdaten
Signalkerzen – der Zeitrahmen, in dem Trades ausgeführt werden. Die Strategie abonniert diese Reihe für Einstiegssignale und Positionsmanagement.
Pivot-Kerzen – der höhere Zeitrahmen, der zur Berechnung der Raymond-Levels verwendet wird. Standardmäßig ist dies eine tägliche Kerze, die die MQL5-Eingabe RayMondTimeframe reproduziert.
Beide Abonnements werden automatisch über GetWorkingSecurities registriert, sodass die Strategie die erforderlichen Datenströme anfordert, sobald sie gestartet wird.
Raymond-Level-Berechnung
Für jede fertige Pivot-Kerze speichert die Strategie die vier Kernebenen, die durch die ursprüngliche EA definiert sind:
Die StockSharp-Implementierung verwaltet den aktuellsten Snapshot dieser Werte und protokolliert jede Aktualisierung, sodass der Benutzer überwachen kann, wie sich die Werte im Laufe der Zeit entwickeln.
Eingabelogik
Sobald die Raymond-Level verfügbar sind, wertet die Strategie jede fertige Signalkerze aus:
Long-Setup – Wenn das Tief der Kerze unter TPS1 fällt und der Schlusskurs über das Niveau zurückkehrt, geht die Strategie eine Long-Position ein. Dies spiegelt die EA-Bedingung Low[1] < TPS1 && Close[1] > TPS1 wider und erfasst die bullische Ablehnung des Niveaus.
Short-Setup – Wenn die Kerze vollständig über TPS1 bleibt, aber darunter schließt, eröffnet die Strategie eine Short-Position (entspricht der ursprünglichen, wenn auch asymmetrischen Regel).
Vor der Platzierung einer neuen Order storniert der Algorithmus alle ausstehenden Orders und schließt gegebenenfalls die Gegenposition, sodass nur noch ein Richtungsgeschäft aktiv bleibt.
Risikomanagement
Raymond Cloudy Day verwendet symmetrische Schutzversätze, gemessen in Ticks:
Stop-Loss – positioniert ProtectiveOffsetTicks unter dem Long-Einstieg (oder über dem Short-Einstieg).
Take-Profit – positioniert ProtectiveOffsetTicks über dem Long-Einstieg (oder unter dem Short-Einstieg).
Die Offsets werden mit dem PriceStep des Instruments multipliziert, um Ticks in absolute Preisabstände umzuwandeln. Jede abgeschlossene Signalkerze löst eine Prüfung aus, die die Position schließt, wenn eines der Schutzniveaus erreicht wird. Wenn die Strategie flach ist, wird der interne Schutzstatus zurückgesetzt, um veraltete Ebenen zu vermeiden.
Parameter
Name
Beschreibung
Standard
Notizen
TradeVolume
Bestellvolumen, das für jeden Eintrag verwendet wird.
1
Beim Start mit der Eigenschaft Volume synchronisiert.
ProtectiveOffsetTicks
Abstand in Ticks für Stop-Loss und Take-Profit.
500
Mit PriceStep multipliziert, um absolute Preise zu erhalten.
SignalCandleType
Kerzentyp, der Handelssignale erzeugt.
1 hour Zeitrahmen
Kann auf einen beliebigen DataType gesetzt werden, der Kerzen darstellt.
PivotCandleType
Längerer Zeitrahmen für Raymond-Level-Berechnungen.
1 day Zeitrahmen
Entspricht der RayMondTimeframe-Eingabe von MQL EA.
Alle Parameter unterstützen Optimierungsbereiche und beschreibende Metadaten für StockSharp Designer.
Zusätzliche Hinweise
Die Strategie erfordert, dass PriceStep durch die verbundene Sicherheit definiert wird. Fehlt es, werden Handelseinträge übersprungen und eine Warnung protokolliert.
Die Diagrammvisualisierung fügt die Ausführungskerzen zusammen mit den ausgeführten Trades hinzu. Bei Bedarf können zusätzliche benutzerdefinierte Zeichnungen hinzugefügt werden.
Die Implementierung vermeidet die direkte Abfrage von Indikatorwerten und verarbeitet nur fertige Kerzen unter Einhaltung der Projektrichtlinien in AGENTS.md.
Ursprüngliche EA-Besonderheiten erhalten
Formeln und Multiplikatoren auf Raymond-Ebene (0.382, 0.618, 1.0).
Eingabelogik basierend auf dem ersten Verkaufs-Take-Profit (TPS1).
Symmetrische 500-Punkte-Stop-Loss- und Take-Profit-Offsets, konvertiert in Ticks in der StockSharp-Umgebung.
Mit diesen Komponenten verhält sich die StockSharp-Strategie identisch mit der Quelle EA und bietet gleichzeitig umfangreiche Konfiguration und Protokollierung, die für weitere Forschung und Automatisierung geeignet sind.
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()