Pivot Point Supertrend 戦略
Pivot PointsとATRベースのSupertrendを組み合わせてトレンドの転換を捉える戦略です。
Pivot Pointsが動的な中心線を定義します。ATR乗数が価格に追従する上下バンドを形成します。トレンドが方向を転換すると、戦略はそれに応じてエントリーします。
詳細
- エントリー条件: Pivot PointsとATR Supertrendに基づくシグナル。
- ロング/ショート: 両方。
- エグジット条件: 逆のシグナル。
- ストップ: なし。
- デフォルト値:
PivotPeriod= 2AtrFactor= 3mAtrPeriod= 10CandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: トレンド
- 方向: 両方
- インジケーター: Pivot Points, ATR
- ストップ: いいえ
- 複雑さ: 基本
- 時間軸: イントラデイ (5m)
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
計算と執行
確定足だけを使用する。ピボット高値・安値は左右各 PivotPeriod 本の高値・安値より厳密に高い・低い必要があり、右側の足がすべて確定した時点で確認する。同じ足が両方の条件を満たす場合は高値を優先する。最初のピボットで center を初期化し、以降は (2 * center + pivot) / 3 で更新する。
ピボットバンドはWilder ATRを使用する。最初の AtrPeriod 個のtrue rangeを平均し、以降は重み 1 / AtrPeriod で平滑化する。基本支持・抵抗は center ± AtrFactor * ATR。前の終値が前の支持を上回る場合、支持は基本値と前値の最大値を保持し、それ以外は再設定する。前の終値が前の抵抗を下回る場合、抵抗は最小値を保持し、それ以外は再設定する。終値が前の抵抗を厳密に上回れば上向き、前の支持を厳密に下回れば下向き、それ以外は方向を維持する。初期方向は上向きで、初期化はシグナルではない。
Wilder ATRと確認済みピボット中心の準備が必要。ピボットバンドの方向転換だけでエントリーし、上向きでロング、下向きでショートとなる。初期方向は上向きで、初期化では発注しない。反対シグナルごとに数量 Volume + abs(Position) の1件の成行注文で全反転する。保有なしの新規注文は Volume。SMA、別の価格SuperTrend、日付フィルター、待機時間、保護ストップはない。
ピボットの概念: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>
/// Trades direction changes of ATR bands around a center of confirmed swing pivots.
/// </summary>
public class PivotPointSupertrendStrategy : Strategy
{
private readonly StrategyParam<int> _pivotPeriod;
private readonly StrategyParam<decimal> _atrFactor;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal High, decimal Low)> _pivots = [];
private decimal? _center;
private decimal? _support;
private decimal? _resistance;
private decimal? _previousClose;
private bool? _pivotUp;
/// <summary>
/// Initializes the published pivot period, ATR settings and five-minute candles.
/// </summary>
public PivotPointSupertrendStrategy()
{
_pivotPeriod = Param(nameof(PivotPeriod), 2).SetGreaterThanZero()
.SetDisplay("Pivot Period", "Finished candles on each side of a confirmed pivot", "Indicators");
_atrFactor = Param(nameof(AtrFactor), 3m).SetGreaterThanZero()
.SetDisplay("ATR 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");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of finished candles", "General");
}
/// <summary>
/// Number of finished candles required on each side of a pivot.
/// </summary>
public int PivotPeriod { get => _pivotPeriod.Value; set => _pivotPeriod.Value = value; }
/// <summary>
/// Wilder ATR multiplier for the pivot bands.
/// </summary>
public decimal AtrFactor { get => _atrFactor.Value; set => _atrFactor.Value = value; }
/// <summary>
/// Wilder ATR period.
/// </summary>
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
/// <summary>
/// Candle type; five minutes by default.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.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 = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetSignals();
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(atr, ProcessCandle, allowEmpty: true).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue)
{
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 = AtrFactor * 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;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (previousPivotUp == false && _pivotUp == true && Position <= 0m)
BuyMarket(Volume + Math.Abs(Position));
else if (previousPivotUp == true && _pivotUp == false && Position >= 0m)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Decimal, TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class pivot_point_supertrend_strategy(Strategy):
def __init__(self):
super(pivot_point_supertrend_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._atr_factor = self.Param("AtrFactor", Decimal.Parse("3")).SetGreaterThanZero() \
.SetDisplay("ATR 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._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of finished candles", "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
def OnReseted(self):
super(pivot_point_supertrend_strategy, self).OnReseted()
self._reset_signals()
def OnStarted2(self, time):
super(pivot_point_supertrend_strategy, self).OnStarted2(time)
self._reset_signals()
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, self.OnProcess, True).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def OnProcess(self, candle, atr_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._atr_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
if not self.IsFormedAndOnlineAndAllowTrading():
return
if previous_pivot_up is False and self._pivot_up and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif previous_pivot_up is True and not self._pivot_up and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return pivot_point_supertrend_strategy()