Стратегия повторяет логику советника MetaTrader "Secwenta" (MQL ID 22977) в среде StockSharp. Алгоритм анализирует завершённые свечи выбранного таймфрейма, считает количество бычьих (close > open) и медвежьих (close < open) баров в коротком скользящем окне и, в зависимости от настроек, открывает или закрывает позиции. Поддерживаются три режима работы: только покупки, только продажи и двусторонние реверсы. Объём заявок соответствует исходному параметру Lots.
Оценка сигналов
Используются только полностью сформированные свечи (CandleType), поступающие через высокоуровневую подписку StockSharp.
Для каждой свечи фиксируется направление: бычья, медвежья или нейтральная. Внутренний буфер хранит последние N направлений, где N равно максимальному из BullishBarCount и BearishBarCount для включённых направлений торговли.
Счётчики увеличиваются при закрытии свечи выше или ниже открытия; нейтральные бары счётчики не меняют.
Сигнал появляется, когда соответствующий счётчик достигает порога внутри окна, что полностью воспроизводит оригинальный перебор массивов CopyRates.
Правила торговли
Режим только покупок (UseBuySignals = true, UseSellSignals = false):
При достижении BearishBarCount медвежьих свечей текущая длинная позиция закрывается рыночной продажей.
При достижении BullishBarCount бычьих свечей и нулевой позиции открывается новая длинная позиция объёмом OrderVolume.
Режим только продаж (UseBuySignals = false, UseSellSignals = true):
При достижении BullishBarCount бычьих свечей текущая короткая позиция закрывается рыночной покупкой.
При достижении BearishBarCount медвежьих свечей и отсутствии позиций открывается новая короткая позиция объёмом OrderVolume.
Реверсивный режим (UseBuySignals = true и UseSellSignals = true):
Бычий сигнал закрывает шорт и, если нет длинной позиции, открывает лонг объёмом OrderVolume плюс модуль закрываемой позиции.
Медвежий сигнал закрывает лонг и, если нет короткой позиции, открывает шорт объёмом OrderVolume плюс модуль закрываемой позиции.
Все операции выполняются через BuyMarket/SellMarket, дополнительно вызывается StartProtection() для совместимости с защитными модулями StockSharp.
Параметры
Параметр
Описание
Значение по умолчанию
Примечание
CandleType
Тип свечей, по которым ведётся подсчёт.
Таймфрейм 1 час
Можно выбрать любой поддерживаемый тип данных.
OrderVolume
Базовый объём рыночной заявки.
1
Добавляется к объёму закрытия при реверсе позиции.
UseBuySignals
Обработка бычьих сигналов.
true
При отключении новые покупки не открываются.
BullishBarCount
Количество бычьих свечей для сигнала.
2
В режиме только покупок определяет длину окна.
UseSellSignals
Обработка медвежьих сигналов.
false
При отключении новые продажи не открываются.
BearishBarCount
Количество медвежьих свечей для сигнала.
1
Используется для открытия шортов и выхода из лонгов.
Особенности реализации
Очередь направлений синхронизирована с текущим размером окна и пересчитывается при изменении параметров.
Обрабатываются только завершённые свечи, как и в оригинале, который реагировал на появление нового бара.
Нейтральные свечи не влияют на счётчики, сохраняя поведение MQL-версии.
Реверсы выполняются одной рыночной заявкой, объединяющей объём закрытия и открытия.
Размер окна равен крупнейшему активному порогу; если одна из сторон отключена, учитывается только порог активной стороны, что соответствует логике исходного кода.
Файлы
CS/SecwentaMultiBarSignalsStrategy.cs – реализация стратегии на C# с использованием высокоуровневого API StockSharp.
Примечание: Для данной стратегии предоставлена только C#-версия, Python-реализация отсутствует.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Secwenta MultiBar Signals strategy using SmoothedMA crossover.
/// Buys when fast SmoothedMA crosses above slow SmoothedMA, sells on reverse.
/// </summary>
public class SecwentaMultiBarSignalsStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private SmoothedMovingAverage _fast;
private SmoothedMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast SmoothedMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow SmoothedMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="SecwentaMultiBarSignalsStrategy"/> class.
/// </summary>
public SecwentaMultiBarSignalsStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast SmoothedMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 60)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow SmoothedMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new SmoothedMovingAverage { Length = FastPeriod };
_slow = new SmoothedMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// SmoothedMA crossover
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 80;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 80;
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
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 SmoothedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class secwenta_multi_bar_signals_strategy(Strategy):
def __init__(self):
super(secwenta_multi_bar_signals_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 60) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(secwenta_multi_bar_signals_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(secwenta_multi_bar_signals_strategy, self).OnStarted2(time)
self._fast = SmoothedMovingAverage()
self._fast.Length = self.fast_period
self._slow = SmoothedMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return secwenta_multi_bar_signals_strategy()