Estrategia de Rebote en Línea de Tendencia
Los mercados suelen respetar las líneas de tendencia trazadas a través de máximos o mínimos anteriores del swing. Esta estrategia ajusta automáticamente líneas de regresión a la acción reciente del precio y busca velas que reboten desde esas líneas en la dirección de la tendencia dominante.
Las pruebas indican un rendimiento anual promedio de aproximadamente el 124%. Funciona mejor en el mercado de divisas.
Las velas recientes se almacenan para calcular líneas de soporte y resistencia de pendiente ascendente o descendente. Cuando el precio se acerca a una línea de tendencia y una vela confirma el rebote mientras permanece en el lado correcto de una media móvil, el sistema abre una operación. El stop se establece usando un porcentaje del precio y la salida ocurre en un cruce de la media móvil.
Al operar solo en la dirección predominante y esperar una reacción clara en soporte o resistencia, el método intenta capturar movimientos de continuación sin perseguir rupturas.
Detalles
- Criterios de entrada: El precio toca la línea de tendencia calculada y la vela cierra en la dirección de la tendencia por encima/debajo de la MA.
- Largo/Corto: Ambos.
- Criterios de salida: Precio cruzando la media móvil o stop-loss.
- Stops: Sí, basados en porcentaje.
- Valores predeterminados:
TrendlinePeriod= 20MAPeriod= 20BounceThresholdPercent= 0.5 Una vela toca una línea cuando su mínimo (máximo) queda a menos de este porcentaje de la línea de soporte (resistencia).CandleType= 5 minuteStopLossPercent= 2
- Filtros:
- Categoría: Seguimiento de tendencia
- Dirección: Ambos
- Indicadores: MA, Trendlines
- Stops: Sí
- Complejidad: Avanzado
- Marco temporal: Intradía
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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>
/// Trendline Bounce strategy.
/// Support is the regression line of the lows of the previous TrendlinePeriod candles and resistance that of their highs,
/// both extended to the current candle. While flat, a bullish candle whose low comes within BounceThresholdPercent of a rising
/// support and that closes above the moving average buys; a bearish candle at a falling resistance closing below it sells.
/// A cross of the moving average or a percent stop closes the position.
/// </summary>
public class TrendlineBounceStrategy : Strategy
{
private readonly StrategyParam<int> _trendlinePeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _bounceThresholdPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = [];
private readonly List<decimal> _lows = [];
/// <summary>
/// Number of previous candles the trendlines are fitted to.
/// </summary>
public int TrendlinePeriod
{
get => _trendlinePeriod.Value;
set => _trendlinePeriod.Value = value;
}
/// <summary>
/// Moving average period.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// How close to a trendline the candle must come, in percent of the line.
/// </summary>
public decimal BounceThresholdPercent
{
get => _bounceThresholdPercent.Value;
set => _bounceThresholdPercent.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public TrendlineBounceStrategy()
{
_trendlinePeriod = Param(nameof(TrendlinePeriod), 20)
.SetRange(2, 1000)
.SetDisplay("Trendline Period", "Previous candles the trendlines are fitted to", "Indicators");
_maPeriod = Param(nameof(MAPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for SMA", "Indicators");
_bounceThresholdPercent = Param(nameof(BounceThresholdPercent), 0.5m)
.SetNotNegative()
.SetDisplay("Bounce Threshold %", "How close to a trendline the candle must come", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highs.Clear();
_lows.Clear();
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sma, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
// The lines are fitted to the candles before this one.
var ready = _highs.Count == TrendlinePeriod;
var (supportSlope, support) = ready ? FitAndExtend(_lows) : default;
var (resistanceSlope, resistance) = ready ? FitAndExtend(_highs) : default;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > TrendlinePeriod)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
if (!ready || !smaValue.IsFormed || !IsFormedAndOnlineAndAllowTrading())
return;
var ma = smaValue.GetValue<decimal>();
var close = candle.ClosePrice;
if (Position > 0)
{
if (close < ma)
SellMarket(Position);
return;
}
if (Position < 0)
{
if (close > ma)
BuyMarket(-Position);
return;
}
var threshold = BounceThresholdPercent / 100m;
if (supportSlope > 0 && candle.LowPrice <= support * (1 + threshold) && close > candle.OpenPrice && close > ma)
BuyMarket(Volume);
else if (resistanceSlope < 0 && candle.HighPrice >= resistance * (1 - threshold) && close < candle.OpenPrice && close < ma)
SellMarket(Volume);
}
private static (decimal Slope, decimal Next) FitAndExtend(List<decimal> values)
{
// Least squares over x = 0..n-1, extended to x = n.
var n = values.Count;
decimal sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
for (var i = 0; i < n; i++)
{
sumX += i;
sumY += values[i];
sumXY += i * values[i];
sumX2 += i * i;
}
var slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
var intercept = (sumY - slope * sumX) / n;
return (slope, intercept + slope * n);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class trendline_bounce_strategy(Strategy):
"""
Trendline Bounce strategy.
Support is the regression line of the lows of the previous TrendlinePeriod candles and resistance that of their highs,
both extended to the current candle. While flat, a bullish candle whose low comes within BounceThresholdPercent of a rising
support and that closes above the moving average buys; a bearish candle at a falling resistance closing below it sells.
A cross of the moving average or a percent stop closes the position.
"""
def __init__(self):
super(trendline_bounce_strategy, self).__init__()
self._trendline_period = self.Param("TrendlinePeriod", 20).SetRange(2, 1000).SetDisplay("Trendline Period", "Previous candles the trendlines are fitted to", "Indicators")
self._ma_period = self.Param("MAPeriod", 20).SetGreaterThanZero().SetDisplay("MA Period", "Period for SMA", "Indicators")
self._bounce_threshold_percent = self.Param("BounceThresholdPercent", 0.5).SetNotNegative().SetDisplay("Bounce Threshold %", "How close to a trendline the candle must come", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._highs = []
self._lows = []
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(trendline_bounce_strategy, self).OnReseted()
self._highs = []
self._lows = []
def OnStarted2(self, time):
super(trendline_bounce_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sma, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
# The lines are fitted to the candles before this one.
period = self._trendline_period.Value
ready = len(self._highs) == period
if ready:
support_slope, support = self._fit_and_extend(self._lows)
resistance_slope, resistance = self._fit_and_extend(self._highs)
self._highs.append(candle.HighPrice)
self._lows.append(candle.LowPrice)
if len(self._highs) > period:
self._highs.pop(0)
self._lows.pop(0)
if not ready or not sma_value.IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
ma = sma_value.GetValue[Decimal](None)
close = candle.ClosePrice
if self.Position > 0:
if close < ma:
self.SellMarket(self.Position)
return
if self.Position < 0:
if close > ma:
self.BuyMarket(-self.Position)
return
threshold = Decimal(self._bounce_threshold_percent.Value) / Decimal(100)
if support_slope > 0 and candle.LowPrice <= support * (Decimal(1) + threshold) and close > candle.OpenPrice and close > ma:
self.BuyMarket(self.Volume)
elif resistance_slope < 0 and candle.HighPrice >= resistance * (Decimal(1) - threshold) and close < candle.OpenPrice and close < ma:
self.SellMarket(self.Volume)
@staticmethod
def _fit_and_extend(values):
# Least squares over x = 0..n-1, extended to x = n.
n = len(values)
sum_x = Decimal(0)
sum_y = Decimal(0)
sum_xy = Decimal(0)
sum_x2 = Decimal(0)
for i in range(n):
x = Decimal(i)
sum_x += x
sum_y += values[i]
sum_xy += x * values[i]
sum_x2 += x * x
count = Decimal(n)
slope = (count * sum_xy - sum_x * sum_y) / (count * sum_x2 - sum_x * sum_x)
intercept = (sum_y - slope * sum_x) / count
return slope, intercept + slope * count
def CreateClone(self):
return trendline_bounce_strategy()