GitHub で見る

4026 – ピボット戦略

概要

この戦略は、MQL/8550 にある MetaTrader 4 ファイル (ピボット インジケーターおよび付随する Pivots_test エキスパート アドバイザ) を StockSharp の高レベルの Strategy API に移植します。毎日のフロアピボットレベルを計算し、中央ピボットで一対の反対の未決注文をステージングし、固定のストップロス、テイクプロフィット、およびトレーリングストップで結果の各ポジションを管理するという元の動作が維持されます。

ピボット計算

  1. この戦略は、構成可能な ピボット タイムフレーム (PivotCandleType、デフォルトでは毎日) をサブスクライブします。
  2. その時間枠のローソク足が終了するたびに、前日の OHLC 価格から従来のフロアピボットレベルが導出されます。
    • Pivot = (High + Low + Close) / 3
    • R1 = 2 × Pivot − Low
    • S1 = 2 × Pivot − High
    • R2 = Pivot + (High − Low)S2 = Pivot − (High − Low)
    • R3 = 2 × Pivot + High − 2 × LowS3 = 2 × Pivot − (2 × High − Low)
  3. レベルは次のセッションの開始時にアクティブになります。これが発生すると、戦略は AddInfoLog を通じて値をログに記録します (例: Pivot levels for 2024-04-05: P=1.0924, R1=1.0956, …)。

保留中の注文のワークフロー

ピボット レベルがアクティブになると、戦略はピボット価格で 2 つの未決注文が存在することを継続的に保証します。

  • 買い指値 @ PivotS2 のポストフィル保護 SellStop (ストップロス) および R2SellLimit (テイクプロフィット)。
  • 売りストップ @ Pivot、ポストフィル保護付き BuyStopR2)および BuyLimitS2)。

すべての注文は、高レベルのヘルパー メソッド BuyLimitSellStopSellLimit、および BuyStop を介して送信されます。注文が約定すると、コードはその方向の平均エントリー価格を再計算し、既存の保護注文をキャンセルし、オープンボリュ​​ーム全体をカバーする新しいストップ/リミットのペアを送信します(各ポジションが同じ S2/R2 プロテクションを継承する MetaTrader の動作を反映しています)。保護ストップまたはテイクプロフィットが実行されると、関連するヘルパーは自動的にクリアされます。

この戦略では単一のネット ポジションを使用するため、反対の約定は互いに相殺されます (MetaTrader のチケットベースのヘッジとは異なります)。これは、元の専門家からの唯一の意図的な逸脱です。

トレーリングストップロジック

  • TrailingStopPoints は、インジケーターポイントでの距離を定義します (楽器 PriceStep で乗算)。
  • ロングポジションの場合、価格が平均エントリーを上回ってその距離を超えて移動すると、トレーリングストップがアクティブになります。その後、保護用の SellStop が市場の近くに移動されます。
  • ショート ポジションの場合はミラー ロジックが適用され、価格が有利に推移するにつれて BuyStop が下がります。
  • 追跡更新は、CandleType を通じて選択された日中シリーズ (デフォルトでは 15 分足のローソク足) によって駆動されます。

パラメーター

パラメータ 説明 デフォルト
OrderVolume 各未決注文のボリューム (ロット/契約)。 0.1
TrailingStopPoints トレーリングストップの距離 (ポイント単位)。 0 は後続ロジックを無効にします。 30
CandleType トレーリングとセッションスケジュールの維持に使用される日中のローソク足シリーズ。 15m の時間枠
PivotCandleType 毎日のピボット レベルを計算するために使用される時間枠。 1D の時間枠
LogPivotUpdates true の場合、ピボット レベルは変更されるたびに戦略ログに書き込まれます。 true

すべての数値パラメータは StrategyParam<T> を通じて公開されるため、StockSharp インフラストラクチャ内で最適化できます。

ロギングと診断

  • ピボット更新は AddInfoLog を通じてルーティングされ、MetaTrader Comment/ObjectSetText 出力を置き換えます。
  • 保護注文管理、ポジション処理、トレーリング ロジックは、StockSharp の高レベル ヘルパーのみに依存します。低レベルの注文登録や指標バッファーは使用されません。

