Esta estrategia es un port de StockSharp del asesor experto de MetaTrader "Secwenta" (ID MQL 22977). El algoritmo escanea velas completadas y cuenta cuántas cerraron alcistas (cierre > apertura) o bajistas (cierre < apertura) dentro de un historial rodante corto. Según la configuración, puede operar en modo solo compra, solo venta o reversión bidireccional. Cuando aparece el número requerido de barras alcistas o bajistas, la estrategia abre o cierra posiciones de mercado usando un volumen fijo que refleja el tamaño de lote original.
Evaluación de señales
Solo se procesan las velas terminadas del CandleType seleccionado mediante la API de suscripción de alto nivel.
Para cada vela la estrategia registra si fue alcista, bajista o neutral (doji). El buffer interno mantiene las últimas N direcciones, donde N es el mayor entre BullishBarCount y BearishBarCount entre los lados habilitados (compra y/o venta).
El contador alcista se incrementa cuando una vela cierra por encima de su apertura, mientras el contador bajista se incrementa en cierres por debajo de la apertura. Las velas neutrales no afectan a los contadores.
Se activa una señal una vez que el contador correspondiente alcanza su umbral configurado dentro de la ventana rodante. Esto reproduce la lógica MQL original que iteraba a través de las barras más recientes hasta encontrar el número solicitado de velas alcistas o bajistas.
Reglas de trading
Modo solo compra (UseBuySignals = true, UseSellSignals = false):
Cuando el contador bajista alcanza BearishBarCount, cualquier posición larga existente se cierra con una orden de venta de mercado.
Cuando el contador alcista alcanza BullishBarCount y la estrategia está plana, se abre una nueva posición larga usando OrderVolume.
Modo solo venta (UseBuySignals = false, UseSellSignals = true):
Cuando el contador alcista alcanza BullishBarCount, una posición corta abierta se cubre con una orden de compra de mercado.
Cuando el contador bajista alcanza BearishBarCount y la estrategia está plana, se abre una nueva posición corta usando OrderVolume.
Modo reversión (UseBuySignals = true y UseSellSignals = true):
Un disparador alcista cierra cualquier exposición corta y, si la estrategia no está ya larga, abre una nueva posición larga comprando OrderVolume más el tamaño absoluto de la posición corta. Esto imita la secuencia original de cerrar ventas antes de abrir compras.
Un disparador bajista cierra cualquier exposición larga y, si la estrategia no está ya corta, abre una nueva posición corta vendiendo OrderVolume más el tamaño absoluto de la posición larga.
Todas las operaciones de mercado reutilizan los helpers BuyMarket y SellMarket de StockSharp, y la estrategia llama a StartProtection() para que las protecciones a nivel de cuenta puedan añadirse encima si se desea.
Parámetros
Parámetro
Descripción
Por defecto
Notas
CandleType
Tipo de datos de vela (marco temporal) usado para evaluar secuencias.
Marco temporal de 1 hora
Se puede seleccionar cualquier tipo de vela soportado por StockSharp.
OrderVolume
Volumen base de la orden de mercado que refleja el tamaño de lote MQL.
1
Se añade al volumen de cierre al revertir una posición.
UseBuySignals
Habilita el procesamiento de señales alcistas.
true
Cuando está deshabilitado, no se abren nuevos trades largos.
BullishBarCount
Número de velas alcistas requeridas para activar un evento alcista.
2
Debe mantenerse consistente con el umbral de cierre al ejecutar en modo solo compra.
UseSellSignals
Habilita el procesamiento de señales bajistas.
false
Cuando está deshabilitado, no se abren nuevos trades cortos.
BearishBarCount
Número de velas bajistas requeridas para activar un evento bajista.
1
Actúa tanto como umbral de apertura para cortos como umbral de salida para largos.
Notas de implementación
La ventana rodante usa una cola para mantener las últimas direcciones de velas y asegura que los contadores coincidan con el tamaño de la ventana incluso después de cambios de parámetros.
Solo se procesan velas terminadas para mantenerse fiel al manejo original de eventos "nueva barra".
Las velas neutrales (doji) dejan los contadores sin cambios, exactamente como en el código MQL.
Las reversiones se ejecutan con una única orden de mercado que combina el volumen de cierre y apertura, manteniendo cambios de exposición deterministas.
La longitud del buffer es igual al mayor umbral activo; si un lado está deshabilitado, solo el umbral correspondiente contribuye a la longitud de lookback, coincidiendo con el comportamiento de CopyRates en la versión MQL.
Archivos
CS/SecwentaMultiBarSignalsStrategy.cs – implementación principal en C# construida sobre la API de estrategia de alto nivel de StockSharp.
Nota: No se proporciona traducción a Python para este ID; solo se incluye la versión C# solicitada.
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()