Esta estratégia replica a lógica do especialista MetaTrader "Tunnel gen4" usando a API de alto nível do StockSharp. Ela mantém uma cobertura neutra ao mercado abrindo um par inicial de compra/venda, dobra a posição na direção do rompimento quando o preço percorre um número configurável de pips e sai de toda a cesta quando a mesma distância é percorrida novamente além da segunda âncora.
Lógica de Negociação
Cobertura inicial: Assim que não existe exposição, a estratégia envia ordens de compra e venda simultâneas a mercado com volume StartVolume. A primeira execução define o preço de referência para todas as decisões subsequentes.
Detecção de passo: O StepPips configurado é convertido em um deslocamento de preço usando o tamanho do tick do instrumento (com ajustes automáticos para cotações forex de três e cinco decimais). As atualizações do melhor bid/ask do fluxo Level 1 são comparadas com esse deslocamento.
Ordem de reforço: Quando o melhor bid sobe pelo menos um passo desde a primeira execução, uma ordem de venda com o dobro do volume base é enviada. Quando o melhor ask cai pelo menos um passo, uma ordem de compra do mesmo tamanho é emitida. A primeira execução dessa ordem se torna a segunda âncora.
Encerramento do ciclo: Após a segunda âncora estar ativa, qualquer movimento adicional do tamanho de um passo em qualquer direção aciona a liquidação completa de todas as posições abertas. Uma vez que ambos os lados estão fechados, o estado é reiniciado e um novo ciclo pode começar.
Validação de volume: O início da estratégia verifica que tanto os volumes inicial quanto dobrado respeitam os requisitos mínimos, máximos e de incremento do instrumento, para que cada ordem enviada ao conector seja executável.
Condições de Entrada
Reforço comprado
Há pelo menos uma posição aberta da cobertura inicial.
A segunda âncora ainda não foi criada.
O preço atual do melhor ask é menor ou igual a first_fill_price - StepPips_em_preço.
Reforço vendido
Há pelo menos uma posição aberta da cobertura inicial.
A segunda âncora ainda não foi criada.
O preço atual do melhor bid é maior ou igual a first_fill_price + StepPips_em_preço.
Gerenciamento de Saída
Fechamento da cesta: Uma vez que a segunda âncora está definida, se o melhor bid subir acima de second_anchor + StepOffset ou o melhor ask cair abaixo de second_anchor - StepOffset, ordens a mercado são enviadas para fechar a exposição longa e curta acumulada. As ordens de fechamento são rastreadas para garantir que o estado seja reiniciado somente após a confirmação de todas as negociações.
Reinício do estado: Após ambos os lados estarem fechados e nenhuma ordem de fechamento permanecer ativa, a estratégia limpa as âncoras internas e aguarda uma nova cobertura ser aberta.
Dados e Indicadores
A assinatura Level 1 fornece os melhores preços bid e ask usados para comparações de passo.
Nenhum indicador adicional é necessário; toda a lógica funciona com atualizações de cotação brutas.
A conversão do passo de preço imita o ajuste ponto-a-pip do MetaTrader, para que símbolos forex com três ou cinco decimais se comportem da mesma forma que no especialista de origem.
Parâmetros
Parâmetro
Descrição
StartVolume
Volume das ordens de compra e venda que formam a cobertura inicial.
StepPips
Distância em pips que aciona a ordem de reforço e a subsequente saída da cesta.
Notas de Implementação
O StockSharp mantém uma posição líquida por ativo. A estratégia mantém contadores de exposição internos para emular os tickets longos e curtos separados usados pelo especialista MetaTrader e emite ordens a mercado com os volumes acumulados ao fechar a cesta.
Como a lógica depende de spreads em tempo real, forneça dados Level 1 tanto em backtests quanto em sessões de trading ao vivo. A falta de informações bid/ask desabilita o loop de negociação.
Certifique-se de que a conta de trading suporte ordens de compra e venda simultâneas para o mesmo instrumento, pois o algoritmo pressupõe que ambos os lados da cobertura possam coexistir até que a condição de saída seja atingida.
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>
/// Tunnel strategy that uses Bollinger Bands to define a price channel.
/// Buys when price crosses above the lower band (reversal from oversold),
/// sells when price crosses below the upper band (reversal from overbought).
/// </summary>
public class TunnelGen4Strategy : Strategy
{
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbWidth;
private readonly StrategyParam<decimal> _stepPips;
private BollingerBands _bb;
private decimal _prevClose;
private decimal _prevUpper;
private decimal _prevLower;
private decimal _entryPrice;
/// <summary>
/// Bollinger Bands period length.
/// </summary>
public int BbLength
{
get => _bbLength.Value;
set => _bbLength.Value = value;
}
/// <summary>
/// Bollinger Bands width (standard deviations).
/// </summary>
public decimal BbWidth
{
get => _bbWidth.Value;
set => _bbWidth.Value = value;
}
/// <summary>
/// Step distance expressed in pips for profit target.
/// </summary>
public decimal StepPips
{
get => _stepPips.Value;
set => _stepPips.Value = value;
}
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public TunnelGen4Strategy()
{
_bbLength = Param(nameof(BbLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Length", "Bollinger Bands period", "Indicator");
_bbWidth = Param(nameof(BbWidth), 2.0m)
.SetGreaterThanZero()
.SetDisplay("BB Width", "Bollinger Bands width", "Indicator");
_stepPips = Param(nameof(StepPips), 50m)
.SetGreaterThanZero()
.SetDisplay("Step (pips)", "Distance between tunnel anchors", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bb = null;
_prevClose = 0;
_prevUpper = 0;
_prevLower = 0;
_entryPrice = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bb = new BollingerBands
{
Length = BbLength,
Width = BbWidth
};
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.BindEx(_bb, OnProcess);
subscription.Start();
}
private void OnProcess(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
var bb = (BollingerBandsValue)value;
if (bb.UpBand is not decimal upper ||
bb.LowBand is not decimal lower)
return;
if (!_bb.IsFormed)
{
_prevClose = candle.ClosePrice;
_prevUpper = upper;
_prevLower = lower;
return;
}
var close = candle.ClosePrice;
// Buy signal: price crosses above lower band from below
if (_prevClose < _prevLower && close >= lower && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
}
// Sell signal: price crosses below upper band from above
else if (_prevClose > _prevUpper && close <= upper && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
}
// Exit on profit target if in position
if (Position > 0 && _entryPrice > 0)
{
var pipValue = Security?.PriceStep ?? 1m;
var target = _entryPrice + StepPips * pipValue;
if (close >= target)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0 && _entryPrice > 0)
{
var pipValue = Security?.PriceStep ?? 1m;
var target = _entryPrice - StepPips * pipValue;
if (close <= target)
{
BuyMarket();
_entryPrice = 0;
}
}
_prevClose = close;
_prevUpper = upper;
_prevLower = lower;
}
}
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 BollingerBands
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class tunnel_gen4_strategy(Strategy):
"""Bollinger Band tunnel: buy on cross above lower band, sell on cross below upper."""
def __init__(self):
super(tunnel_gen4_strategy, self).__init__()
self._bb_length = self.Param("BbLength", 20).SetGreaterThanZero().SetDisplay("BB Length", "BB period", "Indicator")
self._bb_width = self.Param("BbWidth", 2.0).SetGreaterThanZero().SetDisplay("BB Width", "BB std devs", "Indicator")
self._step_pips = self.Param("StepPips", 50.0).SetGreaterThanZero().SetDisplay("Step Pips", "Profit target distance", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(tunnel_gen4_strategy, self).OnReseted()
self._prev_close = 0
self._prev_upper = 0
self._prev_lower = 0
self._entry_price = 0
def OnStarted2(self, time):
super(tunnel_gen4_strategy, self).OnStarted2(time)
self._prev_close = 0
self._prev_upper = 0
self._prev_lower = 0
self._entry_price = 0
self._bb = BollingerBands()
self._bb.Length = self._bb_length.Value
self._bb.Width = self._bb_width.Value
sub = self.SubscribeCandles(self.CandleType)
sub.BindEx(self._bb, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, self._bb)
self.DrawOwnTrades(area)
def OnProcess(self, candle, bb_val):
if candle.State != CandleStates.Finished:
return
if not self._bb.IsFormed:
return
upper = None
lower = None
for inner in bb_val.InnerValues:
name = str(inner.Key.Name) if hasattr(inner.Key, 'Name') else str(inner.Key)
if "Up" in name or "up" in name:
upper = float(inner.Value) if not inner.Value.IsEmpty else None
elif "Low" in name or "low" in name or "Down" in name or "down" in name:
lower = float(inner.Value) if not inner.Value.IsEmpty else None
if upper is None or lower is None:
return
close = float(candle.ClosePrice)
if self._prev_close == 0:
self._prev_close = close
self._prev_upper = upper
self._prev_lower = lower
return
# Buy: price crosses above lower band from below
if self._prev_close < self._prev_lower and close >= lower and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
# Sell: price crosses below upper band from above
elif self._prev_close > self._prev_upper and close <= upper and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
# Exit on profit target
step = self._step_pips.Value
if self.Position > 0 and self._entry_price > 0:
if close >= self._entry_price + step:
self.SellMarket()
self._entry_price = 0
elif self.Position < 0 and self._entry_price > 0:
if close <= self._entry_price - step:
self.BuyMarket()
self._entry_price = 0
self._prev_close = close
self._prev_upper = upper
self._prev_lower = lower
def CreateClone(self):
return tunnel_gen4_strategy()