Открыть на GitHub

Parabolic SAR Limit

Parabolic SAR Limit — это порт советника ytg_Parabolic_exp.mq4, написанного для MetaTrader 4. Стратегия непрерывно отслеживает значение индикатора Parabolic SAR и удерживает возле него лимитные заявки на покупку и продажу. Когда рынок задевает лимит, позиция открывается, а далее стратегия контролирует выход по стоп-лоссу или тейк-профиту, используя экстремумы свечи, что полностью повторяет исходное MQL-решение.

Логика работы

  1. Стратегия подписывается на свечи с настраиваемым таймфреймом (по умолчанию 4 часа) и рассчитывает Parabolic SAR с такими же параметрами step и maximum, как в MT4.
  2. На каждой закрытой свече выполняется последовательность проверок:
    • Если точка SAR расположена ниже минимума свечи и лучшая цена Bid находится не ближе, чем на MinOrderDistancePoints пунктов выше SAR, создаётся (или переставляется) лимитная заявка на покупку по цене SAR.
    • Если точка SAR выше максимума свечи и лучшая цена Ask находится не ближе, чем на MinOrderDistancePoints пунктов ниже SAR, создаётся (или переставляется) лимитная заявка на продажу по цене SAR.
    • Для каждой стороны поддерживается только одна активная лимитная заявка. При смещении SAR предыдущая заявка отменяется и выставляется новая по актуальному уровню.
  3. После исполнения лимитной заявки расстояния стоп-лосса и тейк-профита (в пунктах) конвертируются в абсолютные цены через PriceStep инструмента. Эти уровни запоминаются как виртуальные защитные границы.
  4. На каждой новой свече проверяется, достигла ли цена сохранённых уровней. Если экстремум свечи касается стопа или тейка, позиция закрывается вызовом ClosePosition(), а защитное состояние сбрасывается.

Параметры

  • CandleType — таймфрейм свечей для сигналов. По умолчанию 4 часа, что соответствует параметру timeframe в MT4.
  • SarStep — коэффициент ускорения Parabolic SAR (step). Определяет скорость подтягивания SAR к цене.
  • SarMaximum — максимальное ускорение (maximum). Ограничивает скорость движения SAR.
  • StopLossPoints — расстояние от цены входа до стоп-лосса в пунктах. Значение 0 отключает стоп.
  • TakeProfitPoints — расстояние от цены входа до тейк-профита в пунктах. Значение 0 отключает тейк.
  • MinOrderDistancePoints — аналог MODE_STOPLEVEL в MT4. Лимитные заявки выставляются только если рынок находится дальше этого порога от уровня SAR.
  • OrderVolume — объём каждой лимитной заявки. Следите за тем, чтобы он совпадал с VolumeStep инструмента.

Все расстояния в пунктах преобразуются в цены через шаг PriceStep, поэтому стратегия корректно работает на инструментах с любым числом знаков.

Поведение в сделках

  • Возможна одновременная работа в обе стороны: если SAR меняет сторону относительно цены, в стакане могут сосуществовать buy limit и sell limit.
  • Лимитные заявки постоянно синхронизируются с актуальным значением SAR, устаревшие заявки отменяются до подачи новых.
  • Защитные уровни реализованы программно через анализ свечей, поскольку высокоуровневый API StockSharp не прикрепляет SL/TP напрямую к отложенным заявкам.
  • Для точной фильтрации расстояний используются лучшие цены Bid/Ask; если поток Level 1 недоступен, применяется закрытие свечи как приблизительная оценка текущей цены.

Особенности портирования

  • Параметр MinOrderDistancePoints по умолчанию равен 0. При необходимости установите величину, соответствующую ограничению брокера на минимальное расстояние до рынка.
  • Защитные уровни сбрасываются автоматически после закрытия позиции или отмены лимитной заявки, что предотвращает наложение старых данных на новые сделки.
  • В коде C# добавлены подробные комментарии на английском языке, описывающие работу с индикатором и жизненный цикл заявок через высокоуровневый API.

Рекомендации по использованию

  • Подайте поток заявок Level 1, чтобы условия по расстоянию до рынка рассчитывались по лучшим ценам. При отсутствии котировок оцените, подходит ли закрытие свечи в качестве прокси.
  • Проверьте PriceStep и VolumeStep выбранного инструмента: параметры в пунктах и объём должны преобразовываться в допустимые значения.
  • Если требуется более точный контроль стоп-лосса, используйте более мелкий таймфрейм, чтобы проверка защитных уровней выполнялась чаще.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Parabolic SAR strategy: enters on SAR flip (price crosses SAR level).
/// Buys when price moves above SAR, sells when price drops below SAR.
/// </summary>
public class ParabolicSarLimitStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevEma;
	private decimal _prevClose;
	private decimal _entryPrice;
	private bool _wasBullish;
	private bool _hasPrev;

	public ParabolicSarLimitStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_emaLength = Param(nameof(EmaLength), 14)
			.SetDisplay("EMA Length", "EMA period acting as dynamic SAR proxy.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR for volatility.", "Indicators");
	}

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

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

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

		_prevEma = 0;
		_prevClose = 0;
		_entryPrice = 0;
		_wasBullish = false;
		_hasPrev = false;
	}

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

		_prevEma = 0;
		_prevClose = 0;
		_entryPrice = 0;
		_wasBullish = false;
		_hasPrev = false;

		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		var close = candle.ClosePrice;

		if (!_hasPrev)
		{
			_prevEma = emaVal;
			_prevClose = close;
			_wasBullish = close > emaVal;
			_hasPrev = true;
			return;
		}

		var isBullish = close > emaVal;
		var flip = isBullish != _wasBullish;

		// Exit on SAR flip
		if (Position > 0 && flip && !isBullish)
		{
			SellMarket();
			_entryPrice = 0;
		}
		else if (Position < 0 && flip && isBullish)
		{
			BuyMarket();
			_entryPrice = 0;
		}

		// Entry on flip
		if (Position == 0 && flip)
		{
			if (isBullish)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevEma = emaVal;
		_prevClose = close;
		_wasBullish = isBullish;
	}
}