Стратегия отскока от трендовой линии
Рынки часто реагируют на трендовые линии, проведённые через предыдущие экстремумы. Эта стратегия автоматически строит линии регрессии по последним данным и ищет свечи, которые отскакивают от них в направлении доминирующего тренда.
Тестирование показывает среднегодичную доходность около 124%. Стратегию лучше запускать на рынке Форекс.
Свежие данные используются для расчёта восходящих или нисходящих линий поддержки и сопротивления. Когда цена приближается к трендовой линии и свеча подтверждает отскок, оставаясь по нужную сторону скользящей средней, система входит в сделку. Стоп устанавливается в процентах от цены, выход осуществляется при пересечении скользящей средней.
Торгуя только по основному направлению и ожидая явной реакции от поддержки или сопротивления, метод стремится захватить продолжение движения без погони за пробоями.
Детали
- Условия входа: Цена касается вычисленной трендовой линии и свеча закрывается в направлении тренда выше/ниже MA.
- Лонг/Шорт: Оба.
- Условия выхода: Цена пересекает скользящую среднюю или стоп‑лосс.
- Стопы: Да, на процентной основе.
- Значения по умолчанию:
TrendlinePeriod= 20MAPeriod= 20BounceThresholdPercent= 0.5 Свеча считается касанием линии, если её минимум (максимум) подходит к линии поддержки (сопротивления) ближе этого числа процентов.CandleType= 5 минутStopLossPercent= 2
- Фильтры:
- Категория: Следование тренду
- Направление: Оба
- Индикаторы: MA, трендовые линии
- Стопы: Да
- Сложность: Продвинутая
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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()