Esta estrategia es un port de StockSharp del asesor experto de MetaTrader 5 "ssb5_123". El código original proviene de la colección SSB (Step by Step) de Yury V. Reshetov y combina varios osciladores clásicos para confirmar rupturas direccionales. La versión de StockSharp mantiene la misma lógica utilizando la API de suscripción de velas de alto nivel e implementaciones de indicadores nativos.
El algoritmo trabaja exclusivamente con velas completadas. Compara el precio de apertura de la barra actual con la barra anterior, verifica el impulso y la dirección del Awesome Oscillator, MACD e histograma OsMA, y confirma que el precio opera por encima o por debajo de una media móvil suavizada. La confirmación adicional se obtiene del oscilador estocástico requiriendo que tanto %K como %D estén por encima o por debajo del nivel 50.
Indicadores y Señales
Los siguientes indicadores se emplean exactamente como en la versión de MetaTrader:
Media Móvil Suavizada (SMMA): media móvil suavizada de 45 períodos calculada desde las aperturas de las velas. La dirección de entrada requiere que el precio de apertura esté en el lado correcto de la media.
MACD (rápido 47, lento 95, señal 74): la línea principal debe ser positiva para operaciones largas (negativa para operaciones cortas) y debe estar subiendo (bajando) comparada con la vela anterior.
Histograma OsMA: calculado como MACD menos su línea de señal. El histograma debe decrecer para operaciones largas y aumentar para operaciones cortas, reflejando la función original fosma1().
Awesome Oscillator: usa las medias móviles suavizadas 5/34 predeterminadas del precio mediano. El valor del oscilador debe ser positivo para largos (negativo para cortos) y su impulso entre las últimas dos barras debe apuntar en la dirección de la operación.
Oscilador Estocástico (K=25, D=12, Slowing=56): tanto las líneas %K como %D deben estar por encima de 50 para operaciones largas y por debajo de 50 para operaciones cortas, proporcionando un filtro de régimen.
Lógica de Trading
Esperar una nueva vela completada.
Evaluar la configuración larga. Todas las siguientes condiciones deben ser verdaderas:
La apertura de la vela actual es menor o igual a la apertura de la vela anterior.
El Awesome Oscillator es positivo y cae respecto al valor anterior.
La línea principal MACD es positiva y sube respecto al valor anterior.
El histograma OsMA no está aumentando (histograma actual menos histograma anterior es menor o igual a cero).
La apertura de la vela actual está por encima de la media móvil suavizada.
Las líneas estocásticas %K y %D están en o por encima de 50.
Evaluar la configuración corta. Todas las siguientes condiciones deben ser verdaderas:
La apertura de la vela actual es mayor o igual a la apertura de la vela anterior.
El Awesome Oscillator es negativo y sube respecto al valor anterior.
La línea principal MACD es negativa y cae respecto al valor anterior.
El histograma OsMA no está decreciendo (histograma actual menos histograma anterior es mayor o igual a cero).
La apertura de la vela actual está por debajo de la media móvil suavizada.
Las líneas estocásticas %K y %D están en o por debajo de 50.
Si ya existe una posición, una señal opuesta la cierra inmediatamente, replicando la gestión de órdenes original de MetaTrader.
Cuando está plano, una entrada larga tiene prioridad: si ambas señales resultan ser verdaderas (posible cuando todos los indicadores son exactamente cero), la estrategia abre una posición larga. De lo contrario, abre una posición corta cuando solo se satisfacen las condiciones cortas.
Parámetros
SMMA Period – longitud del filtro de media móvil suavizada (predeterminado 45).
MACD Fast / Slow / Signal – períodos EMA para el indicador MACD (47 / 95 / 74).
Stochastic %K / %D / Slowing – período principal, período de suavizado y suavizado adicional para el oscilador estocástico (25 / 12 / 56).
Order Volume – cantidad utilizada para órdenes de mercado (predeterminado 1).
Candle Type – marco temporal de las velas de entrada (predeterminado 1 hora). Ajuste esto para que coincida con el marco temporal utilizado en MetaTrader.
Notas de Uso
La estrategia opera solo en velas terminadas; las actualizaciones intrabarra son ignoradas.
Los valores de indicadores de la vela anterior se almacenan en caché para que las comparaciones de impulso coincidan con el comportamiento exacto de las funciones auxiliares originales fao1, fmacd1 y fosma1.
No hay reglas de stop-loss o take-profit integradas en el asesor experto original. La gestión de riesgos debe añadirse externamente si es necesario.
Los ajustes de indicadores predeterminados coinciden con los parámetros MQL proporcionados, pero todos los valores se exponen como objetos StrategyParam y pueden optimizarse a través del optimizador de StockSharp.
Consideraciones de Conversión
La versión de MetaTrader usa un número mágico y validación manual de volumen; estas partes no son necesarias en StockSharp y fueron omitidas.
La lógica de cierre de órdenes sigue la misma precedencia que el script MQL: las posiciones se cierran primero, y las nuevas entradas solo se toman cuando la estrategia está plana.
Las implementaciones de Awesome Oscillator y MACD provienen de la biblioteca de indicadores de StockSharp, eliminando la necesidad de manejo manual de búferes presente en el código original.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// SSB5 123 strategy. Uses triple EMA alignment for 1-2-3 pattern detection.
/// </summary>
public class Ssb5123Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _midPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal? _prevFast;
private decimal? _prevMid;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int MidPeriod
{
get => _midPeriod.Value;
set => _midPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public Ssb5123Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_midPeriod = Param(nameof(MidPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Mid EMA", "Middle EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevMid = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null;
_prevMid = null;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var mid = new ExponentialMovingAverage { Length = MidPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, mid, slow, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, mid);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal midVal, decimal slowVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevFast = fastVal;
_prevMid = midVal;
return;
}
if (_prevFast == null || _prevMid == null)
{
_prevFast = fastVal;
_prevMid = midVal;
return;
}
// Fast crosses above mid while both above slow
if (_prevFast.Value <= _prevMid.Value && fastVal > midVal && midVal > slowVal && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Fast crosses below mid while both below slow
else if (_prevFast.Value >= _prevMid.Value && fastVal < midVal && midVal < slowVal && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevFast = fastVal;
_prevMid = midVal;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ssb5123_strategy(Strategy):
def __init__(self):
super(ssb5123_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 5) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._mid_period = self.Param("MidPeriod", 12) \
.SetDisplay("Mid EMA", "Middle EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 26) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_mid = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def MidPeriod(self):
return self._mid_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(ssb5123_strategy, self).OnReseted()
self._prev_fast = None
self._prev_mid = None
def OnStarted2(self, time):
super(ssb5123_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_mid = None
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
mid = ExponentialMovingAverage()
mid.Length = self.MidPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, mid, slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, mid)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, mid_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
mv = float(mid_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_mid is None:
self._prev_fast = fv
self._prev_mid = mv
return
# Fast crosses above mid while both above slow
if self._prev_fast <= self._prev_mid and fv > mv and mv > sv and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Fast crosses below mid while both below slow
elif self._prev_fast >= self._prev_mid and fv < mv and mv < sv and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_fast = fv
self._prev_mid = mv
def CreateClone(self):
return ssb5123_strategy()