Estrategia de Cuadrícula con Cobertura Tunnel Gen4
Esta estrategia replica la lógica del experto MetaTrader "Tunnel gen4" usando la API de alto nivel de StockSharp. Mantiene una cobertura neutral al mercado abriendo un par inicial de compra/venta, duplica la posición en la dirección de la ruptura una vez que el precio recorre un número configurable de pips, y sale de toda la cesta cuando la misma distancia se cubre nuevamente más allá del segundo ancla.
Lógica de Trading
Cobertura inicial: Tan pronto como no existe exposición, la estrategia envía órdenes simultáneas de compra y venta a mercado con volumen StartVolume. La primera ejecución define el precio de referencia para todas las decisiones posteriores.
Detección de paso: El StepPips configurado se convierte en un desplazamiento de precio usando el tamaño de tick del instrumento (con ajustes automáticos para citas forex de tres y cinco decimales). Las actualizaciones del mejor bid/ask del flujo Level 1 se comparan contra este desplazamiento.
Orden de refuerzo: Cuando el mejor bid sube al menos un paso desde la primera ejecución, se envía una orden de venta con el doble del volumen base. Cuando el mejor ask baja al menos un paso, se emite una orden de compra del mismo tamaño. La primera ejecución de esta orden se convierte en el segundo ancla.
Terminación del ciclo: Después de que el segundo ancla está activo, cualquier movimiento adicional del tamaño de un paso en cualquier dirección activa la liquidación completa de todas las posiciones abiertas. Una vez que ambos lados están cerrados, el estado se reinicia y puede comenzar un nuevo ciclo.
Validación de volumen: El inicio de la estrategia verifica que tanto los volúmenes inicial como duplicado respeten los requisitos mínimos, máximos e incrementales del instrumento, para que cada orden enviada al conector sea ejecutable.
Condiciones de Entrada
Refuerzo largo
Hay al menos una posición abierta del cobertura inicial.
El segundo ancla aún no ha sido creado.
El precio actual del mejor ask es menor o igual a first_fill_price - StepPips_en_precio.
Refuerzo corto
Hay al menos una posición abierta de la cobertura inicial.
El segundo ancla aún no ha sido creado.
El precio actual del mejor bid es mayor o igual a first_fill_price + StepPips_en_precio.
Gestión de Salida
Cierre de cesta: Una vez que el segundo ancla está definido, si el mejor bid sube por encima de second_anchor + StepOffset o el mejor ask cae por debajo de second_anchor - StepOffset, se envían órdenes a mercado para cerrar la exposición larga y corta acumulada. Las órdenes de cierre se rastrean para garantizar que el estado se reinicie solo después de que todas las operaciones sean confirmadas.
Reinicio de estado: Después de que ambos lados estén cerrados y no queden órdenes de cierre activas, la estrategia limpia los anclas internos y espera a que se abra una nueva cobertura.
Datos e Indicadores
La suscripción Level 1 entrega los mejores precios bid y ask usados para las comparaciones de paso.
No se requieren indicadores adicionales; toda la lógica funciona con actualizaciones de cotización en bruto.
La conversión del paso de precio imita el ajuste punto-a-pip de MetaTrader, por lo que los símbolos forex con tres o cinco decimales se comportan igual que en el experto origen.
Parámetros
Parámetro
Descripción
StartVolume
Volumen de las órdenes de compra y venta que forman la cobertura inicial.
StepPips
Distancia en pips que activa la orden de refuerzo y la posterior salida de la cesta.
Notas de Implementación
StockSharp mantiene una posición neta por instrumento. La estrategia mantiene contadores de exposición internos para emular los tickets largos y cortos separados usados por el experto MetaTrader y emite órdenes a mercado con los volúmenes acumulados al cerrar la cesta.
Como la lógica depende de spreads en tiempo real, proporcione datos Level 1 tanto en backtests como en sesiones de trading en vivo. La falta de información bid/ask deshabilita el bucle de trading.
Asegúrese de que la cuenta de trading admita órdenes de compra y venta simultáneas para el mismo instrumento, ya que el algoritmo asume que ambos lados de la cobertura pueden coexistir hasta que se cumpla la condición de salida.
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()