Открыть на GitHub

Стратегия Center Of Gravity Candle

Стратегия повторяет советник MetaTrader «Exp_CenterOfGravityCandle» с использованием высокоуровневого API StockSharp. Индикатор формирует синтетические свечи: к ценам Open/High/Low/Close применяется алгоритм центра тяжести Джона Элерса, далее результаты сглаживаются выбранной скользящей средней. Цвет синтетической свечи (бычий, медвежий или нейтральный) служит единственным торговым сигналом.

Логика индикатора

  1. Обработка выполняется только после полного закрытия биржевой свечи.
  2. Для каждого ценового параметра рассчитываются две скользящие средние с периодом Period: простая и линейно-взвешенная.
  3. Их произведение делится на минимальный шаг цены инструмента и дополнительно сглаживается методом Ma Method с длиной Smooth Period.
  4. Синтетические High/Low подстраиваются так, чтобы тело свечи всегда включало синтетические Open/Close — точное соответствие реализации в MetaTrader.
  5. Цвет свечи определяется сравнением синтетических Open и Close: Open < Close — бычья (цвет 2), Open > Close — медвежья (цвет 0), равны — нейтральная (цвет 1).

Правила торговли

  1. Ведётся история цветов синтетических свечей; анализируется свеча с индексом Signal Bar (по умолчанию предыдущая закрытая свеча).
  2. Если рассматриваемая свеча стала бычьей и предыдущая не была бычьей:
    • Закрыть короткую позицию, если Enable Sell Close = true.
    • Открыть длинную позицию, если Enable Buy Open = true.
  3. Если свеча стала медвежьей и предыдущая не была медвежьей:
    • Закрыть длинную позицию, если Enable Buy Close = true.
    • Открыть короткую позицию, если Enable Sell Open = true.
  4. Объём заявок вычисляется по параметрам Money Management и Margin Mode. Отрицательное значение Money Management трактуется как фиксированный лот. В режимах, основанных на допустимом убытке, учитывается расстояние стоп-лосса.
  5. StartProtection выставляет защитные заявки согласно значениям Stop Loss и Take Profit (в шагах цены).

Параметры

  • Money Management – доля капитала для расчёта объёма (отрицательное значение = фиксированный лот, поддерживает оптимизацию).
  • Margin Mode – способ интерпретации Money Management (по equity, по балансу, по риску убытка или фиксированный лот).
  • Stop Loss – расстояние защитного стопа в шагах цены, используется и для расчёта объёма.
  • Take Profit – расстояние тейк-профита в шагах цены, выставляется через StartProtection.
  • Open Long / Open Short – разрешение на открытие длинных/коротких позиций по сигналам.
  • Close Long / Close Short – разрешение на закрытие длинных/коротких позиций при противоположном сигнале.
  • Candle Type – таймфрейм свечей для расчёта индикатора.
  • Center of Gravity Period – базовый период для SMA и LWMA, допускает оптимизацию.
  • Smoothing Period – длина сглаживающей средней, допускает оптимизацию.
  • Smoothing Method – тип скользящей средней (SMA, EMA, SMMA, LWMA).
  • Signal Bar – индекс синтетической свечи, по которой формируется сигнал (0 = текущая, 1 = предыдущая и т.д.).

Особенности

  • Индикатор реализован на C#, что позволяет точно воспроизвести алгоритм MetaTrader без накопительных массивов.
  • Расчёт объёма использует данные портфеля StockSharp и может незначительно отличаться от значений MetaTrader из-за различий в платформах.
  • Стратегия работает только с полностью закрытыми свечами и не торгует внутри бара.
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;
	}
}