GitHub で見る

Pivot Point Supertrend 戦略

Pivot PointsとATRベースのSupertrendを組み合わせてトレンドの転換を捉える戦略です。

Pivot Pointsが動的な中心線を定義します。ATR乗数が価格に追従する上下バンドを形成します。トレンドが方向を転換すると、戦略はそれに応じてエントリーします。

詳細

  • エントリー条件: Pivot PointsとATR Supertrendに基づくシグナル。
  • ロング/ショート: 両方。
  • エグジット条件: 逆のシグナル。
  • ストップ: なし。
  • デフォルト値:
    • PivotPeriod = 2
    • AtrFactor = 3m
    • AtrPeriod = 10
    • CandleType = 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));
	}
}