Открыть на GitHub

Стратегия TrainYourself

Стратегия является портом MetaTrader 4 советника TrainYourself-V1_1-1 на платформу StockSharp. В оригинале построение канала и открытия/закрытия позиций выполнялись через элементы на графике. В версии для StockSharp канал рассчитывается автоматически на каждой завершённой свече, а для ручного управления предоставлены отдельные методы.

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

  1. Построение канала

    • На каждой завершённой свече выбранного CandleType вычисляется индикатор DonchianChannels длиной ChannelLength.
    • Верхняя и нижняя границы дополнительно расширяются на BufferPoints, умноженные на биржевой шаг цены (PriceStep). Это воспроизводит приём из MQL, где линии сначала ставились на расстоянии 50 пунктов от текущих bid/ask, а затем сдвигались к экстремумам последних баров.
    • Получившиеся значения доступны через свойства UpperBand и LowerBand, что позволяет отображать их в пользовательских панелях.
  2. Подготовка к пробою

    • Пока открыта позиция или параметр EnableTrendTrade выключен, автоматическая логика не активна.
    • Если позиция отсутствует, а цена закрылась внутри канала и держится на расстоянии не менее ActivationPoints × PriceStep от обеих границ, флаг _isArmed становится true. Это полный аналог переменной q из оригинального советника.
  3. Открытие сделок

    • При активном флаге IsArmed закрытие свечи выше или на уровне UpperBand вызывает рыночную покупку (при условии AllowBuyOpen).
    • Закрытие ниже или на уровне LowerBand формирует рыночную продажу (если AllowSellOpen разрешён).
    • После входа стратегия мгновенно сбрасывает состояние подготовки и ждёт, пока цена снова вернётся внутрь канала без открытых позиций.
  4. Управление рисками

    • Метод StartProtection выставляет защитные ордера. Дистанции вычисляются как TakeProfitPoints × PriceStep и StopLossPoints × PriceStep. При отсутствии шага цены используется значение 0.0001, что соответствует поведению MetaTrader.
  5. Ручной контроль

    • Объекты BUY_TRIANGLE, SELL_TRIANGLE и CLOSE_ORDER заменены публичными методами TriggerManualBuy(), TriggerManualSell() и ClosePositionManually().
    • Перед исполнением методы проверяют AllowBuyOpen/AllowSellOpen и состояние подключения через IsFormedAndOnlineAndAllowTrading(), а после исполнения отключают автоматический режим, чтобы ручные позиции не перекрывались мгновенно автоматикой.

Параметры

Параметр Значение по умолчанию Описание
CandleType таймфрейм 30m Основной поток свечей для расчётов.
ChannelLength 20 Количество баров в канале Дончиана.
BufferPoints 50 Дополнительный запас в пунктах вокруг последней цены закрытия.
ActivationPoints 2 Минимальная дистанция до границ канала перед активацией пробойной логики.
StopLossPoints 100 Размер стоп-лосса в пунктах (переводится в цену через PriceStep).
TakeProfitPoints 100 Размер тейк-профита в пунктах (переводится в цену через PriceStep).
EnableTrendTrade true Включает автоматические сделки по пробою. При false доступны только ручные методы.
Volume 1 Объём заявок для автоматических и ручных входов.

Рекомендации по эксплуатации

  • В MetaTrader пользователь должен был перемещать элементы на графике, чтобы обновить линии. В StockSharp канал пересчитывается после каждой свечи, поэтому ручных действий не требуется.
  • Свойства UpperBand, LowerBand и IsArmed позволяют строить собственные визуализации состояния стратегии.
  • Чтобы отключить защитные ордера, установите StopLossPoints или TakeProfitPoints в ноль — это повторяет поведение MQL-версии.
  • Ручные сделки используют тот же Volume и автоматически получают заданные стопы/профиты.
  • Для принудительного сброса режима ожидания можно вызвать ClosePositionManually() или дождаться, когда цена вновь окажется внутри канала и выполнит условия активации.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// TrainYourself: Donchian channel breakout strategy.
/// Uses Highest/Lowest as channel, arms after price is inside channel,
/// then trades breakouts.
/// </summary>
public class TrainYourselfStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _channelLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevHighest;
	private decimal _prevLowest;
	private bool _isArmed;
	private decimal _entryPrice;

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

		_channelLength = Param(nameof(ChannelLength), 20)
			.SetDisplay("Channel Length", "Highest/Lowest period.", "Channel");

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

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

	public int ChannelLength
	{
		get => _channelLength.Value;
		set => _channelLength.Value = value;
	}

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

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

		_prevHighest = 0;
		_prevLowest = 0;
		_isArmed = false;
		_entryPrice = 0;
	}

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

		_prevHighest = 0;
		_prevLowest = 0;
		_isArmed = false;
		_entryPrice = 0;

		var highest = new Highest { Length = ChannelLength };
		var lowest = new Lowest { Length = ChannelLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		if (_prevHighest == 0 || _prevLowest == 0 || atrVal <= 0)
		{
			_prevHighest = highVal;
			_prevLowest = lowVal;
			return;
		}

		var close = candle.ClosePrice;
		var upper = _prevHighest;
		var lower = _prevLowest;

		// Manage position: exit on channel re-entry
		if (Position > 0)
		{
			if (close < (upper + lower) / 2m)
			{
				SellMarket();
				_entryPrice = 0;
				_isArmed = false;
			}
		}
		else if (Position < 0)
		{
			if (close > (upper + lower) / 2m)
			{
				BuyMarket();
				_entryPrice = 0;
				_isArmed = false;
			}
		}

		// Arm when price is inside channel
		if (Position == 0)
		{
			if (!_isArmed)
			{
				var margin = atrVal * 0.2m;
				if (close > lower + margin && close < upper - margin)
					_isArmed = true;
			}
			else
			{
				// Breakout entry
				if (close > upper)
				{
					_entryPrice = close;
					BuyMarket();
					_isArmed = false;
				}
				else if (close < lower)
				{
					_entryPrice = close;
					SellMarket();
					_isArmed = false;
				}
			}
		}

		_prevHighest = highVal;
		_prevLowest = lowVal;
	}
}