Estratégia de Rebote na Linha de Tendência
Os mercados frequentemente respeitam as linhas de tendência traçadas através de máximas ou mínimas anteriores de swing. Esta estratégia ajusta automaticamente linhas de regressão à ação de preço recente e procura velas que ricocheteiem nessas linhas na direção da tendência dominante.
Os testes indicam um retorno anual médio de aproximadamente 124%. Funciona melhor no mercado de câmbio.
Velas recentes são armazenadas para calcular linhas de suporte e resistência inclinadas para cima ou para baixo. Quando o preço se aproxima de uma linha de tendência e uma vela confirma o rebote enquanto permanece no lado correto de uma média móvel, o sistema abre uma operação. O stop é definido usando um percentual do preço e a saída ocorre no cruzamento da média móvel.
Ao operar apenas na direção predominante e aguardar uma reação clara no suporte ou resistência, o método tenta capturar movimentos de continuação sem perseguir rompimentos.
Detalhes
- Critérios de entrada: O preço toca a linha de tendência calculada e a vela fecha na direção da tendência acima/abaixo da MA.
- Comprado/Vendido: Ambos.
- Critérios de saída: Preço cruzando a média móvel ou stop-loss.
- Stops: Sim, baseados em percentual.
- Valores padrão:
TrendlinePeriod= 20MAPeriod= 20BounceThresholdPercent= 0.5 Uma vela toca uma linha quando sua mínima (máxima) chega a menos dessa porcentagem da linha de suporte (resistência).CandleType= 5 minuteStopLossPercent= 2
- Filtros:
- Categoria: Seguidor de tendência
- Direção: Ambos
- Indicadores: MA, Trendlines
- Stops: Sim
- Complexidade: Avançado
- Período: Intradiário
- Sazonalidade: Não
- Redes neurais: Não
- Divergência: Não
- Nível de risco: Médio
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()