Sistema híbrido de seguimiento de tendencia que combina reversiones por Bollinger Band, dos medias móviles de marcos temporales superiores y puntos de giro de un detector ZigZag. Abre dos posiciones en cada señal: una con un objetivo de take-profit calculado y una segunda "corriente" que depende de trailing y lógica de break-even.
Detalles
Criterios de entrada:
Largo: La barra anterior cerró por encima de la banda inferior anterior de Bollinger después de haber cerrado por debajo dos barras atrás, el cierre actual también está por encima de esa banda inferior, y el precio está por encima de ambas medias móviles de marcos temporales superiores.
Corto: La barra anterior cerró por debajo de la banda superior anterior de Bollinger después de haber cerrado por encima dos barras atrás, el cierre actual también está por debajo de esa banda superior, y el precio está por debajo de ambas medias móviles de marcos temporales superiores.
Gestión de posiciones:
Se abren dos posiciones por señal usando First Volume (con take-profit) y Second Volume (corriente).
Los stops están anclados al extremo de giro ZigZag más reciente menos/más Pivot Offset (pts).
La protección de break-even desplaza el stop a la entrada más un offset una vez que el beneficio no realizado supera Break-even Threshold (pts) + Break-even Offset (pts).
El trailing stop se mueve después de que el precio avance Trailing Step (pts) más allá del stop existente, manteniendo una distancia de Trailing Stop (pts).
Take Profit:
El take-profit de la primera posición se calcula como un porcentaje (Take Profit %) de la distancia entre la entrada y el stop.
La posición corriente no tiene objetivo fijo y sale por stop, trailing o señales opuestas.
Lógica adicional:
Las señales opuestas cierran inmediatamente las posiciones abiertas en la otra dirección antes de abrir nuevas operaciones.
El procesamiento de señales usa velas cerradas; los datos parciales son ignorados.
Marco temporal: Multi-marco temporal (base 1h, filtros Daily + 4h)
Estacionalidad: No
Redes neuronales: No
Divergencia: No
Nivel de riesgo: Medio
Notas
La estrategia requiere suscripciones de velas en tres marcos temporales distintos para evaluar los filtros y gestionar las salidas.
La detección de giros aproxima la lógica ZigZag de MetaTrader aplicando reglas mínimas de profundidad, desviación y backstep antes de actualizar los niveles de pivote.
Los volúmenes se pueden ajustar de forma independiente para afinar el tamaño del tramo de take-profit frente al tramo corriente.
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>
/// Bollinger Band Two MA ZigZag strategy (simplified). Uses EMA crossover
/// with Highest/Lowest channel for swing-based entries.
/// </summary>
public class BollingerBandTwoMaZigZagStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaFastLength;
private readonly StrategyParam<int> _emaSlowLength;
private readonly StrategyParam<int> _channelLength;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaFastLength
{
get => _emaFastLength.Value;
set => _emaFastLength.Value = value;
}
public int EmaSlowLength
{
get => _emaSlowLength.Value;
set => _emaSlowLength.Value = value;
}
public int ChannelLength
{
get => _channelLength.Value;
set => _channelLength.Value = value;
}
public BollingerBandTwoMaZigZagStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candles", "General");
_emaFastLength = Param(nameof(EmaFastLength), 10)
.SetGreaterThanZero()
.SetDisplay("EMA Fast", "Fast EMA period", "Indicators");
_emaSlowLength = Param(nameof(EmaSlowLength), 30)
.SetGreaterThanZero()
.SetDisplay("EMA Slow", "Slow EMA period", "Indicators");
_channelLength = Param(nameof(ChannelLength), 20)
.SetGreaterThanZero()
.SetDisplay("Channel Length", "Highest/Lowest lookback", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var emaFast = new ExponentialMovingAverage { Length = EmaFastLength };
var emaSlow = new ExponentialMovingAverage { Length = EmaSlowLength };
decimal prevFast = 0, prevSlow = 0;
var hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(emaFast, emaSlow, (ICandleMessage candle, decimal fastVal, decimal slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!hasPrev)
{
prevFast = fastVal;
prevSlow = slowVal;
hasPrev = true;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
prevFast = fastVal;
prevSlow = slowVal;
return;
}
var close = candle.ClosePrice;
// EMA crossover combined with price confirmation
var bullishCross = prevFast <= prevSlow && fastVal > slowVal;
var bearishCross = prevFast >= prevSlow && fastVal < slowVal;
if (bullishCross && Position <= 0 && close > fastVal)
BuyMarket();
else if (bearishCross && Position >= 0 && close < fastVal)
SellMarket();
prevFast = fastVal;
prevSlow = slowVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, emaFast);
DrawIndicator(area, emaSlow);
DrawOwnTrades(area);
}
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class bollinger_band_two_ma_zig_zag_strategy(Strategy):
def __init__(self):
super(bollinger_band_two_ma_zig_zag_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles", "General")
self._ema_fast_length = self.Param("EmaFastLength", 10) \
.SetDisplay("EMA Fast", "Fast EMA period", "Indicators")
self._ema_slow_length = self.Param("EmaSlowLength", 30) \
.SetDisplay("EMA Slow", "Slow EMA period", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaFastLength(self):
return self._ema_fast_length.Value
@property
def EmaSlowLength(self):
return self._ema_slow_length.Value
def OnReseted(self):
super(bollinger_band_two_ma_zig_zag_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(bollinger_band_two_ma_zig_zag_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
ema_fast = ExponentialMovingAverage()
ema_fast.Length = self.EmaFastLength
ema_slow = ExponentialMovingAverage()
ema_slow.Length = self.EmaSlowLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema_fast, ema_slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema_fast)
self.DrawIndicator(area, ema_slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if not self._has_prev:
self._prev_fast = fv
self._prev_slow = sv
self._has_prev = True
return
close = float(candle.ClosePrice)
bullish_cross = self._prev_fast <= self._prev_slow and fv > sv
bearish_cross = self._prev_fast >= self._prev_slow and fv < sv
if bullish_cross and self.Position <= 0 and close > fv:
self.BuyMarket()
elif bearish_cross and self.Position >= 0 and close < fv:
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def CreateClone(self):
return bollinger_band_two_ma_zig_zag_strategy()