Открыть на GitHub

Стратегия Trend Line By Angle

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

Стратегия является портом советника MetaTrader Trend Line By Angle на StockSharp. В оригинале сделки открывались вручную нажатием кнопок, а управление риском реализовано множеством фильтров. Перенос автоматизирует работу за счёт MACD, но сохраняет все элементы защиты:

  • MACD 12/26/9 рассчитывается на свечах SignalCandleType. Пересечение вверх открывает длинные позиции, пересечение вниз — короткие.
  • Объём набирается порциями до значения TradeVolume * MaxEntries, что повторяет последовательные клики по кнопкам в MQL.
  • На торговом таймфрейме рассчитываются полосы Боллинджера 20/2. Касание верхней полосы закрывает лонги, касание нижней — шорты.
  • Стоп-лосс, тейк-профит, трейлинг и перевод в безубыток задаются в пунктах и пересчитываются через PriceStep инструмента.
  • Дополнительно действуют денежный и процентный тейк-профиты, а также денежный трейлинг общей прибыли.

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

  1. Подготовка индикаторов. MovingAverageConvergenceDivergenceSignal подключается к подписке SignalCandleType, BollingerBands — к CandleType.
  2. Формирование сигналов. После закрытия каждой свечи проверяется последнее пересечение MACD. При смене знака стратегия закрывает встречные позиции и открывает новые в сторону сигнала.
  3. Пошаговое наращивание. Пока суммарный объём меньше TradeVolume * MaxEntries, стратегия добавляет лоты рыночными заявками.
  4. Контроль риска. На каждой свече пересчитываются стопы, трейлинг и уровни безубытка. Касание полос Боллинджера принудительно закрывает позицию.
  5. Защита счёта. Сначала выполняются проверки денежных/процентных целей и трейлинга прибыли, и только затем анализируются новые сигналы.

Особенности управления капиталом

  • Суммарная прибыль = реализованная PnL + плавающая прибыль, оценённая по цене закрытия свечи и параметрам шага цены.
  • Перевод в безубыток: при профите свыше BreakEvenTriggerPips стоп переносится на Entry ± BreakEvenOffsetPips.
  • Трейлинг: при росте цены более чем на TrailingStopPips стоп подтягивается ближе к цене. Для лонгов уровень только повышается, для шортов только понижается.
  • Денежный трейлинг: после достижения MoneyTrailTrigger фиксируется максимум прибыли; просадка более MoneyTrailStop закрывает все сделки.

Параметры

Параметр Описание
TradeVolume Объём одной заявки.
MaxEntries Максимальное число заявок, формирующих позицию.
StopLossPips Расстояние стоп-лосса в пунктах.
TakeProfitPips Расстояние тейк-профита в пунктах.
TrailingStopPips Размер трейлинг-стопа в пунктах.
UseBreakEven Включение перевода в безубыток.
BreakEvenTriggerPips Порог активации безубытка.
BreakEvenOffsetPips Дополнительный запас при переносе стопа.
UseBollingerExit Использовать выходы по полосам Боллинджера.
BollingerPeriod / BollingerDeviation Настройки полос Боллинджера.
UseProfitMoneyTarget / ProfitMoneyTarget Денежная цель и её значение.
UseProfitPercentTarget / ProfitPercentTarget Процентная цель и её значение.
EnableMoneyTrail Включение денежного трейлинга прибыли.
MoneyTrailTrigger Минимальная прибыль для запуска трейлинга.
MoneyTrailStop Допустимая просадка от максимума прибыли.
MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod Параметры MACD.
CandleType Таймфрейм исполнения.
SignalCandleType Таймфрейм расчёта MACD.

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

  • Перед запуском убедитесь, что у инструмента задан корректный PriceStep и StepPrice, иначе расчёт пунктов и стоимости шага будет неточным.
  • Если провайдер портфеля не возвращает Portfolio.CurrentValue или Portfolio.BeginValue, процентный тейк-профит автоматически отключается.
  • В исходном C# файле все комментарии сделаны на английском языке для единообразия и будущей поддержки.
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;

public class TrendLineByAngleStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public TrendLineByAngleStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}