Открыть на GitHub

Стратегия Parabolic SAR Alert

Обзор

Эта стратегия представляет собой перенос советника MetaTrader 4 pSAR_alert.mq4 на платформу StockSharp. Исходный скрипт лишь воспроизводил звуковой сигнал, когда Parabolic SAR переходил с одной стороны цены на другую. В версии для StockSharp сигнал преобразован в реальные рыночные заявки, поэтому стратегия автоматически торгует теми же переключениями индикатора.

Логика торговли

  • Стратегия подписывается на заданный тип свечей и рассчитывает индикатор Parabolic SAR с классическими параметрами: начальный шаг 0.02 и максимум 0.2.
  • Для каждой завершённой свечи вычисляется текущее значение SAR, дополнительно сохраняется связка «SAR + цена закрытия» предыдущей свечи.
  • Если на предыдущей свече цена находилась ниже SAR, а на текущей закрылась выше него, индикатор «переворачивается» вниз – стратегия открывает длинную позицию или разворачивает короткую.
  • Если на предыдущей свече цена находилась выше SAR, а на текущей закрылась ниже, фиксируется переворот вверх – стратегия открывает короткую позицию или разворачивает длинную.
  • Объём сделки рассчитывается как сумма базового объёма стратегии и абсолютного значения текущей позиции, что гарантирует полное закрытие старого направления перед открытием нового.
  • При запуске вызывается StartProtection(), чтобы встроенная защита StockSharp отслеживала открытые позиции во время технических сбоев.

Параметры

Параметр Значение по умолчанию Описание
AccelerationFactor 0.02 Начальный шаг ускорения индикатора Parabolic SAR, определяет скорость его движения вслед за ценой.
MaxAccelerationFactor 0.2 Максимальное ускорение, ограничивающее реакцию SAR в сильном тренде.
CandleType Тайм‑фрейм 5 минут Тип рыночных данных, которые подаются на вход индикатора; позволяет выбрать другой период или вид свечей.

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

Работа индикатора

  1. Подписаться на поток свечей с помощью SubscribeCandles.
  2. Привязать поток к индикатору ParabolicSar, чтобы StockSharp автоматически поддерживал его расчёт.
  3. В обработчике хранить предыдущие значения SAR и цены закрытия, сравнивать их с текущими данными.
  4. Определять перевороты, когда SAR переходит из-под цены над ней или наоборот.
  5. Выполнять BuyMarket либо SellMarket и писать подробные сообщения в журнал.

Практические рекомендации

  • Сигналы формируются только после закрытия свечи, что снижает количество ложных срабатываний внутри бара.
  • Параметры по умолчанию повторяют исходный советник, однако их можно изменить, чтобы сделать систему более агрессивной или плавной.
  • Лучшие результаты достигаются на инструментах с выраженными трендами, где перевороты SAR происходят не слишком часто.
  • При наличии области графика стратегия автоматически выводит свечи, линию Parabolic SAR и собственные сделки для визуального контроля.

Файлы

  • CS/ParabolicSarCrossoverAlertStrategy.cs – реализация стратегии на C#.
  • README.md – документация на английском языке.
  • README_zh.md – документация на китайском языке.
  • README_ru.md – документация на русском языке.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Parabolic SAR Crossover: EMA crossover with ATR stops.
/// </summary>
public class ParabolicSarCrossoverAlertStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

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

		_fastEmaLength = Param(nameof(FastEmaLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowEmaLength = Param(nameof(SlowEmaLength), 30)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

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

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

	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

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

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

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
	}

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

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (fastVal < slowVal && _prevFast >= _prevSlow)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (close <= _entryPrice - atrVal * 2m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (close >= _entryPrice + atrVal * 2m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (fastVal < slowVal && _prevFast >= _prevSlow)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}