Estrategia de Reversión de Momentum de Cuatro Barras
La estrategia de Reversión de Momentum de Cuatro Barras entra en largo cuando el cierre ha estado por debajo del cierre de hace Lookback barras durante al menos BuyThreshold velas consecutivas dentro de la ventana de tiempo seleccionada. La posición se cierra una vez que el precio rompe por encima del máximo de la vela anterior.
Detalles
- Criterios de entrada:
BuyThresholdcierres consecutivos por debajo del cierre de haceLookbackbarras dentro de la ventana de tiempo. - Criterios de salida: Precio de cierre mayor que el máximo de la vela anterior.
- Stops: Ninguno.
- Valores predeterminados:
BuyThreshold= 4Lookback= 4StartTime= 2014-01-01EndTime= 2099-01-01
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Four Bar Momentum Reversal Strategy.
/// Enters long after consecutive closes below the close from N bars ago.
/// Exits on breakout above previous high.
/// Uses EMA as trend filter.
/// </summary>
public class FourBarMomentumReversalStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _buyThreshold;
private readonly StrategyParam<int> _lookback;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _cooldownBars;
private ExponentialMovingAverage _ema;
private readonly List<decimal> _closes = new();
private int _belowCount;
private decimal _prevHigh;
private int _cooldownRemaining;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int BuyThreshold { get => _buyThreshold.Value; set => _buyThreshold.Value = value; }
public int Lookback { get => _lookback.Value; set => _lookback.Value = value; }
public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public FourBarMomentumReversalStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_buyThreshold = Param(nameof(BuyThreshold), 3)
.SetGreaterThanZero()
.SetDisplay("Buy Threshold", "Consecutive closes below reference to trigger buy", "Strategy");
_lookback = Param(nameof(Lookback), 4)
.SetGreaterThanZero()
.SetDisplay("Lookback", "Number of bars to compare", "Strategy");
_emaLength = Param(nameof(EmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA trend filter period", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 15)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ema = null;
_closes.Clear();
_belowCount = 0;
_prevHigh = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!_ema.IsFormed)
{
_closes.Add(candle.ClosePrice);
_prevHigh = candle.HighPrice;
return;
}
var close = candle.ClosePrice;
// Track past closes for lookback comparison
if (_closes.Count >= Lookback)
{
var pastClose = _closes[_closes.Count - Lookback];
if (close < pastClose)
_belowCount++;
else
_belowCount = 0;
}
_closes.Add(close);
// Keep list from growing too large
if (_closes.Count > Lookback + 10)
_closes.RemoveAt(0);
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevHigh = candle.HighPrice;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevHigh = candle.HighPrice;
return;
}
// Buy: consecutive closes below reference + price below EMA (reversal from weakness)
if (_belowCount >= BuyThreshold && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: breakout above previous high
else if (Position > 0 && close > _prevHigh)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Short: consecutive closes above reference (overbought reversal)
else if (_belowCount == 0 && _closes.Count > Lookback)
{
var pastClose = _closes[_closes.Count - 1 - Lookback];
var aboveCount = 0;
for (int i = _closes.Count - 1; i >= Math.Max(0, _closes.Count - BuyThreshold); i--)
{
if (_closes[i] > pastClose)
aboveCount++;
else
break;
}
if (aboveCount >= BuyThreshold && Position >= 0 && close > emaVal)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
}
_prevHigh = candle.HighPrice;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class four_bar_momentum_reversal_strategy(Strategy):
"""Four Bar Momentum Reversal Strategy."""
def __init__(self):
super(four_bar_momentum_reversal_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._buy_threshold = self.Param("BuyThreshold", 3) \
.SetDisplay("Buy Threshold", "Consecutive closes below reference to trigger buy", "Strategy")
self._lookback = self.Param("Lookback", 4) \
.SetDisplay("Lookback", "Number of bars to compare", "Strategy")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "EMA trend filter period", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 15) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._ema = None
self._closes = []
self._below_count = 0
self._prev_high = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(four_bar_momentum_reversal_strategy, self).OnReseted()
self._ema = None
self._closes = []
self._below_count = 0
self._prev_high = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(four_bar_momentum_reversal_strategy, self).OnStarted2(time)
self._ema = ExponentialMovingAverage()
self._ema.Length = int(self._ema_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
if not self._ema.IsFormed:
self._closes.append(float(candle.ClosePrice))
self._prev_high = float(candle.HighPrice)
return
close = float(candle.ClosePrice)
lookback = int(self._lookback.Value)
# Track past closes for lookback comparison
if len(self._closes) >= lookback:
past_close = self._closes[len(self._closes) - lookback]
if close < past_close:
self._below_count += 1
else:
self._below_count = 0
self._closes.append(close)
if len(self._closes) > lookback + 10:
self._closes.pop(0)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_high = float(candle.HighPrice)
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_high = float(candle.HighPrice)
return
buy_threshold = int(self._buy_threshold.Value)
cooldown = int(self._cooldown_bars.Value)
ema_v = float(ema_val)
# Buy: consecutive closes below reference
if self._below_count >= buy_threshold and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
# Exit long: breakout above previous high
elif self.Position > 0 and close > self._prev_high:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
# Short: consecutive closes above reference (overbought reversal)
elif self._below_count == 0 and len(self._closes) > lookback:
past_close = self._closes[len(self._closes) - 1 - lookback]
above_count = 0
start = len(self._closes) - 1
end = max(0, len(self._closes) - buy_threshold)
for i in range(start, end - 1, -1):
if self._closes[i] > past_close:
above_count += 1
else:
break
if above_count >= buy_threshold and self.Position >= 0 and close > ema_v:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
self._prev_high = float(candle.HighPrice)
def CreateClone(self):
return four_bar_momentum_reversal_strategy()