Открыть на GitHub

Стратегия Exp Skyscraper Fix + ColorAML + X2MA Candle MMRec

Обзор

  • Перенос эксперта MetaTrader Exp_Skyscraper_Fix_ColorAML_X2MACandle_MMRec на C# и платформу StockSharp.
  • Стратегия объединяет три независимых цветовых фильтра: канал Skyscraper Fix, адаптивный уровень ColorAML и двойное сглаживание свечей X2MA.
  • Каждый блок может открывать и закрывать позиции самостоятельно, работая по одному инструменту и сочетая трендовое сопровождение с быстрыми разворотами.
  • Модуль управления капиталом уменьшает объём сделки до SmallMM, когда серия последних сделок по направлению оказалась убыточной.

Логика стратегии

Блок Skyscraper Fix

  1. Формирует канал Skyscraper Fix на основе диапазона ATR и выбранного источника цены (High/Low либо Close).
  2. При смене цвета на бычий:
    • закрывает текущие короткие позиции, если это разрешено параметрами;
    • после выдержки, заданной Signal Bar, может открыть новую длинную позицию.
  3. При медвежьей окраске выполняется зеркальная логика для шортов.
  4. Параметры Kv и процентный сдвиг воспроизводят оригинальный индикатор.

Блок ColorAML

  1. Вычисляет Adaptive Market Level (AML), измеряя диапазон двух последовательных фрактальных окон и сглаживая комбинированную цену.
  2. Индикатор возвращает три цвета: 2 — бычий, 0 — медвежий, 1 — нейтральный. Нейтральные свечи не дают сигналов.
  3. Бычий цвет закрывает шорты (если разрешено) и может открыть лонг, если на предыдущей проверяемой свече цвет был другим.
  4. Медвежий цвет выполняет симметричные действия для коротких позиций.

Блок X2MA Candle

  1. Дважды сглаживает каждую компоненту OHLC выбранными скользящими средними, создавая синтетическую свечу.
  2. Цвет определяется телом сглаженной свечи: Close > Open — бычий, Close < Open — медвежий, равенство — нейтральный оттенок.
  3. Порог Gap (в шагах цены) сглаживает очень маленькие тела, предотвращая дрожание цвета.
  4. Бычий цвет закрывает шорты и допускает открытие лонга, медвежий — наоборот.

Управление капиталом

  1. Каждый блок ведёт собственную историю результатов по лонгам и шортам.
  2. После закрытия позиции фиксируется, была ли сделка убыточной.
  3. Если последние Loss Trigger сделок в данном направлении завершились с убытком, следующий ордер отправляется уменьшенным объёмом (SmallMM).
  4. После прибыльной или безубыточной сделки объём автоматически возвращается к базовому (MM).

Параметры

Блок Параметр Описание Значение по умолчанию
Skyscraper Skyscraper Candle Таймфрейм свечей для индикатора Skyscraper Fix. 4 часа
Skyscraper Skyscraper Length Длина окна ATR. 10
Skyscraper Skyscraper Kv Множитель чувствительности шага ATR. 0.9
Skyscraper Skyscraper Percentage Процентное смещение средней линии. 0
Skyscraper Skyscraper Mode Источник цены (High/Low или Close). High/Low
Skyscraper Skyscraper Signal Bar Количество закрытых свечей, которое нужно подождать перед действием. 1
Skyscraper Skyscraper Buy / Skyscraper Sell Разрешить открытие лонгов / шортов блоком Skyscraper. true
Skyscraper Skyscraper Close Long / Skyscraper Close Short Разрешить закрытие лонгов / шортов. true
Skyscraper Skyscraper Normal Volume Базовый объём (аналог MM). 0.1
Skyscraper Skyscraper Reduced Volume Уменьшенный объём при серии убытков (SmallMM). 0.01
Skyscraper Skyscraper Buy Loss Trigger / Skyscraper Sell Loss Trigger Число подряд убыточных сделок, переводящее на уменьшенный объём. 2
ColorAML ColorAML Candle Таймфрейм для индикатора ColorAML. 4 часа
ColorAML ColorAML Fractal Размер фрактального окна для расчёта диапазона. 6
ColorAML ColorAML Lag Параметр запаздывания, задающий адаптивное сглаживание. 7
ColorAML ColorAML Signal Bar Смещение по закрытым свечам при анализе цвета. 1
ColorAML ColorAML Buy / ColorAML Sell Разрешить открытия лонгов / шортов блоком ColorAML. true
ColorAML ColorAML Close Long / ColorAML Close Short Разрешить закрытие лонгов / шортов. true
ColorAML ColorAML Normal Volume / ColorAML Reduced Volume Базовый и уменьшенный объём сделок блока. 0.1 / 0.01
ColorAML ColorAML Buy Loss Trigger / ColorAML Sell Loss Trigger Порог убыточных сделок для переключения объёма. 2
X2MA X2MA Candle Таймфрейм для реконструкции свечей X2MA. 4 часа
X2MA First Method / Second Method Типы сглаживания первой и второй скользящей средней. SMA / JJMA
X2MA First Length / Second Length Периоды обеих стадий сглаживания. 12 / 5
X2MA First Phase / Second Phase Параметр фазы для Jurik MA. 15
X2MA Gap Points Порог (в шагах цены) для сглаживания маленьких тел свечей. 10
X2MA X2MA Signal Bar Смещение по закрытым свечам при чтении цвета. 1
X2MA X2MA Buy / X2MA Sell Разрешить открытия лонгов / шортов блоком X2MA. true
X2MA X2MA Close Long / X2MA Close Short Разрешить закрытие лонгов / шортов. true
X2MA X2MA Normal Volume / X2MA Reduced Volume Базовый и уменьшенный объём сделок блока. 0.1 / 0.01
X2MA X2MA Buy Loss Trigger / X2MA Sell Loss Trigger Число подряд убыточных сделок, активирующее уменьшенный объём. 2

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

  1. Подбирайте таймфреймы модулей под волатильность инструмента (для внутридневной торговли подойдут 1–2 часа, для свинга — 4 часа и выше).
  2. Любой блок можно отключить, остальные будут продолжать работу независимо.
  3. Порог Loss Trigger выбран консервативным. Если инструмент движется трендово, увеличьте значение, чтобы дольше сохранять базовый объём.
  4. Стратегия реагирует только на закрытые свечи, поэтому подавайте в неё свечные данные, соответствующие выбранным таймфреймам.
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

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

