GitHub で見る

シンプルなピボットフリップ戦略

概要

この戦略は、MQL/7610/Simplepivot_www_forex-instruments_info.mq4 に保存されている MetaTrader 4 Expert Advisor の高レベル C# ポートです。オリジナルのプログラムは、新しいローソク足の始値を以前のローソク足の範囲と比較してチェックし、市場のロングポジションとショートポジションを切り替えます。 StockSharp バージョンは、SubscribeCandlesBindBuyMarketSellMarketClosePosition などの高レベルのヘルパーのみに依存することで、同じ動作を維持します。

変換されたロジック:

  1. 完成したローソクが始値、高値、安値を取得するまで待ちます。
  2. 前のローソク足の範囲を使用して、中間点に単純なピボットを構築します。
  3. 現在のローソク足がレンジの下半分、または前の高値を上回るギャップでオープンしたときに、新しいロングポジションをオープンします。
  4. 現在のローソク足がレンジの上半分で開いたときに、新しいショートポジションを開きます。
  5. 反対方向にエントリーする前に常に既存のポジションを決済し、MQL バージョンのシングルチケット動作を再現します。

元の Expert Advisor にはストップロスやテイクプロフィットのレベルが実装されていないため、ポジションは新しいローソク足が異なる方向を指示する場合にのみ逆転します。

パラメーター

名前 デフォルト 説明
OrderVolume 1 ポジションをエントリーする際に使用される成行注文の量。
CandleType 1分の時間枠 データフィードからリクエストされたローソクのタイプ。

取引ロジックの詳細

  1. 最初に完成したキャンドルは保存され、次の決定の参考として使用されます。分析する完全なローソクが完成するまで、注文は送信されません。
  2. 後続の完了したキャンドルごとに次のようになります。
    • pivot = (previousHigh + previousLow) / 2 を計算します。
    • Open < previousHigh かつ Open > pivot の場合、戦略は短いエントリを準備します。
    • それ以外の場合は、ロングエントリーを準備します(これは、下半分でのオープン、ピボットに等しいオープン、および前の高値を上回る、または前の安値を下回るギャップをカバーします)。
  3. 戦略が選択した方向にすでにポジションを保持している場合、スプレッドを 2 回支払うことを避けるためにシグナルは無視されます。これは、MQL コードにある早期リターンを反映しています。
  4. 方向が変わると、現在のポジションは ClosePosition() を介してクローズされ、新しい成行注文が OrderVolume を使用して送信されます。
  5. 以前の高値/安値バッファは、次の決定を促進するために、最新の完了したローソク足で更新されます。

リスク管理

変換されたアルゴリズムにはストップや利益目標は含まれません。ポジションのサイジングは OrderVolume パラメーターによってのみ制御されるため、リスクは外部で管理する必要があります (たとえば、ボリュームを調整したり、アカウントレベルの保護と戦略を組み合わせたりすることによって)。

可視化

チャート領域が利用可能な場合、戦略は要求されたローソク足と実行された取引をプロットします。これは、バックテスト中にピボット フリップを検証するのに役立ちます。

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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simple pivot-based strategy converted from the MetaTrader expert advisor in MQL/7610.
/// The strategy compares the current candle open with the previous candle range to decide
/// whether the next trade should be long or short.
/// </summary>
public class SimplePivotFlipStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _previousHigh;
	private decimal _previousLow;
	private bool _hasPreviousCandle;

	/// <summary>
	/// Order volume used for market entries.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Candle type used for price subscription.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="SimplePivotFlipStrategy"/> class.
	/// </summary>
	public SimplePivotFlipStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Market order volume used for entries.", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used for pivot calculation.", "Data");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_previousHigh = 0m;
		_previousLow = 0m;
		_hasPreviousCandle = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

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

		

		if (!_hasPreviousCandle)
		{
			// Store the first completed candle to build the reference range.
			_previousHigh = candle.HighPrice;
			_previousLow = candle.LowPrice;
			_hasPreviousCandle = true;
			return;
		}

		// Calculate the pivot as the midpoint of the previous candle range.
		var pivot = (_previousHigh + _previousLow) / 2m;
		var desiredSide = Sides.Buy;

		// If the new candle opens inside the upper half of the previous range we go short.
		if (candle.OpenPrice < _previousHigh && candle.OpenPrice > pivot)
			desiredSide = Sides.Sell;

		// Skip re-entry if we already hold a position in the desired direction.
		if ((desiredSide == Sides.Buy && Position > 0) || (desiredSide == Sides.Sell && Position < 0))
		{
			_previousHigh = candle.HighPrice;
			_previousLow = candle.LowPrice;
			return;
		}

		if (Position > 0)
			SellMarket();
		else if (Position < 0)
			BuyMarket();

		if (desiredSide == Sides.Buy)
		{
			BuyMarket();
		}
		else
		{
			SellMarket();
		}

		// Update reference range for the next candle.
		_previousHigh = candle.HighPrice;
		_previousLow = candle.LowPrice;
	}
}