Esta estrategia transfiere el asesor experto MetaTrader 5 Expert_AMS_ES_Stoch (Morning/Evening Star con confirmación de Stochastic) a StockSharp. La implementación mantiene el reconocimiento de patrones de velas originales y las reglas de confirmación estocástica mientras utiliza la suscripción de velas de alto nivel API para que cada decisión se tome en barras terminadas.
Lógica de la estrategia
Indicadores
Oscilador Stochastic estándar con %K, %D y períodos de desaceleración configurables.
Promedio móvil simple del tamaño del cuerpo de la vela (open-close absoluto) para clasificar las velas como "largas" o "pequeñas" al igual que la versión MQL.
Entrada larga
Patrón Morning Star en las últimas tres velas completadas:
Hace dos barras: cuerpo bajista largo cuyo tamaño supera la media del cuerpo.
Barra anterior: vela de cuerpo pequeño que cierra y abre por debajo de la vela anterior.
Barra actual: cierre alcista por encima del punto medio de la primera vela.
La línea de señal Stochastic (%D) está por debajo del umbral de sobreventa (predeterminado 30).
La exposición corta existente se aplana antes de abrir la posición larga.
Short Entry
Patrón Evening Star que refleja las reglas anteriores.
Stochastic %D está por encima del umbral de sobrecompra (predeterminado 70).
La exposición larga existente se cierra antes de abrir la operación corta.
Posición de salida
Los cortos se cierran cuando %D cruza por encima del nivel de recuperación rápida (20) o del nivel extremo (80).
Los largos se cierran cuando %D cruza por debajo de 80 o 20.
Estos cruces reproducen las "condiciones de cierre" del módulo de señales MQL.
Parámetros
Nombre
Descripción
CandleType
Marco de tiempo (u otro DataType) utilizado para la detección de patrones y todos los indicadores.
%K, %D y períodos de desaceleración del oscilador estocástico.
StochasticOverbought, StochasticOversold
Umbrales de línea de señal utilizados para confirmar entradas de estrella vespertina/matinal.
PatternAveragePeriod
Número de velas terminadas utilizadas para promediar el tamaño del cuerpo (|abrir-cerrar|).
ShortExitLevel, LongExitLevel
%D niveles que fuerzan salidas cortas/largas cuando se cruzan en dirección opuesta.
Notas de implementación
Las velas se procesan a través de SubscribeCandles().BindEx(...); el código solo funciona con velas terminadas y nunca invoca GetValue() en indicadores.
El promedio del tamaño corporal se basa en SimpleMovingAverage alimentado con cuerpos de velas absolutos para reproducir el ayudante AvgBody() de la biblioteca MQL.
Las comprobaciones de patrones se implementan con métodos auxiliares dedicados para mantener legible la lógica de decisión y reflejar las reglas CCandlePattern originales.
Antes de entrar en la dirección opuesta, la estrategia cierra cualquier exposición existente para igualar el comportamiento del Asesor Experto de operar una posición neta a la vez.
Diferencias con el experto MQL5
La administración de dinero, el trailing stop y la configuración de lotes fijos del marco MetaTrader no se reproducen; El volumen de pedidos StockSharp está controlado por la propiedad de la estrategia Volume.
El oscilador Stochastic utiliza la implementación del indicador de StockSharp; los umbrales siguen siendo configurables para que pueda ajustar el comportamiento si el feed del agente original produjo valores ligeramente diferentes.
El registro proporciona explicaciones detalladas (en inglés) para cada entrada y salida para ayudar en la depuración y las pruebas retrospectivas.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Morning/Evening Star pattern strategy with Stochastic confirmation.
/// Buys on morning star + oversold stochastic, sells on evening star + overbought stochastic.
/// </summary>
public class MorningEveningStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<decimal> _oversold;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<int> _signalCooldownCandles;
private readonly List<ICandleMessage> _candles = new();
private decimal _prevK;
private bool _hasPrevK;
private int _candlesSinceTrade;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.Value = value; }
public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public MorningEveningStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stoch Period", "Stochastic K period", "Indicators");
_oversold = Param(nameof(Oversold), 30m)
.SetDisplay("Oversold", "Stochastic oversold level", "Signals");
_overbought = Param(nameof(Overbought), 70m)
.SetDisplay("Overbought", "Stochastic overbought level", "Signals");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candles.Clear();
_prevK = 0m;
_hasPrevK = false;
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candles.Clear();
_hasPrevK = false;
_candlesSinceTrade = SignalCooldownCandles;
var stoch = new StochasticOscillator { K = { Length = StochPeriod }, D = { Length = 3 } };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(stoch, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
var stochTyped = stochValue as StochasticOscillatorValue;
if (stochTyped?.K is not decimal kValue) return;
_candles.Add(candle);
if (_candles.Count > 5)
_candles.RemoveAt(0);
if (_candles.Count >= 3)
{
var c3 = _candles[^1]; // current
var c2 = _candles[^2]; // middle (star)
var c1 = _candles[^3]; // first
var body1 = Math.Abs(c1.ClosePrice - c1.OpenPrice);
var body2 = Math.Abs(c2.ClosePrice - c2.OpenPrice);
var body3 = Math.Abs(c3.ClosePrice - c3.OpenPrice);
// Morning Star: bearish + small body + bullish, close above midpoint of first
var isMorningStar = c1.OpenPrice > c1.ClosePrice // first bearish
&& body2 < body1 * 0.5m // small middle body
&& c3.ClosePrice > c3.OpenPrice // third bullish
&& c3.ClosePrice > (c1.OpenPrice + c1.ClosePrice) / 2m;
// Evening Star: bullish + small body + bearish, close below midpoint of first
var isEveningStar = c1.ClosePrice > c1.OpenPrice // first bullish
&& body2 < body1 * 0.5m // small middle body
&& c3.OpenPrice > c3.ClosePrice // third bearish
&& c3.ClosePrice < (c1.OpenPrice + c1.ClosePrice) / 2m;
if (isMorningStar && kValue < Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (isEveningStar && kValue > Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
// Exit on stochastic cross
if (_hasPrevK)
{
if (Position > 0 && _prevK >= Overbought && kValue < Overbought && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
else if (Position < 0 && _prevK <= Oversold && kValue > Oversold && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
}
_prevK = kValue;
_hasPrevK = true;
}
}
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
from StockSharp.Algo.Indicators import StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class morning_evening_stochastic_strategy(Strategy):
def __init__(self):
super(morning_evening_stochastic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._stoch_period = self.Param("StochPeriod", 14)
self._oversold = self.Param("Oversold", 30.0)
self._overbought = self.Param("Overbought", 70.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 6)
self._candles = []
self._prev_k = 0.0
self._has_prev_k = False
self._candles_since_trade = 6
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StochPeriod(self):
return self._stoch_period.Value
@StochPeriod.setter
def StochPeriod(self, value):
self._stoch_period.Value = value
@property
def Oversold(self):
return self._oversold.Value
@Oversold.setter
def Oversold(self, value):
self._oversold.Value = value
@property
def Overbought(self):
return self._overbought.Value
@Overbought.setter
def Overbought(self, value):
self._overbought.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(morning_evening_stochastic_strategy, self).OnReseted()
self._candles.clear()
self._prev_k = 0.0
self._has_prev_k = False
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(morning_evening_stochastic_strategy, self).OnStarted2(time)
self._candles.clear()
self._has_prev_k = False
self._candles_since_trade = self.SignalCooldownCandles
stoch = StochasticOscillator()
stoch.K.Length = self.StochPeriod
stoch.D.Length = 3
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(stoch, self._process_candle).Start()
def _process_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
k_val = stoch_value.K
if k_val is None:
return
k_value = float(k_val)
self._candles.append(candle)
if len(self._candles) > 5:
self._candles.pop(0)
if len(self._candles) >= 3:
c3 = self._candles[-1]
c2 = self._candles[-2]
c1 = self._candles[-3]
body1 = abs(float(c1.ClosePrice) - float(c1.OpenPrice))
body2 = abs(float(c2.ClosePrice) - float(c2.OpenPrice))
is_morning_star = (float(c1.OpenPrice) > float(c1.ClosePrice)
and body2 < body1 * 0.5
and float(c3.ClosePrice) > float(c3.OpenPrice)
and float(c3.ClosePrice) > (float(c1.OpenPrice) + float(c1.ClosePrice)) / 2.0)
is_evening_star = (float(c1.ClosePrice) > float(c1.OpenPrice)
and body2 < body1 * 0.5
and float(c3.OpenPrice) > float(c3.ClosePrice)
and float(c3.ClosePrice) < (float(c1.OpenPrice) + float(c1.ClosePrice)) / 2.0)
if is_morning_star and k_value < self.Oversold and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif is_evening_star and k_value > self.Overbought and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
if self._has_prev_k:
if self.Position > 0 and self._prev_k >= self.Overbought and k_value < self.Overbought and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
elif self.Position < 0 and self._prev_k <= self.Oversold and k_value > self.Oversold and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
self._prev_k = k_value
self._has_prev_k = True
def CreateClone(self):
return morning_evening_stochastic_strategy()