Стратегия представляет собой перенос советника MetaTrader 5 «ssb5_123» на платформу StockSharp. Исходный алгоритм из коллекции SSB (Step by Step) Юрия Решетова объединяет несколько классических осцилляторов для подтверждения пробоев. Переписанная версия использует высокоуровневый API подписки на свечи и встроенные индикаторы StockSharp, полностью сохраняя торговые правила оригинала.
Обработка выполняется только по факту закрытия свечи. Стратегия сравнивает открытие текущей свечи с предыдущей, анализирует направление и изменение Awesome Oscillator, MACD и гистограммы OsMA, проверяет положение цены относительно сглаженной скользящей средней и фильтрует сигналы с помощью стохастика, требуя, чтобы линии %K и %D находились по одну сторону от уровня 50.
Используемые индикаторы
Smoothed Moving Average (SMMA, период 45) – фильтрует направление: открытие должно находиться выше средней для лонга и ниже для шорта.
MACD (47 / 95 / 74) – главная линия должна иметь правильный знак, а также расти (для покупок) или падать (для продаж) относительно предыдущей свечи.
OsMA – рассчитывается как разница между главной линией MACD и сигналом. Для покупок гистограмма не должна расти, для продаж – не должна падать.
Awesome Oscillator – стандартные периоды 5/34. Требуется, чтобы сам осциллятор имел нужный знак, а также чтобы разница между текущим и предыдущим значением указывала в сторону сделки.
Стохастик (K=25, D=12, Slowing=56) – обе линии должны находиться выше 50 при покупках и ниже 50 при продажах.
Логика торговли
Дождаться закрытия новой свечи.
Проверить условия на покупку (все должны выполняться):
Открытие текущей свечи меньше либо равно открытию предыдущей.
AO > 0 и текущее значение ниже предыдущего.
MACD > 0 и текущее значение выше предыдущего.
Гистограмма OsMA не увеличивается.
Открытие находится выше SMMA.
%K и %D стохастика ≥ 50.
Проверить условия на продажу (все должны выполняться):
Открытие текущей свечи больше либо равно открытию предыдущей.
AO < 0 и текущее значение выше предыдущего.
MACD < 0 и текущее значение ниже предыдущего.
Гистограмма OsMA не уменьшается.
Открытие находится ниже SMMA.
%K и %D стохастика ≤ 50.
Если позиция уже открыта, противоположный сигнал немедленно закрывает её. Это повторяет порядок действий оригинального советника.
При отсутствии позиции сначала рассматривается сигнал на покупку. Если внезапно оба направления выполняются (возможная ситуация при нулевых значениях индикаторов), стратегия, как и MQL-версия, открывает лонг.
Параметры
SMMA Period – период сглаженной средней (по умолчанию 45).
MACD Fast / Slow / Signal – периоды EMA для MACD (47 / 95 / 74).
Order Volume – объём рыночных заявок (по умолчанию 1).
Candle Type – тип/таймфрейм свечей. По умолчанию 1 час, при необходимости выставите период, использованный в MetaTrader.
Дополнительные замечания
Расчёты выполняются только на закрытых свечах; внутрисессионные обновления игнорируются.
Значения индикаторов предыдущей свечи сохраняются, чтобы сравнение направлений полностью соответствовало функциям fao1, fmacd1 и fosma1 из MQL.
В советнике не предусмотрены стоп-лосс и тейк-профит. Управление рисками следует реализовать отдельно.
Все настройки заданы через StrategyParam, поэтому доступны для оптимизации стандартными средствами StockSharp.
Особенности переноса
Проверка доступных лотов и "magic number" из MQL не требуются в StockSharp и были исключены.
Порядок управления позициями сохранён: закрытие выполняется раньше, чем попытка открыть новую позицию, поэтому разворот в ту же свечу не происходит.
Индикаторы Awesome Oscillator и MACD берутся из библиотеки StockSharp, что избавляет от ручного копирования буферов, присутствовавшего в исходном коде.
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()