Auf GitHub ansehen

Simple Pivot Strategy

This strategy reproduces the "SimplePivot" MetaTrader 5 expert advisor. It continuously evaluates the relationship between the current bar open and the prior bar's pivot level, always maintaining a single directional position. When the bias flips, the strategy closes the existing position and immediately opens one in the opposite direction.

Overview

  • Market regime: Always-in-the-market swing trading.
  • Instruments: Any instrument that provides candle data for the selected timeframe.
  • Timeframes: Configurable through the Candle Type parameter (default 1-hour candles).
  • Orders: Market orders sized by the Volume parameter.

How It Works

Pivot calculation

  1. Wait for at least one completed candle to seed the calculation.
  2. Compute the pivot of the previous candle as the arithmetic mean of its high and low prices.
  3. Retain the previous high and low so the pivot for the next bar can be produced immediately when a new candle finishes.

Directional decision

  1. Default bias is long (buy).
  2. If the current candle opens below the previous high while staying above the pivot, the bias switches to short (sell).
  3. If the desired direction is unchanged from the last executed trade, the existing position is preserved and no new orders are sent.

Position management

  1. If the desired direction differs from the current trade, the running position is flattened via an opposite market order.
  2. After flatting, a market order sized by Volume establishes the new directional exposure.
  3. The process repeats on each finished candle, ensuring the strategy is always either long or short.

Parameters

  • Volume: Trade size used for every entry. It also determines the size of the closing order when the strategy flips direction.
  • Candle Type: Data type of the candles used for pivot and entry calculations. The default is a 1-hour time frame but any available time frame can be selected.

Additional Notes

  • The logic reacts on fully completed candles (CandleStates.Finished) to avoid repeated signals while a candle is still forming.
  • No stops or profit targets are defined; exits occur only when the pivot rule requests a direction change.
  • Because the strategy is always in the market, risk controls such as max drawdown monitoring or session filters should be handled externally if required.
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;
		}
	}
}