Открыть на GitHub

Стратегия Color X Derivative

Обзор

Стратегия представляет собой перенос советника MetaTrader "Exp_ColorXDerivative" на платформу StockSharp. Она работает на настраиваемом таймфрейме свечей (по умолчанию 12 часов) и анализирует гистограмму импульса ColorXDerivative. Индикатор измеряет скорость изменения выбранной цены за фиксированный сдвиг, сглаживает результат скользящей средней и классифицирует каждый бар в один из пяти цветовых состояний. Сделки повторяют оригинальную логику: робот покупает при ускорении бычьего импульса или при ослаблении нисходящего движения, и продаёт при усилении медвежьего давления или затухании бычьей ноги.

Логика индикатора

  1. Преобразовать свечу в выбранный тип цены AppliedPrice (close, open, weighted close, Demark и т.д.).
  2. Вычислить производную цены: (price[0] - price[shift]) * 100 / shift, где shift = DerivativePeriod.
  3. Сгладить производную выбранным методом (SMA, EMA, SMMA, LWMA или Jurik). По умолчанию используется Jurik Moving Average как аналог JJMA из библиотеки MQL.
  4. Назначить цветовое состояние:
    • 0 – производная > 0 и растёт (сильное бычье ускорение).
    • 1 – производная > 0, но падает (ослабление бычьего импульса).
    • 2 – производная ≈ 0 (нейтральное состояние).
    • 3 – производная < 0, но растёт (сжатие медвежьего движения).
    • 4 – производная < 0 и падает (усиление медвежьего импульса).

Параметр SignalShift определяет, какой завершённый бар используется для сигналов (1 = последний закрытый бар, 2 = предыдущий и т.д.).

Правила торговли

  • Вход в лонг (если EnableLongEntry = true):
    • текущий цвет 0, а предыдущий отличался от 0 (резкое ускорение вверх); или
    • текущий цвет 3, а предыдущий был 4 либо 2 (нисходящее движение ослабевает).
  • Вход в шорт (если EnableShortEntry = true):
    • текущий цвет 4, а предыдущий отличался от 4 (ускорение вниз); или
    • текущий цвет 1, а предыдущий был 0 либо 2 (затухание бычьего импульса).
  • Выход из лонга: текущий цвет 1 либо 4 и включён EnableLongExit.
  • Выход из шорта: текущий цвет 0 либо 3 и включён EnableShortExit.

Заявки отправляются по рынку с объёмом OrderVolume. Перед открытием новой позиции выполняется закрытие текущей, что повторяет последовательную работу оригинального советника.

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

Параметры StopLossTicks и TakeProfitTicks задают необязательные защитные дистанции в шагах цены. Если любое значение больше нуля, метод StartProtection конвертирует тики в шаги инструмента (Security.Step) и активирует стоп/тейк-профит один раз. Механизм работает как в автоторговле, так и в тестировании.

Параметры

  • OrderVolume – объём рыночных заявок.
  • CandleType – таймфрейм для расчёта индикатора (по умолчанию 12 часов).
  • DerivativePeriod – сдвиг в барах для вычисления производной.
  • AppliedPrice – источник цены (close, median, weighted, Demark и т.д.).
  • SmoothingMethod – метод сглаживания производной (SMA, EMA, SMMA, LWMA, Jurik).
  • SmoothingLength – период сглаживающего фильтра.
  • SignalShift – сколько закрытых баров назад считывать цвета (1 = последний бар).
  • StopLossTicks / TakeProfitTicks – необязательные защитные дистанции в шагах цены.
  • EnableLongEntry, EnableShortEntry, EnableLongExit, EnableShortExit – флаги, повторяющие входные параметры MQL-версии.

Примечания

  • Реализация полностью повторяет индикаторную логику MetaTrader без дополнительных блоков мани-менеджмента.
  • Сглаживание Jurik является ближайшим аналогом фильтра JJMA из библиотеки SmoothAlgorithms, остальные методы сопоставлены стандартным скользящим средним StockSharp.
  • История цветов хранится внутри индикатора, поэтому оптимизация по SignalShift даёт такие же результаты, как в оригинальной платформе.
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>
/// Color X Derivative strategy (simplified). Uses Momentum to detect
/// acceleration/deceleration and generate reversal signals.
/// </summary>
public class ColorXDerivativeStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _momentumLength;
	private readonly StrategyParam<int> _emaLength;

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

	public int MomentumLength
	{
		get => _momentumLength.Value;
		set => _momentumLength.Value = value;
	}

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

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

		_momentumLength = Param(nameof(MomentumLength), 34)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Length", "Derivative lookback", "Indicators");

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

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

		var momentum = new Momentum { Length = MomentumLength };
		var ema = new ExponentialMovingAverage { Length = EmaLength };

		decimal prevMom = 0;
		var hasPrev = false;

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

				if (!hasPrev)
				{
					prevMom = momValue;
					hasPrev = true;
					return;
				}

				if (!IsFormedAndOnlineAndAllowTrading())
				{
					prevMom = momValue;
					return;
				}

				var close = candle.ClosePrice;

				// Momentum turning positive with price above EMA
				if (prevMom <= 0 && momValue > 0 && close > emaValue && Position <= 0)
					BuyMarket();
				// Momentum turning negative with price below EMA
				else if (prevMom >= 0 && momValue < 0 && close < emaValue && Position >= 0)
					SellMarket();

				prevMom = momValue;
			})
			.Start();

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