Ver en GitHub

Estrategia de Vela de Centro de Gravedad

Esta estrategia replica el experto MetaTrader "Exp_CenterOfGravityCandle" utilizando la API de alto nivel de StockSharp. El experto opera con velas sintéticas generadas por el indicador Center of Gravity Candle. Cada vela sintética se construye aplicando el cálculo del Centro de Gravedad de John Ehlers a los precios de apertura, máximo, mínimo y cierre, y luego suavizando los resultados con una media móvil configurable. El color de la vela sintética (alcista, bajista o neutral) es la única señal de trading.

Lógica del indicador

  1. Cada vela de mercado entrante se procesa después de que esté completamente cerrada.
  2. Para cada componente de precio (apertura, máximo, mínimo, cierre) la estrategia calcula dos medias móviles: una MA simple y una MA ponderada linealmente con el período definido por Period.
  3. El producto de estas dos medias se divide por el paso de precio del instrumento y se suaviza con el método configurado (Ma Method) y la longitud (Smooth Period).
  4. Los máximos y mínimos sintéticos se fuerzan para incluir la apertura/cierre sintéticos de modo que los cuerpos de las velas sean consistentes con la implementación de MetaTrader.
  5. El color de la vela se determina comparando la apertura y el cierre sintéticos: apertura por debajo del cierre = alcista (color 2), apertura por encima del cierre = bajista (color 0), de lo contrario neutral (color 1).

Reglas de trading

  1. La estrategia mantiene un historial de colores de velas sintéticas e inspecciona la barra definida por Signal Bar (predeterminado = barra finalizada anterior).
  2. Cuando la vela sintética inspeccionada se vuelve alcista y la vela anterior no era alcista:
    • Cerrar cualquier posición corta existente si Enable Sell Close es true.
    • Abrir una nueva posición larga si Enable Buy Open es true.
  3. Cuando la vela sintética inspeccionada se vuelve bajista y la vela anterior no era bajista:
    • Cerrar cualquier posición larga existente si Enable Buy Close es true.
    • Abrir una nueva posición corta si Enable Sell Open es true.
  4. Las entradas de mercado utilizan el volumen calculado a partir de Money Management y Margin Mode. Los valores negativos para Money Management se tratan como un tamaño de lote fijo. Para los modos basados en pérdidas, el algoritmo aproxima el riesgo por operación usando la distancia de stop-loss configurada.
  5. StartProtection se activa para colocar automáticamente órdenes de take-profit y stop-loss según las distancias Take Profit y Stop Loss expresadas en pasos de precio.

Parámetros

  • Money Management – fracción del valor de la cuenta utilizada para derivar el volumen de la orden (valores negativos = lote fijo). Optimizable.
  • Margin Mode – interpretación del parámetro de gestión del dinero (basado en equity, basado en balance, basado en pérdida o lote fijo).
  • Stop Loss – distancia del stop-loss en pasos de precio. Se usa tanto para órdenes de protección como para el dimensionamiento de posiciones basado en riesgo.
  • Take Profit – distancia del take-profit en pasos de precio. Aplicado a través de StartProtection.
  • Open Long / Open Short – permitir abrir posiciones largas/cortas en sus respectivas señales.
  • Close Long / Close Short – permitir cerrar posiciones largas/cortas cuando aparece la señal opuesta.
  • Candle Type – marco temporal de las velas utilizadas para el cálculo del indicador.
  • Center of Gravity Period – período base para las medias móviles simple y ponderada linealmente. Optimizable.
  • Smoothing Period – longitud de la media móvil de suavizado aplicada a las velas sintéticas. Optimizable.
  • Smoothing Method – tipo de media móvil usado en la etapa de suavizado (SMA, EMA, SMMA o LWMA).
  • Signal Bar – índice de la vela sintética usada para generar señales (0 = actual, 1 = anterior, etc.).

Notas

  • El cálculo del indicador está implementado en C# para reproducir la lógica original de MetaTrader, evitando buffers manuales o colecciones históricas.
  • El cálculo del volumen utiliza información del portafolio de StockSharp y puede diferir ligeramente de los resultados de MetaTrader debido a diferencias de plataforma.
  • La estrategia opera completamente sobre velas terminadas y nunca opera en barras parciales.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that trades synthetic candles generated by Center of Gravity calculation.
