Открыть на GitHub

Стратегия «Virtual Trailing Stop Level1»

Общее описание

Виртуальный трейлинг-стоп — портирование эксперта MetaTrader Virtual Trailing Stop.mq5 (MQL ID 21362) на платформу StockSharp. Исходный советник не открывал сделки, а лишь сопровождал уже существующие позиции, рассчитывая «виртуальные» стоп-приказы. Версия на C# повторяет этот подход: стратегия отслеживает лучшие котировки bid/ask и закрывает позицию рыночными заявками при срабатывании стоп-лосса, тейк-профита или трейлинг-стопа.

Стратегия никогда не инициирует входов. Её задача — обеспечить автоматическое сопровождение позиций, созданных вручную или другими модулями, но в инфраструктуре StockSharp (Designer, Shell, Runner).

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

  1. Подписка на Level1 – стратегия получает поток лучших цен и сохраняет последние значения bid/ask.
  2. Перевод параметров в цену – все входы задаются в пипсах. Значение умножается на PriceStep. Для инструментов с 3 и 5 знаками после запятой используется дополнительный множитель ×10, чтобы соответствовать определению pip в MetaTrader.
  3. Контроль стоп-лосса – длинная позиция закрывается, если bid ≤ цена входа − StopLoss, короткая – если ask ≥ цена входа + StopLoss.
  4. Контроль тейк-профита – длинная позиция закрывается при bid ≥ цена входа + TakeProfit, короткая – при ask ≤ цена входа − TakeProfit.
  5. Активация трейлинга – как только плавающая прибыль достигла TrailingStart пипсов, формируется виртуальный уровень трейлинг-стопа (bid − TrailingStop для long, ask + TrailingStop для short).
  6. Сдвиг трейлинга – при увеличении прибыли минимум на TrailingStep пипсов уровень сдвигается вслед за ценой. Нулевое значение означает непрерывный трейлинг.
  7. Выход по трейлингу – позиция закрывается, когда цена касается уровня трейлинга и сделка остаётся прибыльной (аналог проверки Profit()>0 в исходном коде).

Никакие стоп-ордера на биржу не отправляются — выход всегда осуществляется рыночной заявкой, сохраняя «виртуальный» характер сопровождения.

Параметры

Параметр Назначение Значение по умолчанию
StopLossPips Расстояние до стоп-лосса в пипсах. 0 отключает жёсткий стоп. 0
TakeProfitPips Расстояние до тейк-профита в пипсах. 0 отключает тейк-профит. 0
TrailingStopPips Дистанция между ценой и трейлинг-уровнем в пипсах. 5
TrailingStartPips Минимальная прибыль (в пипсах) для запуска трейлинга. 5
TrailingStepPips Минимальный шаг обновления трейлинга (в пипсах). 0 — обновление при каждом выгодном тике. 1

Параметры реализованы через StrategyParam, поэтому доступны для оптимизации.

Особенности реализации

  • Используются только данные уровня 1 (DataType.Level1); графические объекты из MetaTrader не переносятся.
  • Перевод пипсов в цену зависит от Security.PriceStep и Security.Decimals. При отсутствии информации по тик-сайзу применяется значение 1.
  • Для длинных и коротких позиций поддерживаются отдельные переменные трейлинг-уровня.
  • Автоматическое открытие тестовых позиций, присутствовавшее в исходном советнике, опущено, так как в StockSharp используется учёт чистой позиции.

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

  • Запускайте стратегию на инструменте, по которому уже есть открытые сделки или они появятся от других стратегий/ручных действий.
  • Комбинируйте с собственными модулями входа, чтобы получать знакомый по MetaTrader функционал сопровождения в продуктах StockSharp.
  • Для инструментов с крупным тиком подбирайте значения в пипсах в соответствии с PriceStep; при TrailingStopPips = 1 трейлинг сдвигается на один шаг цены.

Состав поставки

  • CS/VirtualTrailingStopLevel1Strategy.cs — исходный код стратегии.
  • README.md, README_zh.md, README_ru.md — документация на английском, китайском и русском языках.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Virtual Trailing Stop Level1 strategy (simplified). Uses EMA with
/// percentage-based trailing stop for position management.
/// </summary>
public class VirtualTrailingStopLevel1Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<decimal> _trailingPercent;

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

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

	public decimal TrailingPercent
	{
		get => _trailingPercent.Value;
		set => _trailingPercent.Value = value;
	}

	public VirtualTrailingStopLevel1Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candles", "General");

		_emaLength = Param(nameof(EmaLength), 15)
			.SetGreaterThanZero()
			.SetDisplay("EMA Length", "EMA period", "Indicators");

		_trailingPercent = Param(nameof(TrailingPercent), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("Trailing %", "Trailing stop percent", "Risk");
	}

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

		var ema = new ExponentialMovingAverage { Length = EmaLength };
		decimal highSinceEntry = 0;
		decimal lowSinceEntry = decimal.MaxValue;
		decimal prevClose = 0;
		decimal prevEma = 0;
		var hasPrev = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, (ICandleMessage candle, decimal emaVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!hasPrev)
				{
					prevClose = candle.ClosePrice;
					prevEma = emaVal;
					hasPrev = true;
					return;
				}

				if (!IsFormedAndOnlineAndAllowTrading())
				{
					prevClose = candle.ClosePrice;
					prevEma = emaVal;
					return;
				}

				var close = candle.ClosePrice;

				// Trailing stop management
				if (Position > 0)
				{
					if (candle.HighPrice > highSinceEntry) highSinceEntry = candle.HighPrice;
					if (close < highSinceEntry * (1m - TrailingPercent / 100m))
					{
						SellMarket();
						highSinceEntry = 0;
						lowSinceEntry = decimal.MaxValue;
						return;
					}
				}
				else if (Position < 0)
				{
					if (candle.LowPrice < lowSinceEntry) lowSinceEntry = candle.LowPrice;
					if (close > lowSinceEntry * (1m + TrailingPercent / 100m))
					{
						BuyMarket();
						highSinceEntry = 0;
						lowSinceEntry = decimal.MaxValue;
						return;
					}
				}

				// Entry based on EMA
				var bullishCross = prevClose <= prevEma && close > emaVal;
				var bearishCross = prevClose >= prevEma && close < emaVal;

				if (bullishCross && Position <= 0)
				{
					BuyMarket();
					highSinceEntry = candle.HighPrice;
					lowSinceEntry = decimal.MaxValue;
				}
				else if (bearishCross && Position >= 0)
				{
					SellMarket();
					lowSinceEntry = candle.LowPrice;
					highSinceEntry = 0;
				}

				prevClose = close;
				prevEma = emaVal;
			})
			.Start();

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