Открыть на GitHub

Стратегия Cryptocurrency Divergence

Обзор

Cryptocurrency Divergence Strategy выявляет классические дивергенции между ценой и индексом относительной силы (RSI), одновременно подтверждая направление тренда с помощью скользящих средних и MACD. Оригинальный советник MetaTrader использовал многопериодный анализ импульса, сложное сопровождение позиций и денежные фильтры. Порт на StockSharp сохраняет задумку:

  • Определяет бычью дивергенцию, когда цена формирует более низкий минимум, а RSI — более высокий минимум.
  • Определяет медвежью дивергенцию, когда цена обновляет максимум, но RSI показывает более низкий максимум.
  • Проверяет сигнал фильтром из быстрой/медленной SMA и сравнением линии MACD с сигнальной линией.
  • Управляет позицией через настраиваемые стоп-лосс, тейк-профит, перевод в безубыток и трейлинг, выраженные в шагах цены.

Стратегия ориентирована на криптовалютные рынки, однако подходит для любого волатильного инструмента с четко выраженными свингами.

Индикаторы

  • Простая скользящая средняя (SMA): быстрая и медленная средние формируют фильтр тренда.
  • RSI: фиксирует значения импульса на экстремумах для оценки дивергенции.
  • MACD: подтверждает, что импульс поддерживает найденную дивергенцию.

Все индикаторы подключаются через высокоуровневый API, поэтому буферы и ручные расчеты не требуются.

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

  1. Подписываемся на выбранный тип свечей и на каждой закрытой свече рассчитываем SMA, RSI и MACD.
  2. Отслеживаем последние экстремумы цены и соответствующие им значения RSI. Данные обновляются только при формировании новых максимумов или минимумов.
  3. Бычья дивергенция появляется, когда минимум цены обновляется, RSI растет, быстрая SMA находится выше медленной, MACD выше сигнальной линии, а RSI остается ниже бычьего порога (по умолчанию 45).
  4. Медвежья дивергенция требует нового максимума цены, падения RSI на экстремуме, расположения быстрой SMA ниже медленной, линии MACD ниже сигнальной и значения RSI выше медвежьего порога (по умолчанию 55).
  5. В рынке одновременно может находиться только одна чистая позиция. При смене сигнала стратегия закрывает текущую позицию и сразу переходит в противоположную.

Управление рисками

  • Объем сделки: задается пользователем и применяется ко всем рыночным ордерам.
  • Стоп-лосс и тейк-профит: рассчитываются в шагах цены и выставляются после фактического исполнения.
  • Перевод в безубыток: при достижении заданной прибыли стоп переносится к цене входа с заданным отступом.
  • Трейлинг-стоп: следует за ценой на фиксированном расстоянии и имеет приоритет над исходным стопом после активации.

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

Параметры

Имя Описание
CandleType Тип свечей для анализа (по умолчанию 15 минут).
TradeVolume Объем входа в позицию.
FastMaLength / SlowMaLength Периоды быстрой и медленной SMA.
RsiLength Период RSI.
RsiBullishLevel / RsiBearishLevel Пороговые значения RSI для подтверждения бычьей и медвежьей дивергенции.
MacdShortLength / MacdLongLength / MacdSignalLength Настройки MACD.
StopLossPoints / TakeProfitPoints Расстояние до стопа и цели в шагах цены.
EnableBreakEven, BreakEvenTrigger, BreakEvenOffset Управление переводом в безубыток.
EnableTrailing, TrailDistance Управление трейлинг-стопом.

Параметры объявлены через StrategyParam<T>, что позволяет оптимизировать стратегию в дизайнере StockSharp.

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

  1. Подключите стратегию к выбранному инструменту и убедитесь, что для него заданы PriceStep и Board, иначе расчет защитных уровней невозможен.
  2. Подберите таймфрейм свечей в соответствии с торгуемым рынком (15 минут, час и т.д.), так как распознавание дивергенций зависит от масштаба.
  3. Настройте расстояния стопов и целей под среднюю волатильность инструмента. Для пар с пятью знаками после запятой потребуются увеличенные значения.
  4. Активируйте перевод в безубыток и трейлинг только после проверки на истории, чтобы избежать преждевременного выхода из прибыльных сделок.
  5. Используйте дизайнер StockSharp для визуального контроля индикаторов и исполнения ордеров.

Отличия от версии MQL

  • Денежные тралы и останов по капиталу заменены на универсальное управление стопами по шагам цены.
  • Многопериодные проверки импульса заменены на подтверждение по MACD в одном таймфрейме для повышения прозрачности.
  • Удалены уведомления по почте/пушу, которые могут быть реализованы сторонними сервисами StockSharp.

Несмотря на упрощения, стратегия сохраняет основную идею поиска дивергенций и защиту позиций исходного советника.

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 CryptocurrencyDivergenceStrategy : 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 CryptocurrencyDivergenceStrategy()
	{
		_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;
	}
}