Ver en GitHub

Estrategia Exp Skyscraper Fix + ColorAML + X2MA Candle MMRec

Descripción general

  • Conversión en C# del experto de MetaTrader Exp_Skyscraper_Fix_ColorAML_X2MACandle_MMRec.
  • Combina tres filtros independientes basados en color (canal Skyscraper Fix, nivel adaptativo ColorAML, velas de doble suavizado X2MACandle).
  • Cada filtro puede abrir o cerrar operaciones por sí solo mientras comparte el mismo símbolo, permitiendo seguimiento de tendencia cooperativo y reversales rápidas.
  • Incluye un módulo de gestión monetaria simplificado: cuando las últimas operaciones de una dirección pierden repetidamente, el módulo cambia al volumen reducido (SmallMM).

Lógica de la estrategia

Bloque Skyscraper Fix

  1. Construye el canal trailing de Skyscraper Fix analizando el rango ATR y la fuente de precio elegida (high/low o close).
  2. Cuando el color del canal se vuelve alcista, el bloque:
    • cierra cualquier posición corta pendiente (si Skyscraper Close Shorts está habilitado);
    • puede abrir una nueva posición larga después del retraso de señal configurado (si Skyscraper Buy está habilitado).
  3. Cuando el color se vuelve bajista, la lógica refleja los pasos para operaciones cortas.
  4. Los envelopes de high/low, el multiplicador ATR (Kv) y el offset porcentual reproducen el comportamiento del indicador original.

Bloque ColorAML

  1. Calcula el Nivel de Mercado Adaptativo (AML) midiendo el rango de dos ventanas fractales consecutivas y suavizando el precio compuesto.
  2. El indicador produce tres colores: 2 (alcista), 0 (bajista) y 1 (neutral). Las velas neutrales no desencadenan ninguna acción.
  3. Un color alcista cierra cortos (si está permitido) y puede abrir un largo cuando el color cambió desde alcista en la vela anterior inspeccionada.
  4. Un color bajista realiza las acciones simétricas para operaciones cortas.

Bloque X2MACandle

  1. Cascada de dos medias móviles configurables sobre cada componente OHLC (apertura, máximo, mínimo, cierre) para construir una vela sintética.
  2. El color se determina por el cuerpo de la vela suavizada: alcista cuando cierre > apertura, bajista cuando cierre < apertura, neutral en caso contrario.
  3. Un pequeño umbral de brecha (en pasos de precio) aplana cuerpos de velas muy pequeños para evitar flips rápidos de color.
  4. Los colores alcistas cierran cortos y pueden abrir largos; los colores bajistas realizan lo opuesto.

Gestión monetaria

  1. Cada bloque mantiene un historial independiente de sus propias operaciones para las direcciones larga y corta.
  2. Después de que una operación se cierra, el módulo registra si terminó con pérdida.
  3. Si las últimas Loss Trigger operaciones para una dirección fueron todas pérdidas, la siguiente orden de ese bloque cambia al volumen reducido (SmallMM).
  4. Cuando una operación rentable o neutral rompe la racha perdedora, el módulo vuelve automáticamente al volumen normal (MM).

Parámetros

Sección Nombre Descripción Por defecto
Skyscraper Skyscraper Candle Marco temporal para muestrear velas para el indicador Skyscraper Fix. 4h
Skyscraper Skyscraper Length Ventana de promedio ATR (número de velas). 10
Skyscraper Skyscraper Kv Multiplicador de sensibilidad aplicado al paso ATR. 0.9
Skyscraper Skyscraper Percentage Porcentaje adicional añadido/eliminado de la línea media. 0
Skyscraper Skyscraper Mode Fuente de precio (High/Low o Close) usada para los envelopes. High/Low
Skyscraper Skyscraper Signal Bar Número de velas ya cerradas a esperar antes de actuar sobre un color. 1
Skyscraper Skyscraper Buy / Skyscraper Sell Permitir apertura de operaciones largas / cortas. true
Skyscraper Skyscraper Close Long / Skyscraper Close Short Permitir a este bloque salir de operaciones largas / cortas. true
Skyscraper Skyscraper Normal Volume Volumen base de orden (MM en el EA). 0.1
Skyscraper Skyscraper Reduced Volume Volumen reducido de orden usado tras una racha de pérdidas (SmallMM). 0.01
Skyscraper Skyscraper Buy Loss Trigger / Skyscraper Sell Loss Trigger Número de pérdidas consecutivas que cambian al volumen reducido. 2
ColorAML ColorAML Candle Tipo de vela usado por el indicador ColorAML. 4h
ColorAML ColorAML Fractal Ventana fractal (en barras) usada para el cálculo de rango. 6
ColorAML ColorAML Lag Parámetro de lag que controla el suavizado adaptativo. 7
ColorAML ColorAML Signal Bar Offset de vela aplicado antes de evaluar colores. 1
ColorAML ColorAML Buy / ColorAML Sell Habilitar entradas largas / cortas generadas por ColorAML. true
ColorAML ColorAML Close Long / ColorAML Close Short Permitir a ColorAML cerrar posiciones largas / cortas. true
ColorAML ColorAML Normal Volume / ColorAML Reduced Volume Volúmenes base y reducido para este bloque. 0.1 / 0.01
ColorAML ColorAML Buy Loss Trigger / ColorAML Sell Loss Trigger Pérdidas consecutivas que activan el volumen reducido. 2
X2MA X2MA Candle Marco temporal usado para la reconstrucción de velas X2MACandle. 4h
X2MA First Method / Second Method Métodos de suavizado para la primera y segunda media móvil. SMA / JJMA
X2MA First Length / Second Length Períodos de las dos etapas de suavizado. 12 / 5
X2MA First Phase / Second Phase Fases de compatibilidad usadas por las medias móviles Jurik. 15
X2MA Gap Points Umbral de brecha (en pasos de precio) que aplana cuerpos de velas pequeños. 10
X2MA X2MA Signal Bar Offset de vela aplicado antes de reaccionar a los colores. 1
X2MA X2MA Buy / X2MA Sell Permitir apertura de operaciones largas / cortas desde el bloque X2MACandle. true
X2MA X2MA Close Long / X2MA Close Short Permitir al bloque salir de posiciones largas / cortas. true
X2MA X2MA Normal Volume / X2MA Reduced Volume Volúmenes base y reducido para operaciones X2MACandle. 0.1 / 0.01
X2MA X2MA Buy Loss Trigger / X2MA Sell Loss Trigger Número de pérdidas consecutivas antes de cambiar al volumen reducido. 2

Consejos de uso

  1. Ajuste los tipos de velas para que coincidan con la volatilidad del mercado (por ejemplo, 1h para trading intradía, 4h para swing trading).
  2. Los tres módulos pueden ajustarse independientemente; deshabilitar un bloque deja a los otros activos.
  3. Los umbrales de gestión monetaria son intencionalmente conservadores. Aumente los disparadores si el instrumento tiene tendencias fuertes y quiere mantener el volumen base por más tiempo.
  4. Dado que la estrategia depende de velas terminadas, siempre aliméntela con datos de velas que coincidan con los marcos temporales configurados.
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;
	}
}