使用上の注意

  1. 選択した証券の日足と日中の両方のローソク足を提供するコネクターに戦略を接続します。
  2. 必要に応じて、機器のステップを調整します(PriceStep は自動検出されます。フォールバックは 0.0001 です)。
  3. 必要に応じて、元の MT4 設定と一致するように OrderVolumeTrailingStopPoints、またはローソク足のタイプを調整します。

要求されたとおり、このポートには Python バージョンが提供されていません。

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that calculates classic floor pivot levels from daily candles and trades
/// breakouts around the central pivot. Goes long on close above pivot, short on close below.
/// Uses S2/R2 as stop/target levels.
/// </summary>
public class PivotsStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;

	private decimal _pivotLevel;
	private decimal _r1, _r2, _s1, _s2;
	private decimal? _previousClose;
	private decimal? _entryPrice;
	private bool _pivotReady;

	private readonly List<decimal> _dailyHighs = new();
	private readonly List<decimal> _dailyLows = new();
	private readonly List<decimal> _dailyCloses = new();
	private DateTime _currentDay;
	private decimal _dayHigh;
	private decimal _dayLow;
	private decimal _dayClose;

	public PivotsStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for signal generation", "General");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_pivotLevel = 0m;
		_r1 = _r2 = _s1 = _s2 = 0m;
		_previousClose = null;
		_entryPrice = null;
		_pivotReady = false;
		_dailyHighs.Clear();
		_dailyLows.Clear();
		_dailyCloses.Clear();
		_currentDay = DateTime.MinValue;
		_dayHigh = 0m;
		_dayLow = decimal.MaxValue;
		_dayClose = 0m;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var sma = new SimpleMovingAverage { Length = 2 };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(sma, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal smaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var candleDay = candle.OpenTime.Date;

		// Track daily OHLC
		if (candleDay != _currentDay)
		{
			if (_currentDay != DateTime.MinValue && _dayHigh > 0m)
			{
				// Previous day completed, calculate pivots
				var high = _dayHigh;
				var low = _dayLow;
				var close = _dayClose;

				_pivotLevel = (high + low + close) / 3m;
				_r1 = 2m * _pivotLevel - low;
				_s1 = 2m * _pivotLevel - high;
				_r2 = _pivotLevel + (high - low);
				_s2 = _pivotLevel - (high - low);
				_pivotReady = true;
			}

			_currentDay = candleDay;
			_dayHigh = candle.HighPrice;
			_dayLow = candle.LowPrice;
			_dayClose = candle.ClosePrice;
		}
		else
		{
			if (candle.HighPrice > _dayHigh) _dayHigh = candle.HighPrice;
			if (candle.LowPrice < _dayLow) _dayLow = candle.LowPrice;
			_dayClose = candle.ClosePrice;
		}

		if (!_pivotReady)
		{
			_previousClose = candle.ClosePrice;
			return;
		}

		if (_previousClose is null)
		{
			_previousClose = candle.ClosePrice;
			return;
		}

		// Manage open positions
		if (Position > 0)
		{
			// Exit long at R2 (take profit) or S1 (stop loss)
			if (candle.HighPrice >= _r2 || candle.LowPrice <= _s1)
			{
				SellMarket(Math.Abs(Position));
				_entryPrice = null;
			}
		}
		else if (Position < 0)
		{
			// Exit short at S2 (take profit) or R1 (stop loss)
			if (candle.LowPrice <= _s2 || candle.HighPrice >= _r1)
			{
				BuyMarket(Math.Abs(Position));
				_entryPrice = null;
			}
		}

		// Entry signals based on pivot cross
		if (Position == 0)
		{
			var crossAbovePivot = _previousClose.Value <= _pivotLevel && candle.ClosePrice > _pivotLevel;
			var crossBelowPivot = _previousClose.Value >= _pivotLevel && candle.ClosePrice < _pivotLevel;

			if (crossAbovePivot)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
			}
			else if (crossBelowPivot)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
			}
		}

		_previousClose = candle.ClosePrice;
	}
}