Esta estratégia é um port do StockSharp do consultor especializado MetaTrader "Secwenta" (ID MQL 22977). O algoritmo escaneia candles concluídos e conta quantos fecharam em alta (fechamento > abertura) ou em baixa (fechamento < abertura) dentro de um histórico rolante curto. Dependendo da configuração, pode operar nos modos somente compra, somente venda ou reversão bidirecional. Quando o número necessário de barras de alta ou baixa aparece, a estratégia abre ou fecha posições de mercado usando um volume fixo que espelha a configuração de lote original.
Avaliação de sinais
Apenas candles terminados do CandleType selecionado são processados via API de assinatura de alto nível.
Para cada candle a estratégia registra se foi de alta, baixa ou neutro (doji). O buffer interno mantém as últimas N direções, onde N é o maior entre BullishBarCount e BearishBarCount entre os lados habilitados (compra e/ou venda).
O contador de alta incrementa quando um candle fecha acima de sua abertura, enquanto o contador de baixa incrementa em fechamentos abaixo da abertura. Candles neutros não afetam os contadores.
Um sinal é acionado uma vez que o contador correspondente atinge seu limiar configurado dentro da janela rolante. Isso reproduz a lógica MQL original que iterava pelas barras mais recentes até encontrar o número solicitado de candles de alta ou baixa.
Regras de negociação
Modo somente compra (UseBuySignals = true, UseSellSignals = false):
Quando o contador de baixa atinge BearishBarCount, qualquer posição comprada existente é fechada com uma ordem de venda a mercado.
Quando o contador de alta atinge BullishBarCount e a estratégia está zerada, uma nova posição comprada é aberta usando OrderVolume.
Modo somente venda (UseBuySignals = false, UseSellSignals = true):
Quando o contador de alta atinge BullishBarCount, uma posição vendida aberta é coberta com uma ordem de compra a mercado.
Quando o contador de baixa atinge BearishBarCount e a estratégia está zerada, uma nova posição vendida é aberta usando OrderVolume.
Modo de reversão (UseBuySignals = true e UseSellSignals = true):
Um gatilho de alta fecha qualquer exposição vendida e, se a estratégia não estiver já comprada, abre uma nova posição comprada comprando OrderVolume mais o tamanho absoluto da posição vendida. Isso imita a sequência original de fechar vendas antes de abrir compras.
Um gatilho de baixa fecha qualquer exposição comprada e, se a estratégia não estiver já vendida, abre uma nova posição vendida vendendo OrderVolume mais o tamanho absoluto da posição comprada.
Todas as operações de mercado reutilizam os helpers BuyMarket e SellMarket do StockSharp, e a estratégia chama StartProtection() para que proteções no nível de conta possam ser sobrepostas se desejado.
Parâmetros
Parâmetro
Descrição
Padrão
Notas
CandleType
Tipo de dados de candle (período) usado para avaliar sequências.
Período de 1 hora
Qualquer tipo de candle suportado pelo StockSharp pode ser selecionado.
OrderVolume
Volume base da ordem de mercado que espelha o tamanho de lote MQL.
1
Adicionado ao volume de fechamento ao reverter uma posição.
UseBuySignals
Habilita o processamento de sinais de alta.
true
Quando desabilitado, nenhum novo trade comprado é aberto.
BullishBarCount
Número de candles de alta necessários para acionar um evento de alta.
2
Deve permanecer consistente com o limiar de fechamento ao executar no modo somente compra.
UseSellSignals
Habilita o processamento de sinais de baixa.
false
Quando desabilitado, nenhum novo trade vendido é aberto.
BearishBarCount
Número de candles de baixa necessários para acionar um evento de baixa.
1
Atua tanto como limiar de abertura para vendidos quanto como limiar de saída para comprados.
Notas de implementação
A janela rolante usa uma fila para manter as últimas direções de candles e garante que os contadores correspondam ao tamanho da janela mesmo após mudanças de parâmetros.
Apenas candles terminados são processados para manter fidelidade ao tratamento original de eventos "nova barra".
Candles neutros (doji) deixam os contadores inalterados, exatamente como no código MQL.
As reversões são executadas com uma única ordem de mercado que combina o volume de fechamento e abertura, mantendo mudanças de exposição deterministas.
O comprimento do buffer é igual ao maior limiar ativo; se um lado está desabilitado, apenas o limiar correspondente contribui para o comprimento do lookback, correspondendo ao comportamento de CopyRates na versão MQL.
Arquivos
CS/SecwentaMultiBarSignalsStrategy.cs – implementação principal em C# construída sobre a API de estratégia de alto nível do StockSharp.
Nota: Nenhuma tradução para Python é fornecida para este ID; apenas a versão C# solicitada está disponível.
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()