View on GitHub

Simple Pivot Flip Strategy

Overview

This strategy is a high-level C# port of the MetaTrader 4 Expert Advisor stored in MQL/7610/Simplepivot_www_forex-instruments_info.mq4. The original program checks the open price of each new candle against the previous candle range and flips between long and short market positions. The StockSharp version keeps the same behaviour by relying exclusively on high-level helpers such as SubscribeCandles, Bind, BuyMarket, SellMarket, and ClosePosition.

The converted logic:

  1. Waits for a finished candle to obtain the open, high, and low values.
  2. Uses the previous candle range to build a simple pivot at the midpoint.
  3. Opens a new long position when the current candle opens in the lower half of the range or gaps above the previous high.
  4. Opens a new short position when the current candle opens in the upper half of the range.
  5. Always closes the existing position before entering in the opposite direction, replicating the single-ticket behaviour of the MQL version.

No stop-loss or take-profit levels are implemented in the original Expert Advisor, so the position is reversed only when a new candle dictates a different direction.

Parameters

Name Default Description
OrderVolume 1 Market order volume used when entering a position.
CandleType 1 minute time frame Candle type requested from the data feed.

Trading Logic Details

  1. The very first finished candle is stored and used as the reference for the next decision. No order is sent until there is a complete candle to analyse.
  2. For every subsequent completed candle:
    • Compute pivot = (previousHigh + previousLow) / 2.
    • If Open < previousHigh and Open > pivot, the strategy prepares a short entry.
    • Otherwise it prepares a long entry (this covers opens in the lower half, opens equal to the pivot, and any gaps above the previous high or below the previous low).
  3. If the strategy already holds a position in the chosen direction, the signal is ignored to avoid paying the spread twice—mirroring the early return found in the MQL code.
  4. If the direction changes, the current position is closed via ClosePosition() and a new market order is sent using OrderVolume.
  5. The previous high/low buffer is updated with the latest completed candle to drive the next decision.

Risk Management

The converted algorithm does not include stops or profit targets. Position sizing is controlled only by the OrderVolume parameter, so risk should be managed externally (for example by adjusting the volume or by combining the strategy with account-level protections).

Visualisation

When a chart area is available, the strategy plots the requested candles and the executed trades, which helps validate the pivot flips during backtests.

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;
	}
}