GitHub で見る

シンプル・ピボット戦略

この戦略はMetaTrader 5のエキスパートアドバイザー「SimplePivot」を再現します。現在のバーの始値と前のバーのピボットレベルの関係を継続的に評価し、常に単一の方向ポジションを維持します。バイアスが反転すると、戦略は既存のポジションを閉じて直ちに反対方向に新しいポジションを開きます。

概要

  • 市場レジーム: 常時市場参加型のスイングトレード。
  • 対象銘柄: 選択した時間軸のローソク足データを提供する任意の銘柄。
  • 時間軸: Candle Typeパラメーターで設定可能(デフォルトは1時間足)。
  • 注文: Volumeパラメーターでサイズが決まる成行注文。

動作原理

ピボット計算

  1. 計算を初期化するために少なくとも1本の完成したローソク足を待ちます。
  2. 前のローソク足のピボットを高値と安値の算術平均として計算します。
  3. 新しいローソク足が完成したときに次のバーのピボットをすぐに算出できるよう、前のバーの高値と安値を保持します。

方向の決定

  1. デフォルトのバイアスはロング(買い)です。
  2. 現在のローソク足が前のバーの高値を下回って開始し、ピボットより上に留まる場合、バイアスはショート(売り)に切り替わります。
  3. 希望する方向が最後に実行したトレードから変わらない場合、既存のポジションが保持され新しい注文は送信されません。

ポジション管理

  1. 希望する方向が現在のトレードと異なる場合、実行中のポジションは反対の成行注文でフラットにされます。
  2. フラット化後、Volumeでサイズが決まる成行注文が新しい方向のエクスポージャーを確立します。
  3. プロセスは完成した各ローソク足で繰り返され、戦略が常にロングまたはショートのいずれかであることを保証します。

パラメーター

  • Volume: すべてのエントリーに使用するトレードサイズ。戦略が方向を切り替えるときのクローズ注文のサイズも決定します。
  • Candle Type: ピボットとエントリー計算に使用するローソク足のデータタイプ。デフォルトは1時間の時間軸ですが、利用可能な任意の時間軸を選択できます。

追加事項

  • ロジックはローソク足がまだ形成中の間の繰り返しシグナルを避けるために、完全に完成したローソク足(CandleStates.Finished)に反応します。
  • ストップや利益目標は定義されていません;ピボットルールが方向変更を要求した場合にのみ決済されます。
  • 戦略は常に市場にいるため、最大ドローダウン監視やセッションフィルターなどのリスク管理が必要な場合は外部で処理する必要があります。
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>
/// Simple pivot strategy that flips position based on the previous bar pivot.
/// </summary>
public class SimplePivotStrategy : Strategy
{
	private enum TradeDirections
	{
		None,
		Long,
		Short,
	}

	private readonly StrategyParam<DataType> _candleType;

	private TradeDirections _lastDirection = TradeDirections.None;
	private decimal _previousHigh;
	private decimal _previousLow;
	private bool _hasPreviousCandle;

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

	public SimplePivotStrategy()
	{

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "Data");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();

		_lastDirection = TradeDirections.None;
		_previousHigh = 0m;
		_previousLow = 0m;
		_hasPreviousCandle = false;
	}

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

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();
	}

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

		if (!_hasPreviousCandle)
		{
			// Collect the very first completed candle as the seed for the pivot calculation.
			_previousHigh = candle.HighPrice;
			_previousLow = candle.LowPrice;
			_hasPreviousCandle = true;
			return;
		}

		var pivot = (_previousHigh + _previousLow) / 2m;
		var desiredDirection = TradeDirections.Long;

		// When the new open sits between the previous high and pivot we switch to a short bias.
		if (candle.OpenPrice < _previousHigh && candle.OpenPrice > pivot)
			desiredDirection = TradeDirections.Short;

		if (desiredDirection == _lastDirection && _lastDirection != TradeDirections.None)
		{
			// Keep the existing position when direction has not changed.
			_previousHigh = candle.HighPrice;
			_previousLow = candle.LowPrice;
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_previousHigh = candle.HighPrice;
			_previousLow = candle.LowPrice;
			return;
		}

		CloseExistingPosition();

		if (desiredDirection == TradeDirections.Long)
		{
			// Enter a long position when the open is below the pivot.
			BuyMarket(Volume);
		}
		else
		{
			// Enter a short position when the open is above the pivot zone.
			SellMarket(Volume);
		}

		_lastDirection = desiredDirection;
		_previousHigh = candle.HighPrice;
		_previousLow = candle.LowPrice;
	}

	private void CloseExistingPosition()
	{
		if (Position > 0)
		{
			// Flip from long to flat before opening the opposite direction.
			SellMarket(Math.Abs(Position));
			_lastDirection = TradeDirections.None;
		}
		else if (Position < 0)
		{
			// Flip from short to flat before opening the opposite direction.
			BuyMarket(Math.Abs(Position));
			_lastDirection = TradeDirections.None;
		}
	}
}