Ver no GitHub

Exp Skyscraper Fix + ColorAML + X2MA Candle MMRec Strategy

Overview

  • C# conversion of the MetaTrader expert Exp_Skyscraper_Fix_ColorAML_X2MACandle_MMRec.
  • Combines three independent colour-based filters (Skyscraper Fix channel, ColorAML adaptive level, X2MA double-smoothed candles).
  • Each filter can open or close trades on its own while sharing the same symbol, enabling cooperative trend following and rapid reversals.
  • Includes a simplified money management module: when the latest trades of one direction lose repeatedly the module switches to the reduced volume (SmallMM).

Strategy Logic

Skyscraper Fix Block

  1. Builds the Skyscraper Fix trailing channel by analysing the ATR range and the chosen price source (high/low or close).
  2. When the channel colour turns bullish the block:
    • closes any outstanding short position (if Skyscraper Close Shorts is enabled);
    • may open a new long position after the configured signal delay (if Skyscraper Buy is enabled).
  3. When the colour turns bearish the logic mirrors the steps for short trades.
  4. The high/low envelopes, ATR multiplier (Kv) and percentage offset reproduce the behaviour of the original indicator.

ColorAML Block

  1. Calculates the Adaptive Market Level (AML) by measuring the range of two consecutive fractal windows and smoothing the composite price.
  2. The indicator outputs three colours: 2 (bullish), 0 (bearish) and 1 (neutral). Neutral candles do not trigger any action.
  3. A bullish colour closes shorts (if allowed) and may open a long when the colour changed away from bullish on the previous inspected candle.
  4. A bearish colour performs the symmetric actions for short trades.

X2MA Candle Block

  1. Cascades two configurable moving averages over each OHLC component (open, high, low, close) to build a synthetic candle.
  2. The colour is determined by the smoothed candle body: bullish when close > open, bearish when close < open, neutral otherwise.
  3. A small gap threshold (in price steps) flattens very small candle bodies to avoid rapid colour flips.
  4. Bullish colours close shorts and can open longs, bearish colours perform the opposite.

Money Management

  1. Each block keeps an independent history of its own trades for long and short directions.
  2. After a trade is closed the module records whether it ended with a loss.
  3. If the last Loss Trigger trades for a direction were all losses, the next order from that block switches to the reduced volume (SmallMM).
  4. When a profitable or neutral trade breaks the losing streak the module automatically returns to the normal volume (MM).

Parameters

Section Name Description Default
Skyscraper Skyscraper Candle Timeframe used to sample candles for the Skyscraper Fix indicator. 4h
Skyscraper Skyscraper Length ATR averaging window (number of candles). 10
Skyscraper Skyscraper Kv Sensitivity multiplier applied to the ATR step. 0.9
Skyscraper Skyscraper Percentage Additional percentage added to/removed from the midline. 0
Skyscraper Skyscraper Mode Price source (High/Low or Close) used for envelopes. High/Low
Skyscraper Skyscraper Signal Bar Number of already closed candles to wait before acting on a colour. 1
Skyscraper Skyscraper Buy / Skyscraper Sell Allow opening long / short trades. true
Skyscraper Skyscraper Close Long / Skyscraper Close Short Allow this block to exit long / short trades. true
Skyscraper Skyscraper Normal Volume Base order volume (MM in the EA). 0.1
Skyscraper Skyscraper Reduced Volume Reduced order volume used after a loss streak (SmallMM). 0.01
Skyscraper Skyscraper Buy Loss Trigger / Skyscraper Sell Loss Trigger Number of consecutive losses that switch to the reduced volume. 2
ColorAML ColorAML Candle Candle type used by the ColorAML indicator. 4h
ColorAML ColorAML Fractal Fractal window (in bars) used for the range calculation. 6
ColorAML ColorAML Lag Lag parameter that controls the adaptive smoothing. 7
ColorAML ColorAML Signal Bar Candle offset applied before evaluating colours. 1
ColorAML ColorAML Buy / ColorAML Sell Enable long / short entries generated by ColorAML. true
ColorAML ColorAML Close Long / ColorAML Close Short Allow ColorAML to close long / short positions. true
ColorAML ColorAML Normal Volume / ColorAML Reduced Volume Base and reduced volumes for this block. 0.1 / 0.01
ColorAML ColorAML Buy Loss Trigger / ColorAML Sell Loss Trigger Consecutive losses that activate the reduced volume. 2
X2MA X2MA Candle Timeframe used for the X2MA candle reconstruction. 4h
X2MA First Method / Second Method Smoothing methods for the first and second moving averages. SMA / JJMA
X2MA First Length / Second Length Periods of the two smoothing stages. 12 / 5
X2MA First Phase / Second Phase Compatibility phases used by Jurik moving averages. 15
X2MA Gap Points Gap threshold (in price steps) that flattens small candle bodies. 10
X2MA X2MA Signal Bar Candle offset applied before reacting to colours. 1
X2MA X2MA Buy / X2MA Sell Allow opening long / short trades from the X2MA block. true
X2MA X2MA Close Long / X2MA Close Short Allow the block to exit long / short positions. true
X2MA X2MA Normal Volume / X2MA Reduced Volume Base and reduced volumes for X2MA trades. 0.1 / 0.01
X2MA X2MA Buy Loss Trigger / X2MA Sell Loss Trigger Number of consecutive losses before switching to the reduced volume. 2

Usage Tips

  1. Adjust candle types to match the market's volatility (e.g., 1h for intraday trading, 4h for swing trading).
  2. The three modules can be tuned independently—disabling one block still leaves the others active.
  3. The money management thresholds are intentionally conservative. Increase the triggers if the instrument trends strongly and you want to maintain the base volume longer.
  4. Because the strategy relies on finished candles, always feed it with candle data that matches the configured timeframes.
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;
	}
}