Открыть на GitHub

Стратегия MTrendLine

Обзор

MTrendLine Strategy — это портирование скрипта MetaTrader MTrendLine.mq4 на высокоуровневый API StockSharp. Оригинальный советник постоянно переносит цены уже существующих отложенных ордеров на новую позицию, чтобы они оставались рядом с проведённой на графике трендовой линией. Версия для StockSharp выполняет ту же задачу, но восстанавливает трендовую линию посредством настраиваемого индикатора LinearRegression. До трёх независимых слотов отложенных ордеров могут следовать за расчётной линией регрессии, имея собственный тип заявки, расстояние и объём. При закрытии каждой свечи стратегия пересчитывает значение линии, уточняет требуемые смещения и обновляет связанные с ними ордера.

В порте добавлены современные инструменты управления рисками и удобства: параметры имеют понятную структуру, MetaTrader-пункты автоматически переводятся в реальные ценовые шаги, а стоп-лосс и тейк-профит (при необходимости) движутся вместе с ордером. Через SubscribeLevel1() стратегия отслеживает лучшие bid/ask и тем самым соблюдает минимально допустимую дистанцию до рынка, которую требует брокер.

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

  1. Подписаться на выбранный таймфрейм через SubscribeCandles() и подавать закрытые свечи в индикатор LinearRegression. Индикатор моделирует вручную нарисованную линию из MetaTrader.
  2. Параллельно получать поток Level1, чтобы кэшировать текущие лучшие цены покупателя и продавца. Они используются для контроля параметра минимальной дистанции перед переносом ордера.
  3. Для каждого активного слота вычислить целевую цену как значение регрессии + расстояние × размер пункта. Размер пункта по умолчанию равен PriceStep, но может быть принудительно задан для точного совпадения с метатрейдеровским Point.
  4. Преобразовать настройки слота в вызовы удобных методов StockSharp (BuyLimit, SellLimit, BuyStop, SellStop). Если заданы стоп-лосс и тейк-профит, их уровни пересчитываются из заданного расстояния и всегда «едут» вместе с ордером.
  5. Если для слота уже существует активный отложенный ордер и новая целевая цена отличается, сначала отменить текущий ордер и дождаться следующей свечи для регистрации обновлённого. Такой подход имитирует OrderModify из MQL и не создаёт дубликатов.
  6. При отключении слота или при некорректном расчёте цены (например, отрицательное значение) ордер отменяется, а внутреннее состояние сбрасывается.

Слоты отложенных ордеров

Каждый слот соответствует вызову функции modify() в исходном коде EA и настраивается отдельно:

  • Тип — Buy Limit, Buy Stop, Sell Limit или Sell Stop.
  • Дистанция — значение в пунктах MetaTrader, добавляемое к регрессионной цене. Для размещения ордера ниже линии используйте отрицательные значения.
  • Объём — объём отложенного ордера. Если задано ноль или отрицательное число, используется глобальный параметр TradeVolume.
  • Флаг активности — позволяет временно отключить слот. Отключённый слот автоматически отменяет связанный ордер.

Параметры

Имя Тип Значение по умолчанию Описание
CandleType DataType Свечи H1 Таймфрейм, на котором строится линия регрессии.
RegressionLength int 24 Количество свечей, участвующих в расчёте LinearRegression.
PointValue decimal 0 Стоимость одного пункта MetaTrader. При нуле используется PriceStep.
TradeVolume decimal 1 Базовый объём, применяемый слотами, если их собственный объём равен нулю.
StopLossPoints decimal 0 Расстояние стоп-лосса в пунктах. Ноль отключает постановку стопа.
TakeProfitPoints decimal 0 Расстояние тейк-профита в пунктах. Ноль отключает постановку тейка.
MinDistancePoints decimal 0 Минимальный зазор (в пунктах) между лучшими ценами и отложенным ордером.
PendingOrder{1,2,3}Enabled bool Индивидуально Включает или отключает слот.
PendingOrder{1,2,3}Mode enum Индивидуально Тип заявки: BuyLimit, BuyStop, SellLimit или SellStop.
PendingOrder{1,2,3}DistancePoints decimal Индивидуально Расстояние в пунктах, добавляемое к цене регрессии.
PendingOrder{1,2,3}Volume decimal Индивидуально Объём слота. Ноль — использовать TradeVolume.

Отличия от оригинального скрипта MetaTrader

  • В MetaTrader ордера изменяются «на месте». StockSharp использует отмену и повторную регистрацию, дожидаясь подтверждения на следующей свече.
  • В MQL считывается значение вручную нарисованной линии. Здесь её заменяет индикатор LinearRegression, что делает логику полностью автоматической и воспроизводимой.
  • Параметр MODE_STOPLEVEL недоступен в StockSharp, поэтому добавлен настраиваемый MinDistancePoints, контролируемый через поток bid/ask.
  • Стоп-лосс и тейк-профит задаются параметрами, а не считываются из существующих ордеров. Благодаря этому расстояния не «прыгают» при каждом обновлении.

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

  • Задайте PointValue согласно требованиям брокера, если размер пункта отличается от PriceStep инструмента.
  • Включайте только нужные слоты. Каждый ордер получает комментарий вида "MTrendLine slot N", что облегчает контроль в отчётах и журнале заявок.
  • При необходимости добавьте стандартные защитные механизмы StockSharp (например, трейлинг или ограничение убытков по счёту). В этой реализации акцент сделан на точном повторении логики переноса ордеров.

Индикаторы

  • LinearRegression, работающий по закрытым свечам.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trend line strategy using linear regression slope for direction.
/// Enters long when slope is positive and price is above regression, short otherwise.
/// </summary>
public class MTrendLineStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _regressionLength;

	private decimal _prevSlope;
	private bool _hasPrev;

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

		_regressionLength = Param(nameof(RegressionLength), 20)
			.SetDisplay("Regression Length", "Length of the linear regression.", "Indicators");
	}

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

	public int RegressionLength
	{
		get => _regressionLength.Value;
		set => _regressionLength.Value = value;
	}

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

		_prevSlope = 0;
		_hasPrev = false;
	}

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

		_prevSlope = 0;
		_hasPrev = false;

		var lr = new LinearReg { Length = RegressionLength };
		var ema = new ExponentialMovingAverage { Length = RegressionLength };

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

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

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

		// Compute slope as difference between LR and EMA
		var slope = lrValue - emaValue;

		if (!_hasPrev)
		{
			_prevSlope = slope;
			_hasPrev = true;
			return;
		}

		var close = candle.ClosePrice;

		// Exit conditions
		if (Position > 0 && slope < 0 && _prevSlope >= 0)
		{
			SellMarket();
		}
		else if (Position < 0 && slope > 0 && _prevSlope <= 0)
		{
			BuyMarket();
		}

		// Entry conditions
		if (Position == 0)
		{
			if (slope > 0 && _prevSlope <= 0 && close > lrValue)
			{
				BuyMarket();
			}
			else if (slope < 0 && _prevSlope >= 0 && close < lrValue)
			{
				SellMarket();
			}
		}

		_prevSlope = slope;
	}
}