/// Uses SMA and LWMA products smoothed to create synthetic OHLC, then trades color changes.
/// </summary>
public class CenterOfGravityCandleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<int> _smoothPeriod;

	// Internal indicators for computing synthetic candle
	private SimpleMovingAverage _openSma;
	private SimpleMovingAverage _highSma;
	private SimpleMovingAverage _lowSma;
	private SimpleMovingAverage _closeSma;
	private WeightedMovingAverage _openLwma;
	private WeightedMovingAverage _highLwma;
	private WeightedMovingAverage _lowLwma;
	private WeightedMovingAverage _closeLwma;
	private SimpleMovingAverage _openSmooth;
	private SimpleMovingAverage _highSmooth;
	private SimpleMovingAverage _lowSmooth;
	private SimpleMovingAverage _closeSmooth;

	private int _prevColor; // 0=neutral, 1=bullish, -1=bearish
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int Period { get => _period.Value; set => _period.Value = value; }
	public int SmoothPeriod { get => _smoothPeriod.Value; set => _smoothPeriod.Value = value; }

	public CenterOfGravityCandleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_period = Param(nameof(Period), 10)
			.SetGreaterThanZero()
			.SetDisplay("CoG Period", "Center of gravity period", "Indicator");

		_smoothPeriod = Param(nameof(SmoothPeriod), 1)
			.SetGreaterThanZero()
			.SetDisplay("Smooth Period", "Smoothing period", "Indicator");
	}

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

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

		_prevColor = 0;
		_hasPrev = false;
		_openSma = null;
		_highSma = null;
		_lowSma = null;
		_closeSma = null;
		_openLwma = null;
		_highLwma = null;
		_lowLwma = null;
		_closeLwma = null;
		_openSmooth = null;
		_highSmooth = null;
		_lowSmooth = null;
		_closeSmooth = null;
	}

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

		var len = Period;
		var sLen = SmoothPeriod;

		_openSma = new SMA { Length = len };
		_highSma = new SMA { Length = len };
		_lowSma = new SMA { Length = len };
		_closeSma = new SMA { Length = len };
		_openLwma = new WeightedMovingAverage { Length = len };
		_highLwma = new WeightedMovingAverage { Length = len };
		_lowLwma = new WeightedMovingAverage { Length = len };
		_closeLwma = new WeightedMovingAverage { Length = len };
		_openSmooth = new SMA { Length = sLen };
		_highSmooth = new SMA { Length = sLen };
		_lowSmooth = new SMA { Length = sLen };
		_closeSmooth = new SMA { Length = sLen };

		_prevColor = 0;
		_hasPrev = false;

		var sub = SubscribeCandles(CandleType);
		sub.Bind(ProcessCandle).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, sub);
			DrawOwnTrades(area);
		}
	}

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

		var t = candle.OpenTime;
		var point = Security?.PriceStep ?? 1m;
		if (point <= 0m) point = 1m;

		// Compute SMA and LWMA for each OHLC component
		var oSma = _openSma.Process(candle.OpenPrice, t, true);
		var oLwma = _openLwma.Process(candle.OpenPrice, t, true);
		var hSma = _highSma.Process(candle.HighPrice, t, true);
		var hLwma = _highLwma.Process(candle.HighPrice, t, true);
		var lSma = _lowSma.Process(candle.LowPrice, t, true);
		var lLwma = _lowLwma.Process(candle.LowPrice, t, true);
		var cSma = _closeSma.Process(candle.ClosePrice, t, true);
		var cLwma = _closeLwma.Process(candle.ClosePrice, t, true);

		if (!_openSma.IsFormed || !_openLwma.IsFormed ||
		    !_highSma.IsFormed || !_highLwma.IsFormed ||
		    !_lowSma.IsFormed || !_lowLwma.IsFormed ||
		    !_closeSma.IsFormed || !_closeLwma.IsFormed)
			return;

		// Use average of SMA and LWMA instead of product (avoids overflow with high prices)
		var openProd = (oSma.ToDecimal() + oLwma.ToDecimal()) / 2m;
		var highProd = (hSma.ToDecimal() + hLwma.ToDecimal()) / 2m;
		var lowProd = (lSma.ToDecimal() + lLwma.ToDecimal()) / 2m;
		var closeProd = (cSma.ToDecimal() + cLwma.ToDecimal()) / 2m;

		// Smooth
		var oSmooth = _openSmooth.Process(openProd, t, true);
		var hSmooth = _highSmooth.Process(highProd, t, true);
		var lSmooth = _lowSmooth.Process(lowProd, t, true);
		var cSmooth = _closeSmooth.Process(closeProd, t, true);

		if (!_openSmooth.IsFormed || !_highSmooth.IsFormed ||
		    !_lowSmooth.IsFormed || !_closeSmooth.IsFormed)
			return;

		var openVal = oSmooth.ToDecimal();
		var closeVal = cSmooth.ToDecimal();

		// Determine color
		int color;
		if (openVal < closeVal)
			color = 1; // bullish
		else if (openVal > closeVal)
			color = -1; // bearish
		else
			color = 0; // neutral

		if (_hasPrev)
		{
			// Bullish color change
			if (color == 1 && _prevColor != 1 && Position <= 0)
				BuyMarket();
			// Bearish color change
			else if (color == -1 && _prevColor != -1 && Position >= 0)
				SellMarket();
		}

		_prevColor = color;
		_hasPrev = true;
	}
}