フォローライン戦略
この戦略は、オプションのATRオフセットを含むボリンジャーバンドのブレイクアウトから導出されたフォローラインを追跡します。ラインが方向を反転したときにエントリーし、オプションで上位時間軸のトレンドで確認します。
詳細
- エントリー条件: 価格がボリンジャーバンドをブレイクした後にフォローラインが方向を変え、オプションで上位時間軸の確認を使用。
- ロング/ショート: 両方。
- エグジット条件: フォローラインまたは上位時間軸のトレンドが反転。
- ストップ: なし。
- デフォルト値:
AtrPeriod= 5BbPeriod= 21BbDeviation= 1UseAtrFilter= trueUseTimeFilter= falseSession= "0000-2400"UseHtfConfirmation= falseCandleType= TimeSpan.FromMinutes(5)HtfCandleType= TimeSpan.FromHours(4)
- フィルター:
- カテゴリ: トレンド
- 方向: 両方
- インジケーター: Bollinger Bands, ATR
- ストップ: なし
- 複雑さ: 基本
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// Follow Line strategy.
/// A close above the upper Bollinger Band moves the follow line up to the candle low (minus ATR with UseAtrFilter) but never down;
/// a close below the lower band moves it down to the candle high (plus ATR) but never up; otherwise it stays. The trend turns up when
/// the line rises and down when it falls. A turn up goes long and a turn down goes short, reversing an opposite position. With
/// UseHtfConfirmation the same follow line on HtfCandleType must point the same way, and a position is closed when that trend turns
/// against it. With UseTimeFilter entries happen only inside Session ("HHmm-HHmm", UTC).
/// </summary>
public class FollowLineStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _bbPeriod;
private readonly StrategyParam<decimal> _bbDeviation;
private readonly StrategyParam<bool> _useAtrFilter;
private readonly StrategyParam<bool> _useTimeFilter;
private readonly StrategyParam<string> _session;
private readonly StrategyParam<bool> _useHtfConfirmation;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DataType> _htfCandleType;
private decimal? _line;
private int _trend;
private int _prevTrend;
private decimal? _htfLine;
private int _htfTrend;
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BbPeriod
{
get => _bbPeriod.Value;
set => _bbPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands deviation.
/// </summary>
public decimal BbDeviation
{
get => _bbDeviation.Value;
set => _bbDeviation.Value = value;
}
/// <summary>
/// Offset the follow line by ATR.
/// </summary>
public bool UseAtrFilter
{
get => _useAtrFilter.Value;
set => _useAtrFilter.Value = value;
}
/// <summary>
/// Enter only inside the session.
/// </summary>
public bool UseTimeFilter
{
get => _useTimeFilter.Value;
set => _useTimeFilter.Value = value;
}
/// <summary>
/// Trading session as "HHmm-HHmm" in UTC.
/// </summary>
public string Session
{
get => _session.Value;
set => _session.Value = value;
}
/// <summary>
/// Require the higher timeframe follow line to agree.
/// </summary>
public bool UseHtfConfirmation
{
get => _useHtfConfirmation.Value;
set => _useHtfConfirmation.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Higher timeframe candle type.
/// </summary>
public DataType HtfCandleType
{
get => _htfCandleType.Value;
set => _htfCandleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public FollowLineStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Indicators");
_bbPeriod = Param(nameof(BbPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");
_bbDeviation = Param(nameof(BbDeviation), 1m)
.SetGreaterThanZero()
.SetDisplay("BB Deviation", "Bollinger Bands deviation", "Indicators");
_useAtrFilter = Param(nameof(UseAtrFilter), true)
.SetDisplay("Use ATR Filter", "Offset the follow line by ATR", "Indicators");
_useTimeFilter = Param(nameof(UseTimeFilter), false)
.SetDisplay("Use Time Filter", "Enter only inside the session", "Time");
_session = Param(nameof(Session), "0000-2400")
.SetDisplay("Session", "Trading session as HHmm-HHmm in UTC", "Time");
_useHtfConfirmation = Param(nameof(UseHtfConfirmation), false)
.SetDisplay("HTF Confirmation", "Require the higher timeframe follow line to agree", "Higher Timeframe");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_htfCandleType = Param(nameof(HtfCandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("HTF Candle Type", "Higher timeframe candles", "Higher Timeframe");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (UseHtfConfirmation)
return [(Security, CandleType), (Security, HtfCandleType)];
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_line = null;
_trend = 0;
_prevTrend = 0;
_htfLine = null;
_htfTrend = 0;
}
private static decimal? NextLine(ICandleMessage candle, decimal? prevLine, decimal upper, decimal lower, decimal atr, bool useAtr)
{
if (candle.ClosePrice > upper)
{
var candidate = useAtr ? candle.LowPrice - atr : candle.LowPrice;
return prevLine is decimal p && candidate < p ? p : candidate;
}
if (candle.ClosePrice < lower)
{
var candidate = useAtr ? candle.HighPrice + atr : candle.HighPrice;
return prevLine is decimal p && candidate > p ? p : candidate;
}
return prevLine;
}
private static int NextTrend(int trend, decimal? prevLine, decimal? line)
{
if (line is decimal current && prevLine is decimal previous)
{
if (current > previous)
return 1;
if (current < previous)
return -1;
}
return trend;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var bollinger = new BollingerBands { Length = BbPeriod, Width = BbDeviation };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, atr, ProcessCandle)
.Start();
if (UseHtfConfirmation)
{
var htfBollinger = new BollingerBands { Length = BbPeriod, Width = BbDeviation };
var htfAtr = new AverageTrueRange { Length = AtrPeriod };
SubscribeCandles(HtfCandleType)
.BindEx(htfBollinger, htfAtr, ProcessHtfCandle)
.Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ProcessHtfCandle(ICandleMessage candle, IIndicatorValue bbValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bbValue.IsFormed || !atrValue.IsFormed || bbValue is not IBollingerBandsValue { UpBand: decimal upper, LowBand: decimal lower })
return;
var line = NextLine(candle, _htfLine, upper, lower, atrValue.ToDecimal(), UseAtrFilter);
_htfTrend = NextTrend(_htfTrend, _htfLine, line);
_htfLine = line;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bbValue.IsFormed || !atrValue.IsFormed || bbValue is not IBollingerBandsValue { UpBand: decimal upper, LowBand: decimal lower })
return;
var line = NextLine(candle, _line, upper, lower, atrValue.ToDecimal(), UseAtrFilter);
_prevTrend = _trend;
_trend = NextTrend(_trend, _line, line);
_line = line;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var htfTrend = _htfTrend;
if (UseHtfConfirmation)
{
if (Position > 0 && htfTrend < 0)
{
SellMarket(Position);
return;
}
if (Position < 0 && htfTrend > 0)
{
BuyMarket(-Position);
return;
}
}
var turnUp = _prevTrend == -1 && _trend == 1;
var turnDown = _prevTrend == 1 && _trend == -1;
if (!turnUp && !turnDown)
return;
var canEnter = !UseTimeFilter || InSession(candle.OpenTime);
if (turnUp)
{
if (canEnter && (!UseHtfConfirmation || htfTrend > 0) && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (Position < 0)
BuyMarket(-Position);
}
else
{
if (canEnter && (!UseHtfConfirmation || htfTrend < 0) && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0)
SellMarket(Position);
}
}
private bool InSession(DateTime time)
{
var parts = (Session ?? string.Empty).Split('-');
if (parts.Length != 2 || !int.TryParse(parts[0], out var start) || !int.TryParse(parts[1], out var end))
return true;
var startMinutes = start / 100 * 60 + start % 100;
var endMinutes = end / 100 * 60 + end % 100;
var minutes = time.Hour * 60 + time.Minute;
return startMinutes <= endMinutes
? minutes >= startMinutes && minutes < endMinutes
: minutes >= startMinutes || minutes < endMinutes;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class _follow_line(object):
def __init__(self):
self.reset()
def reset(self):
self._line = None
self.trend = 0
self.prev_trend = 0
def update(self, candle, upper, lower, atr, use_atr):
prev_line = self._line
line = prev_line
close = float(candle.ClosePrice)
if close > upper:
candidate = float(candle.LowPrice) - atr if use_atr else float(candle.LowPrice)
line = prev_line if prev_line is not None and candidate < prev_line else candidate
elif close < lower:
candidate = float(candle.HighPrice) + atr if use_atr else float(candle.HighPrice)
line = prev_line if prev_line is not None and candidate > prev_line else candidate
self.prev_trend = self.trend
if line is not None and prev_line is not None:
if line > prev_line:
self.trend = 1
elif line < prev_line:
self.trend = -1
self._line = line
class follow_line_strategy(Strategy):
"""
Follow Line strategy.
A close above the upper Bollinger Band moves the follow line up to the candle low (minus ATR with UseAtrFilter) but never down;
a close below the lower band moves it down to the candle high (plus ATR) but never up; otherwise it stays. The trend turns up when
the line rises and down when it falls. A turn up goes long and a turn down goes short, reversing an opposite position. With
UseHtfConfirmation the same follow line on HtfCandleType must point the same way, and a position is closed when that trend turns
against it. With UseTimeFilter entries happen only inside Session ("HHmm-HHmm", UTC).
"""
def __init__(self):
super(follow_line_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 5).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "Indicators")
self._bb_period = self.Param("BbPeriod", 21).SetGreaterThanZero().SetDisplay("BB Period", "Bollinger Bands period", "Indicators")
self._bb_deviation = self.Param("BbDeviation", 1.0).SetGreaterThanZero().SetDisplay("BB Deviation", "Bollinger Bands deviation", "Indicators")
self._use_atr_filter = self.Param("UseAtrFilter", True).SetDisplay("Use ATR Filter", "Offset the follow line by ATR", "Indicators")
self._use_time_filter = self.Param("UseTimeFilter", False).SetDisplay("Use Time Filter", "Enter only inside the session", "Time")
self._session = self.Param("Session", "0000-2400").SetDisplay("Session", "Trading session as HHmm-HHmm in UTC", "Time")
self._use_htf_confirmation = self.Param("UseHtfConfirmation", False).SetDisplay("HTF Confirmation", "Require the higher timeframe follow line to agree", "Higher Timeframe")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._htf_candle_type = self.Param("HtfCandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("HTF Candle Type", "Higher timeframe candles", "Higher Timeframe")
self._line = _follow_line()
self._htf_line = _follow_line()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
if self._use_htf_confirmation.Value:
return [(self.Security, self.candle_type), (self.Security, self._htf_candle_type.Value)]
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(follow_line_strategy, self).OnReseted()
self._line.reset()
self._htf_line.reset()
def OnStarted2(self, time):
super(follow_line_strategy, self).OnStarted2(time)
self._line.reset()
self._htf_line.reset()
bollinger = BollingerBands()
bollinger.Length = self._bb_period.Value
bollinger.Width = Decimal(self._bb_deviation.Value)
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, atr, self._process_candle).Start()
if self._use_htf_confirmation.Value:
htf_bollinger = BollingerBands()
htf_bollinger.Length = self._bb_period.Value
htf_bollinger.Width = Decimal(self._bb_deviation.Value)
htf_atr = AverageTrueRange()
htf_atr.Length = self._atr_period.Value
self.SubscribeCandles(self._htf_candle_type.Value).BindEx(htf_bollinger, htf_atr, self._process_htf_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
def _bands(self, bb_value, atr_value):
if not bb_value.IsFormed or not atr_value.IsFormed:
return None
if bb_value.UpBand is None or bb_value.LowBand is None:
return None
return float(bb_value.UpBand), float(bb_value.LowBand), float(atr_value.GetValue[Decimal](None))
def _process_htf_candle(self, candle, bb_value, atr_value):
if candle.State != CandleStates.Finished:
return
bands = self._bands(bb_value, atr_value)
if bands is None:
return
self._htf_line.update(candle, bands[0], bands[1], bands[2], self._use_atr_filter.Value)
def _process_candle(self, candle, bb_value, atr_value):
if candle.State != CandleStates.Finished:
return
bands = self._bands(bb_value, atr_value)
if bands is None:
return
self._line.update(candle, bands[0], bands[1], bands[2], self._use_atr_filter.Value)
if not self.IsFormedAndOnlineAndAllowTrading():
return
use_htf = self._use_htf_confirmation.Value
htf_trend = self._htf_line.trend
if use_htf:
if self.Position > 0 and htf_trend < 0:
self.SellMarket(self.Position)
return
if self.Position < 0 and htf_trend > 0:
self.BuyMarket(-self.Position)
return
turn_up = self._line.prev_trend == -1 and self._line.trend == 1
turn_down = self._line.prev_trend == 1 and self._line.trend == -1
if not turn_up and not turn_down:
return
can_enter = not self._use_time_filter.Value or self._in_session(candle.OpenTime)
if turn_up:
if can_enter and (not use_htf or htf_trend > 0) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif self.Position < 0:
self.BuyMarket(-self.Position)
else:
if can_enter and (not use_htf or htf_trend < 0) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0:
self.SellMarket(self.Position)
def _in_session(self, time):
parts = str(self._session.Value or "").split("-")
try:
start = int(parts[0])
end = int(parts[1])
except (ValueError, IndexError):
return True
if len(parts) != 2:
return True
start_minutes = start // 100 * 60 + start % 100
end_minutes = end // 100 * 60 + end % 100
minutes = time.Hour * 60 + time.Minute
if start_minutes <= end_minutes:
return start_minutes <= minutes < end_minutes
return minutes >= start_minutes or minutes < end_minutes
def CreateClone(self):
return follow_line_strategy()