趋势线回调策略
市场往往会尊重通过先前波峰或波谷绘制的趋势线。本策略自动对近期价格动作拟合回归线,并在价格靠近这些趋势线并出现反弹且符合主趋势方向时入场。
测试表明年均收益约为 124%,该策略在外汇市场表现最佳。
系统存储近期K线,计算向上或向下倾斜的支撑和阻力线。当价格接近趋势线且蜡烛在移动平均线正确一侧确认反弹时,进入交易。止损按价格百分比设置,移动平均线被突破时离场。
通过只在主趋势方向交易并等待在支撑或阻力处的明确反应,该方法力求捕捉延续走势而不是追逐突破。
细节
- 入场条件:价格触及计算出的趋势线并在移动平均线上方/下方以趋势方向收盘。
- 多/空:双向。
- 退出条件:价格穿越移动平均线或止损。
- 止损:是,按百分比。
- 默认值:
TrendlinePeriod= 20MAPeriod= 20BounceThresholdPercent= 0.5 当K线最低价(最高价)与支撑线(阻力线)的距离在该百分比以内时,视为触及该线。CandleType= 5 分钟StopLossPercent= 2
- 过滤条件:
- 类别: 趋势跟随
- 方向: 双向
- 指标: 均线, 趋势线
- 止损: 有
- 复杂度: 高级
- 时间框架: 日内
- 季节性: 无
- 神经网络: 无
- 背离: 无
- 风险级别: 中等
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()