/// <summary>
/// Combines Skyscraper Fix, ColorAML and X2MA-style filters into a single consensus strategy.
/// </summary>
public class ExpSkyscraperFixColorAmlX2MaCandleMmRecStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _channelLength;
	private readonly StrategyParam<decimal> _channelFactor;
	private readonly StrategyParam<int> _amlLength;
	private readonly StrategyParam<int> _x2FastLength;
	private readonly StrategyParam<int> _x2SlowLength;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();
	private readonly List<decimal> _closes = new();
	private readonly List<decimal> _weightedPrices = new();
	private readonly List<decimal> _amlSeries = new();
	private readonly List<decimal> _fastSeries = new();
	private readonly List<decimal> _slowSeries = new();

	private decimal? _previousAml;
	private int _previousConsensus;
	private decimal? _entryPrice;
	private int _cooldownLeft;

	public ExpSkyscraperFixColorAmlX2MaCandleMmRecStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_channelLength = Param(nameof(ChannelLength), 10).SetGreaterThanZero().SetDisplay("Channel Length", "ATR channel length", "Skyscraper");
		_channelFactor = Param(nameof(ChannelFactor), 0.9m).SetGreaterThanZero().SetDisplay("Channel Factor", "ATR multiplier", "Skyscraper");
		_amlLength = Param(nameof(AmlLength), 7).SetGreaterThanZero().SetDisplay("AML Length", "Adaptive smoothing length", "ColorAML");
		_x2FastLength = Param(nameof(X2FastLength), 12).SetGreaterThanZero().SetDisplay("X2 Fast", "Fast smoothing length", "X2MA");
		_x2SlowLength = Param(nameof(X2SlowLength), 5).SetGreaterThanZero().SetDisplay("X2 Slow", "Slow smoothing length", "X2MA");
		_cooldownBars = Param(nameof(CooldownBars), 2).SetNotNegative().SetDisplay("Cooldown Bars", "Bars between flips", "Trading");
		_stopLossPips = Param(nameof(StopLossPips), 500).SetNotNegative().SetDisplay("Stop Loss", "Stop distance in pips", "Risk");
		_takeProfitPips = Param(nameof(TakeProfitPips), 900).SetNotNegative().SetDisplay("Take Profit", "Take-profit distance in pips", "Risk");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int ChannelLength { get => _channelLength.Value; set => _channelLength.Value = value; }
	public decimal ChannelFactor { get => _channelFactor.Value; set => _channelFactor.Value = value; }
	public int AmlLength { get => _amlLength.Value; set => _amlLength.Value = value; }
	public int X2FastLength { get => _x2FastLength.Value; set => _x2FastLength.Value = value; }
	public int X2SlowLength { get => _x2SlowLength.Value; set => _x2SlowLength.Value = value; }
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
	public int StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
	public int TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

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

		_highs.Clear();
		_lows.Clear();
		_closes.Clear();
		_weightedPrices.Clear();
		_amlSeries.Clear();
		_fastSeries.Clear();
		_slowSeries.Clear();
		_previousAml = null;
		_previousConsensus = 0;
		_entryPrice = null;
		_cooldownLeft = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		OnReseted();

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();
	}

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

		if (_cooldownLeft > 0)
			_cooldownLeft--;

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);
		_closes.Add(candle.ClosePrice);

		var weightedPrice = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 5m;
		_weightedPrices.Add(weightedPrice);

		var fast = CalculateEma(_closes, X2FastLength, _fastSeries);
		var slow = CalculateEma(_fastSeries, X2SlowLength, _slowSeries);
		var aml = CalculateEma(_weightedPrices, AmlLength, _amlSeries);

		if (Position != 0 && _entryPrice is null)
			_entryPrice = candle.ClosePrice;

		if (TryExitByRisk(candle))
			return;

		if (_highs.Count <= Math.Max(ChannelLength, Math.Max(AmlLength, X2FastLength + X2SlowLength)))
		{
			_previousAml = aml;
			return;
		}

		var skyscraperSignal = GetSkyscraperSignal();
		var colorAmlSignal = _previousAml is decimal previousAml
			? aml > previousAml ? 1 : aml < previousAml ? -1 : 0
			: 0;
		var x2MaSignal = fast > slow && candle.ClosePrice >= candle.OpenPrice
			? 1
			: fast < slow && candle.ClosePrice <= candle.OpenPrice
				? -1
				: 0;

		_previousAml = aml;

		var score = skyscraperSignal + colorAmlSignal + x2MaSignal;
		var consensus = score >= 2 ? 1 : score <= -2 ? -1 : 0;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_previousConsensus = consensus;
			return;
		}

		if (consensus == _previousConsensus || consensus == 0 || _cooldownLeft > 0)
		{
			_previousConsensus = consensus;
			return;
		}

		if (consensus > 0 && Position <= 0)
		{
			if (Position < 0)
			{
				BuyMarket(Math.Abs(Position));
				_entryPrice = null;
			}
			else
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
			}

			_cooldownLeft = CooldownBars;
		}
		else if (consensus < 0 && Position >= 0)
		{
			if (Position > 0)
			{
				SellMarket(Position);
				_entryPrice = null;
			}
			else
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
			}

			_cooldownLeft = CooldownBars;
		}

		_previousConsensus = consensus;
	}

	private int GetSkyscraperSignal()
	{
		var length = ChannelLength;
		if (_closes.Count < length || _highs.Count < length || _lows.Count < length)
			return 0;

		var start = _closes.Count - length;
		decimal atrSum = 0m;

		for (var i = start; i < _closes.Count; i++)
		{
			var high = _highs[i];
			var low = _lows[i];
			var previousClose = i > 0 ? _closes[i - 1] : _closes[i];
			var trueRange = Math.Max(high - low, Math.Max(Math.Abs(high - previousClose), Math.Abs(low - previousClose)));
			atrSum += trueRange;
		}

		var atr = atrSum / length;
		var middle = (_highs[^1] + _lows[^1]) / 2m;
		var upper = middle + atr * ChannelFactor;
		var lower = middle - atr * ChannelFactor;
		var close = _closes[^1];

		if (close > upper)
			return 1;

		if (close < lower)
			return -1;

		return 0;
	}

	private bool TryExitByRisk(ICandleMessage candle)
	{
		if (_entryPrice is not decimal entryPrice || Position == 0)
			return false;

		var step = Security?.PriceStep ?? 1m;
		if (step <= 0)
			step = 1m;

		var stopDistance = StopLossPips * step;
		var takeDistance = TakeProfitPips * step;

		if (Position > 0)
		{
			if ((stopDistance > 0 && candle.LowPrice <= entryPrice - stopDistance) ||
				(takeDistance > 0 && candle.HighPrice >= entryPrice + takeDistance))
			{
				SellMarket(Position);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
				return true;
			}
		}
		else if (Position < 0)
		{
			var volume = Math.Abs(Position);

			if ((stopDistance > 0 && candle.HighPrice >= entryPrice + stopDistance) ||
				(takeDistance > 0 && candle.LowPrice <= entryPrice - takeDistance))
			{
				BuyMarket(volume);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
				return true;
			}
		}

		return false;
	}

	private static decimal CalculateEma(IReadOnlyList<decimal> source, int length, List<decimal> target)
	{
		var multiplier = 2m / (length + 1m);
		var value = target is { Count: > 0 }
			? source[^1] * multiplier + target[^1] * (1m - multiplier)
			: source[^1];

		target?.Add(value);
		return value;
	}
}