Открыть на GitHub

4026 – Стратегия Pivots

Обзор

Стратегия переносит файлы MetaTrader 4 из каталога MQL/8550 (индикатор Pivots и эксперта Pivots_test) в высокоуровневый API Strategy платформы StockSharp. Реализовано исходное поведение: вычисление дневных уровней классических пивотов, поддержание пары противоположных отложенных заявок на центральном уровне и сопровождение каждой позиции фиксированными стоп- и тейк-профитами с трейлинг-стопом.

Расчёт пивотов

  1. Стратегия подписывается на настраиваемый таймфрейм (PivotCandleType, по умолчанию дневные свечи).
  2. После завершения свечи рассчитываются уровни по формулам классических floor-pivot:
    • 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 × Low, S3 = 2 × Pivot − (2 × High − Low)
  3. Новые уровни активируются с открытия следующей торговой сессии. В этот момент значения выводятся в лог через AddInfoLog (пример: Pivot levels for 2024-04-05: P=1.0924, R1=1.0956, …).

Работа с отложенными заявками

После активации уровней стратегия гарантирует присутствие двух заявок на цене Pivot:

  • Buy Limit по цене Pivot с защитой SellStop на S2 (стоп-лосс) и SellLimit на R2 (тейк-профит).
  • Sell Stop по цене Pivot с защитой BuyStop на R2 и BuyLimit на S2.

Регистрация ведётся только через высокоуровневые методы BuyLimit, SellStop, SellLimit, BuyStop. При исполнении заявки пересчитывается средняя цена входа по направлению, отменяются старые защитные ордера и выставляется новая пара стоп/тейк на весь текущий объём – полностью повторяя логику MetaTrader, где каждая позиция использует уровни S2/R2. При срабатывании стопа или тейка вспомогательные ордера очищаются автоматически.

StockSharp работает с нетто-позицией, поэтому встречные сделки взаимоудаляются (в MT4 с хеджингом тикеты существовали отдельно). Это единственное осознанное отличие от оригинального эксперта.

Трейлинг-стоп

  • Параметр TrailingStopPoints задаёт расстояние в пунктах (умножается на PriceStep).
  • Для лонга трейлинг активируется, когда цена ушла выше средней входной цены больше чем на указанное расстояние, после чего SellStop подтягивается ближе к рынку.
  • Для шорта выполняется зеркальная логика с BuyStop.
  • Обновления трейлинга происходят на основе внутридневного таймфрейма CandleType (по умолчанию 15-минутные свечи).

Параметры

Параметр Описание Значение по умолчанию
OrderVolume Объём каждой отложенной заявки (лоты/контракты). 0.1
TrailingStopPoints Размер трейлинг-стопа в пунктах (0 отключает механизм). 30
CandleType Внутридневные свечи для трейлинга и контроля сессии. Таймфрейм 15 минут
PivotCandleType Таймфрейм для расчёта пивотов. Таймфрейм 1 день
LogPivotUpdates Логировать ли обновления уровней. true

Все числовые настройки созданы через StrategyParam<T>, поэтому их можно оптимизировать средствами StockSharp.

Логи и диагностика

  • Обновления уровней выводятся методом AddInfoLog, заменяя комментарии и подписи MT4.
  • Управление позициями и ордерами реализовано исключительно высокоуровневыми вызовами без прямой работы с буферами или низкоуровневой регистрацией заявок.

Использование

  1. Подключите стратегию к источнику данных, предоставляющему дневные и внутридневные свечи по инструменту.
  2. При необходимости уточните шаг цены инструмента (PriceStep определяется автоматически, резервное значение – 0.0001).
  3. Настройте OrderVolume, TrailingStopPoints и типы свечей в соответствии с исходной конфигурацией MT4.

По запросу 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;
	}
}