Auf GitHub ansehen

Center Of Gravity Candle Strategy

This strategy replicates the MetaTrader expert "Exp_CenterOfGravityCandle" using the StockSharp high level API. The expert trades synthetic candles generated by the Center of Gravity Candle indicator. Each synthetic candle is built by applying John Ehlers' Center of Gravity calculation to the open, high, low and close prices and then smoothing the results with a configurable moving average. The color of the synthetic candle (bullish, bearish or neutral) is the only trading signal.

Indicator logic

  1. Each incoming market candle is processed after it is fully closed.
  2. For every price component (open, high, low, close) the strategy calculates two moving averages: a simple MA and a linear weighted MA with the period defined by Period.
  3. The product of these two averages is divided by the instrument's price step and smoothed with the configured method (Ma Method) and length (Smooth Period).
  4. The synthetic high and low are forced to include the synthetic open/close so that candle bodies stay consistent with the MetaTrader implementation.
  5. The candle color is determined by comparing the synthetic open and close: open below close = bullish (color 2), open above close = bearish (color 0), otherwise neutral (color 1).

Trading rules

  1. The strategy keeps a rolling history of synthetic candle colors and inspects the bar defined by Signal Bar (default = previous finished bar).
  2. When the inspected synthetic candle turns bullish and the previous candle was not bullish:
    • Close any existing short position if Enable Sell Close is true.
    • Open a new long position if Enable Buy Open is true.
  3. When the inspected synthetic candle turns bearish and the previous candle was not bearish:
    • Close any existing long position if Enable Buy Close is true.
    • Open a new short position if Enable Sell Open is true.
  4. Market entries use the volume calculated from Money Management and Margin Mode. Negative values for Money Management are treated as a fixed lot size. For loss-based modes the algorithm approximates risk per trade using the configured stop-loss distance.
  5. StartProtection is activated to automatically place take-profit and stop-loss orders according to Take Profit and Stop Loss distances expressed in price steps.

Parameters

  • Money Management – fraction of account value used to derive order volume (negative values = fixed lot). Optimizable.
  • Margin Mode – interpretation of the money management parameter (equity based, balance based, loss based or fixed lot).
  • Stop Loss – stop-loss distance in price steps. Used both for protection orders and risk-based position sizing.
  • Take Profit – take-profit distance in price steps. Applied via StartProtection.
  • Open Long / Open Short – allow opening long/short positions on their respective signals.
  • Close Long / Close Short – allow closing long/short positions when the opposite signal appears.
  • Candle Type – timeframe of candles used for indicator calculation.
  • Center of Gravity Period – base period for the simple and linear weighted moving averages. Optimizable.
  • Smoothing Period – length of the smoothing moving average applied to the synthetic candles. Optimizable.
  • Smoothing Method – moving average type used in the smoothing stage (SMA, EMA, SMMA or LWMA).
  • Signal Bar – index of the synthetic candle used to generate signals (0 = current, 1 = previous, etc.).

Notes

  • The indicator calculation is implemented in C# to reproduce the original MetaTrader logic, avoiding manual buffers or historical collections.
  • The volume calculation uses StockSharp portfolio information and may differ slightly from MetaTrader results because of platform differences.
  • The strategy operates entirely on finished candles and never trades on partial bars.
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;
	}
}