Pivot Point SuperTrendトレンドフィルター戦略
ピボットベースのSuperTrend線をSuperTrendトレンドフィルターおよび移動平均による確認と組み合わせます。トレンドが転換したとき、または指定した日付ウィンドウ内にピボットSuperTrendシグナルが現れたときに取引します。
詳細
- エントリー条件:
- ロング:価格SuperTrendが上向きに転換し終値が終値SMAを上回る、またはピボットSuperTrendが日付範囲内で上向きに転換する。
- ショート:価格SuperTrendが下向きに転換し終値がSMAを下回る、またはピボットSuperTrendが日付範囲内で下向きに転換する。
- エグジット条件:
- いずれかの下向き転換でロングを全決済、上向き転換でショートを全決済する。SMAと日付は決済を制限しない。
- 反対方向のエントリー条件が成立した場合のみ反転する。同時に逆方向の転換が発生したら決済だけ行う。
- ストップ: なし
- デフォルト値:
PivotPeriod= 2Factor= 3AtrPeriod= 10TrendAtrPeriod= 10TrendMultiplier= 3MaPeriod= 20CandleType= 5分StartDate/EndDate= UTCDateTime.MinValue/DateTime.MaxValue
- フィルター:
- カテゴリ: トレンド
- 方向: 両方
- インジケーター: Pivot, SuperTrend, SMA
- ストップ: いいえ
- 複雑さ: 中程度
- 時間軸: 任意
- 季節性: オプション
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
計算と執行
確定足だけを使用する。ピボット高値・安値は左右各 PivotPeriod 本の高値・安値より厳密に高い・低い必要があり、右側の足がすべて確定した時点で確認する。同じ足が両方の条件を満たす場合は高値を優先する。最初のピボットで center を初期化し、以降は (2 * center + pivot) / 3 で更新する。
ピボットバンドはWilder ATRを使用する。最初の AtrPeriod 個のtrue rangeを平均し、以降は重み 1 / AtrPeriod で平滑化する。基本支持・抵抗は center ± Factor * ATR。前の終値が前の支持を上回る場合、支持は基本値と前値の最大値を保持し、それ以外は再設定する。前の終値が前の抵抗を下回る場合、抵抗は最小値を保持し、それ以外は再設定する。終値が前の抵抗を厳密に上回れば上向き、前の支持を厳密に下回れば下向き、それ以外は方向を維持する。初期方向は上向きで、初期化はシグナルではない。
独立した価格フィルターはStockSharpの標準SuperTrend(TrendAtrPeriod、TrendMultiplier)を使用する。SMAは現在の終値を含む。シグナルは方向転換だけであり、両ATR、SMA、ピボット中心の準備が必要。StartDate <= candle.OpenTime <= EndDate はピボットの新規エントリーだけに適用する。日付は順序の正しいUTC値とする。新規数量は Volume、反転注文は Volume + abs(Position)、決済は abs(Position)。待機時間や保護ストップはない。
ピボットの概念:LonesomeTheBlueのPivot Point Supertrend。上記が本例の双方向モデルの定義となる。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Confirmed pivot SuperTrend signals combined with a price SuperTrend and SMA entry filter.
/// </summary>
public class PivotPointSuperTrendTrendFilterStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _pivotPeriod;
private readonly StrategyParam<decimal> _factor;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _trendAtrPeriod;
private readonly StrategyParam<decimal> _trendMultiplier;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DateTime> _startDate;
private readonly StrategyParam<DateTime> _endDate;
private readonly List<(decimal High, decimal Low)> _pivots = [];
private decimal? _center;
private decimal? _support;
private decimal? _resistance;
private decimal? _previousClose;
private bool? _pivotUp;
private bool? _filterUp;
/// <summary>
/// Initializes the published indicator periods and an unrestricted UTC date window.
/// </summary>
public PivotPointSuperTrendTrendFilterStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of finished candles", "General");
_pivotPeriod = Param(nameof(PivotPeriod), 2).SetGreaterThanZero()
.SetDisplay("Pivot Period", "Finished candles on each side of a confirmed pivot", "Indicators");
_factor = Param(nameof(Factor), 3m).SetGreaterThanZero()
.SetDisplay("Factor", "ATR width of the pivot bands", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 10).SetGreaterThanZero()
.SetDisplay("ATR Period", "Wilder ATR period of the pivot bands", "Indicators");
_trendAtrPeriod = Param(nameof(TrendAtrPeriod), 10).SetGreaterThanZero()
.SetDisplay("Trend ATR Period", "Wilder ATR period of the price SuperTrend", "Indicators");
_trendMultiplier = Param(nameof(TrendMultiplier), 3m).SetGreaterThanZero()
.SetDisplay("Trend Multiplier", "ATR width of the price SuperTrend", "Indicators");
_maPeriod = Param(nameof(MaPeriod), 20).SetGreaterThanZero()
.SetDisplay("MA Period", "Close-price SMA period for trend-filter entries", "Indicators");
_startDate = Param(nameof(StartDate), DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc))
.SetDisplay("Start Date", "Inclusive UTC candle-open time for pivot entries", "General");
_endDate = Param(nameof(EndDate), DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc))
.SetDisplay("End Date", "Inclusive UTC candle-open time for pivot entries", "General");
}
/// <summary>
/// Candle type; five minutes by default.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Number of finished candles required on each side of a pivot.
/// </summary>
public int PivotPeriod { get => _pivotPeriod.Value; set => _pivotPeriod.Value = value; }
/// <summary>
/// ATR multiplier for the pivot bands.
/// </summary>
public decimal Factor { get => _factor.Value; set => _factor.Value = value; }
/// <summary>
/// Wilder ATR period for the pivot bands.
/// </summary>
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
/// <summary>
/// ATR period of the price SuperTrend filter.
/// </summary>
public int TrendAtrPeriod { get => _trendAtrPeriod.Value; set => _trendAtrPeriod.Value = value; }
/// <summary>
/// ATR multiplier of the price SuperTrend filter.
/// </summary>
public decimal TrendMultiplier { get => _trendMultiplier.Value; set => _trendMultiplier.Value = value; }
/// <summary>
/// Close-price SMA period for trend-filter entries.
/// </summary>
public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
/// <summary>
/// Inclusive lower UTC candle-open time for pivot entries.
/// </summary>
public DateTime StartDate { get => _startDate.Value; set => _startDate.Value = value; }
/// <summary>
/// Inclusive upper UTC candle-open time for pivot entries.
/// </summary>
public DateTime EndDate { get => _endDate.Value; set => _endDate.Value = value; }
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetSignals();
}
private void ResetSignals()
{
_pivots.Clear();
_center = _support = _resistance = _previousClose = null;
_pivotUp = _filterUp = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (StartDate.Kind != DateTimeKind.Utc || EndDate.Kind != DateTimeKind.Utc || StartDate > EndDate)
throw new ArgumentException("The pivot entry window requires ordered UTC dates.");
ResetSignals();
var atr = new AverageTrueRange { Length = AtrPeriod };
var trend = new SuperTrend { Length = TrendAtrPeriod, Multiplier = TrendMultiplier };
var average = new SimpleMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(atr, trend, average, ProcessCandle, allowEmpty: true).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, trend);
DrawIndicator(area, average);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue, IIndicatorValue trendValue, IIndicatorValue averageValue)
{
if (candle.State != CandleStates.Finished)
return;
var previousClose = _previousClose ?? candle.ClosePrice;
_previousClose = candle.ClosePrice;
_pivots.Add((candle.HighPrice, candle.LowPrice));
if (_pivots.Count > 2 * PivotPeriod + 1)
_pivots.RemoveAt(0);
if (_pivots.Count == 2 * PivotPeriod + 1)
{
var candidate = _pivots[PivotPeriod];
var isHigh = true;
var isLow = true;
for (var i = 0; i < _pivots.Count; i++)
{
if (i == PivotPeriod)
continue;
isHigh &= candidate.High > _pivots[i].High;
isLow &= candidate.Low < _pivots[i].Low;
}
if (isHigh || isLow)
{
var pivot = isHigh ? candidate.High : candidate.Low;
_center = _center is decimal center ? (2m * center + pivot) / 3m : pivot;
}
}
var previousPivotUp = _pivotUp;
if (_center is decimal pivotCenter && atrValue.IsFormed && !atrValue.IsEmpty)
{
var width = Factor * atrValue.ToDecimal();
var oldSupport = _support;
var oldResistance = _resistance;
_support = oldSupport is decimal support && previousClose > support ? Math.Max(pivotCenter - width, support) : pivotCenter - width;
_resistance = oldResistance is decimal resistance && previousClose < resistance ? Math.Min(pivotCenter + width, resistance) : pivotCenter + width;
_pivotUp = oldResistance is decimal upper && candle.ClosePrice > upper ? true
: oldSupport is decimal lower && candle.ClosePrice < lower ? false : _pivotUp ?? true;
}
var previousFilterUp = _filterUp;
if (trendValue is SuperTrendIndicatorValue { IsFormed: true, IsEmpty: false } filter)
_filterUp = filter.IsUpTrend;
if (_pivotUp is null || _filterUp is null || !averageValue.IsFormed || averageValue.IsEmpty || !IsFormedAndOnlineAndAllowTrading())
return;
var pivotBuy = previousPivotUp == false && _pivotUp == true;
var pivotSell = previousPivotUp == true && _pivotUp == false;
var filterBuy = previousFilterUp == false && _filterUp == true;
var filterSell = previousFilterUp == true && _filterUp == false;
var withinDates = candle.OpenTime >= StartDate && candle.OpenTime <= EndDate;
var upSignal = pivotBuy || filterBuy;
var downSignal = pivotSell || filterSell;
var averagePrice = averageValue.ToDecimal();
var enterLong = !downSignal && (filterBuy && candle.ClosePrice > averagePrice || pivotBuy && withinDates);
var enterShort = !upSignal && (filterSell && candle.ClosePrice < averagePrice || pivotSell && withinDates);
if (Position > 0m && downSignal)
SellMarket(Position + (enterShort ? Volume : 0m));
else if (Position < 0m && upSignal)
BuyMarket(-Position + (enterLong ? Volume : 0m));
else if (Position == 0m && enterLong)
BuyMarket(Volume);
else if (Position == 0m && enterShort)
SellMarket(Volume);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import ArgumentException, DateTime, DateTimeKind, Decimal, TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange, SimpleMovingAverage, SuperTrend
from StockSharp.Algo.Strategies import Strategy
class pivot_point_super_trend_trend_filter_strategy(Strategy):
def __init__(self):
super(pivot_point_super_trend_trend_filter_strategy, self).__init__()
self._pivot_period = self.Param("PivotPeriod", 2).SetGreaterThanZero() \
.SetDisplay("Pivot Period", "Finished candles on each side of a confirmed pivot", "Indicators")
self._factor = self.Param("Factor", Decimal.Parse("3")).SetGreaterThanZero() \
.SetDisplay("Factor", "ATR width of the pivot bands", "Indicators")
self._atr_period = self.Param("AtrPeriod", 10).SetGreaterThanZero() \
.SetDisplay("ATR Period", "Wilder ATR period of the pivot bands", "Indicators")
self._trend_atr_period = self.Param("TrendAtrPeriod", 10).SetGreaterThanZero() \
.SetDisplay("Trend ATR Period", "Wilder ATR period of the price SuperTrend", "Indicators")
self._trend_multiplier = self.Param("TrendMultiplier", Decimal.Parse("3")).SetGreaterThanZero() \
.SetDisplay("Trend Multiplier", "ATR width of the price SuperTrend", "Indicators")
self._ma_period = self.Param("MaPeriod", 20).SetGreaterThanZero() \
.SetDisplay("MA Period", "Close-price SMA period for trend-filter entries", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of finished candles", "General")
self._start_date = self.Param("StartDate", DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc)) \
.SetDisplay("Start Date", "Inclusive UTC candle-open time for pivot entries", "General")
self._end_date = self.Param("EndDate", DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc)) \
.SetDisplay("End Date", "Inclusive UTC candle-open time for pivot entries", "General")
self._reset_signals()
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def _reset_signals(self):
self._pivots = []
self._center = None
self._support = None
self._resistance = None
self._previous_close = None
self._pivot_up = None
self._filter_up = None
def OnReseted(self):
super(pivot_point_super_trend_trend_filter_strategy, self).OnReseted()
self._reset_signals()
def OnStarted2(self, time):
super(pivot_point_super_trend_trend_filter_strategy, self).OnStarted2(time)
start = self._start_date.Value
end = self._end_date.Value
if start.Kind != DateTimeKind.Utc or end.Kind != DateTimeKind.Utc or start > end:
raise ArgumentException("The pivot entry window requires ordered UTC dates.")
self._reset_signals()
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
trend = SuperTrend()
trend.Length = self._trend_atr_period.Value
trend.Multiplier = self._trend_multiplier.Value
average = SimpleMovingAverage()
average.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, trend, average, self.OnProcess, True).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, trend)
self.DrawIndicator(area, average)
self.DrawOwnTrades(area)
def OnProcess(self, candle, atr_value, trend_value, average_value):
if candle.State != CandleStates.Finished:
return
previous_close = candle.ClosePrice if self._previous_close is None else self._previous_close
self._previous_close = candle.ClosePrice
self._pivots.append((candle.HighPrice, candle.LowPrice))
period = self._pivot_period.Value
if len(self._pivots) > 2 * period + 1:
self._pivots.pop(0)
if len(self._pivots) == 2 * period + 1:
candidate = self._pivots[period]
neighbors = self._pivots[:period] + self._pivots[period + 1:]
is_high = all(candidate[0] > bar[0] for bar in neighbors)
is_low = all(candidate[1] < bar[1] for bar in neighbors)
if is_high or is_low:
pivot = candidate[0] if is_high else candidate[1]
self._center = pivot if self._center is None else (Decimal(2) * self._center + pivot) / Decimal(3)
previous_pivot_up = self._pivot_up
if self._center is not None and atr_value.IsFormed and not atr_value.IsEmpty:
width = self._factor.Value * atr_value.GetValue[Decimal](None)
old_support = self._support
old_resistance = self._resistance
basic_support = self._center - width
basic_resistance = self._center + width
self._support = max(basic_support, old_support) if old_support is not None and previous_close > old_support else basic_support
self._resistance = min(basic_resistance, old_resistance) if old_resistance is not None and previous_close < old_resistance else basic_resistance
if old_resistance is not None and candle.ClosePrice > old_resistance:
self._pivot_up = True
elif old_support is not None and candle.ClosePrice < old_support:
self._pivot_up = False
elif self._pivot_up is None:
self._pivot_up = True
previous_filter_up = self._filter_up
if trend_value.IsFormed and not trend_value.IsEmpty:
self._filter_up = bool(trend_value.IsUpTrend)
if self._pivot_up is None or self._filter_up is None or not average_value.IsFormed \
or average_value.IsEmpty or not self.IsFormedAndOnlineAndAllowTrading():
return
pivot_buy = previous_pivot_up is False and self._pivot_up
pivot_sell = previous_pivot_up is True and not self._pivot_up
filter_buy = previous_filter_up is False and self._filter_up
filter_sell = previous_filter_up is True and not self._filter_up
within_dates = self._start_date.Value <= candle.OpenTime <= self._end_date.Value
up_signal = pivot_buy or filter_buy
down_signal = pivot_sell or filter_sell
average_price = average_value.GetValue[Decimal](None)
enter_long = not down_signal and (filter_buy and candle.ClosePrice > average_price or pivot_buy and within_dates)
enter_short = not up_signal and (filter_sell and candle.ClosePrice < average_price or pivot_sell and within_dates)
if self.Position > 0 and down_signal:
self.SellMarket(self.Position + (self.Volume if enter_short else Decimal.Zero))
elif self.Position < 0 and up_signal:
self.BuyMarket(-self.Position + (self.Volume if enter_long else Decimal.Zero))
elif self.Position == 0 and enter_long:
self.BuyMarket(self.Volume)
elif self.Position == 0 and enter_short:
self.SellMarket(self.Volume)
def CreateClone(self):
return pivot_point_super_trend_trend_filter_strategy()