Открыть на GitHub

Стратегия Trailing Stop FrCnSar

Обзор

Стратегия Trailing Stop FrCnSar является портом двух MQL-скриптов: TrailingStopFrCnSARen_v4.mq4 и OrderBalansEN_v3_4.mq4. Первый скрипт управлял активными позициями, подтягивая стоп-лоссы по свечам, фракталам, индикатору Parabolic SAR или скорости движения цены. Второй выводил на графике баланс и список открытых ордеров. В StockSharp логика переписана на уровень чистых позиций: стратегия наблюдает один инструмент, рассчитывает уровни стопа выбранным методом и закрывает позицию при достижении барьерного значения. Дополнительно предусмотрен текстовый лог, который заменяет визуальную панель OrderBalans.

Стратегия не открывает сделки самостоятельно. Она используется как защитный модуль: поддерживает внутреннее состояние (последние экстремумы, фракталы, оценку скорости), применяет заданные фильтры и выставляет рыночную заявку на закрытие, когда цена пересекает виртуальный стоп.

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

  1. Подписка на свечи типа CandleType и обработка только закрытых баров.
  2. Сохранение небольшого буфера максимумов и минимумов для поиска экстремумов и фракталов без обращения к запрещённым функциям.
  3. При активном режиме «скорость» расчёт среднего изменения цены в пунктах на основе VelocityPeriod.
  4. Формирование кандидатного стоп-уровня в зависимости от режима:
    • минимум/максимум предыдущих свечей ± DeltaPoints;
    • последний сформированный фрактал ± DeltaPoints;
    • текущая цена минус/плюс расстояние, скорректированное разницей скоростей;
    • значение Parabolic SAR ± DeltaPoints;
    • фиксированный отступ в пунктах.
  5. Проверка фильтров: требование уже существующего стопа, обязательная прибыль, остановка на уровне безубытка, использование средней цены позиции.
  6. Обновление стопа только если новая величина улучшает текущую не менее чем на StepPoints пунктов.
  7. При касании баром стоп-уровня отправка рыночной заявки на закрытие позиции.
  8. При включённом LogOrderSummary запись в лог баланса, размера позиции, цены входа, текущего стопа и нереализованной прибыли — аналог панели OrderBalans.

Режимы трейлинга

  • Candle – подтягивание за последними свечными экстремумами с учётом DeltaPoints.
  • Fractal – использование ближайшего пятибарного фрактала.
  • Velocity – расстояние до стопа зависит от изменения средней скорости (разница двух последних значений, умноженная на VelocityMultiplier).
  • Parabolic – следование за Parabolic SAR c настраиваемыми шагом и максимальным ускорением.
  • FixedPoints – постоянный отступ, аналог функции «больше 4 пунктов» из оригинала.
  • Off – отключение подтягивания.

Параметры

Имя Тип Значение по умолчанию Описание
Mode TrailingStopMode Candle Выбранный алгоритм трейлинга.
CandleType DataType 15-минутные свечи Таймфрейм, по которому рассчитываются уровни.
DeltaPoints int 0 Отступ в пунктах относительно базового уровня.
StepPoints int 0 Минимальное улучшение (в пунктах) для обновления стопа.
FixedDistancePoints int 50 Дистанция в режиме фиксированного трейлинга.
TrailOnlyProfit bool true Разрешать подтягивание только после выхода позиции в прибыль.
TrailOnlyBreakEven bool false Остановиться после достижения безубытка.
RequireExistingStop bool false Игнорировать обновления, пока стоп не рассчитан.
UseGeneralBreakEven bool false Использовать среднюю цену позиции для проверки прибыльности (аналог MQL-функции TProfit).
VelocityPeriod int 30 Количество закрытий для усреднения скорости.
VelocityMultiplier decimal 1 Коэффициент влияния скорости на расстояние до стопа.
ParabolicStep decimal 0.02 Шаг ускорения Parabolic SAR.
ParabolicMaximum decimal 0.2 Максимальное ускорение Parabolic SAR.
LogOrderSummary bool true Включить вывод текстовой сводки о позиции.
TradeVolume decimal 1 Объём, используемый при закрытии позиции.

Отличия от оригинала

  • StockSharp оперирует чистым объёмом, поэтому привязка к отдельным ордерам и магическим номерам удалена.
  • Velocity рассчитывается как среднее изменение цены в пунктах, что может слегка отличаться от оригинального индикатора, но сохраняет общий характер поведения.
  • Графические объекты из OrderBalans заменены логами. Это упрощает порт и не требует ручного управления объектами на графике.
  • Вместо OrderModify применяются методы BuyMarket/SellMarket для немедленного закрытия нетто-позиции.

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

  • Для визуального контроля подключите стратегию к графику и активируйте отображение свечей и сделок. В режиме Parabolic удобно видеть точки SAR.
  • Настраивайте DeltaPoints и StepPoints с учётом шага цены инструмента (PriceStep/MinPriceStep).
  • Если важна строгость оригинальной логики, оставьте TrailOnlyProfit включённым.
  • Отключите LogOrderSummary, если стратегия запускается массово и необходимо сократить объём журналов.
  • В режиме Velocity экспериментируйте с VelocityMultiplier: большие значения позволяют быстрее фиксировать прибыль при резких импульсах.

Индикаторы

  • Parabolic SAR (ParabolicSar)
  • Буферы максимумов и минимумов свечей (для свечного и фрактального режимов)
  • Усреднённая скорость закрытий (для режима Velocity)
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trailing Stop FrCnSar: EMA crossover with ATR trailing stops.
/// </summary>
public class TrailingStopFrCnSarStrategy : 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;
	private decimal _bestPrice;

	public TrailingStopFrCnSarStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_fastEmaLength = Param(nameof(FastEmaLength), 10)
			.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
		_slowEmaLength = Param(nameof(SlowEmaLength), 30)
			.SetDisplay("Slow EMA Length", "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; _bestPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _bestPrice = 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 (close > _bestPrice) _bestPrice = close;
			if (close <= _bestPrice - atrVal * 2m || (fastVal < slowVal && _prevFast >= _prevSlow)) { SellMarket(); _entryPrice = 0; _bestPrice = 0; }
		}
		else if (Position < 0)
		{
			if (close < _bestPrice) _bestPrice = close;
			if (close >= _bestPrice + atrVal * 2m || (fastVal > slowVal && _prevFast <= _prevSlow)) { BuyMarket(); _entryPrice = 0; _bestPrice = 0; }
		}

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