Ver no GitHub

Estratégia de Vela de Centro de Gravidade

Esta estratégia replica o especialista MetaTrader "Exp_CenterOfGravityCandle" usando a API de alto nível do StockSharp. O especialista opera velas sintéticas geradas pelo indicador Center of Gravity Candle. Cada vela sintética é construída aplicando o cálculo do Centro de Gravidade de John Ehlers aos preços de abertura, máximo, mínimo e fechamento, e suavizando os resultados com uma média móvel configurável. A cor da vela sintética (altista, baixista ou neutra) é o único sinal de negociação.

Lógica do indicador

  1. Cada vela de mercado recebida é processada após estar completamente fechada.
  2. Para cada componente de preço (abertura, máximo, mínimo, fechamento) a estratégia calcula duas médias móveis: uma MA simples e uma MA ponderada linearmente com o período definido por Period.
  3. O produto dessas duas médias é dividido pelo passo de preço do instrumento e suavizado com o método configurado (Ma Method) e comprimento (Smooth Period).
  4. O máximo e mínimo sintéticos são forçados a incluir a abertura/fechamento sintéticos para que os corpos das velas permaneçam consistentes com a implementação MetaTrader.
  5. A cor da vela é determinada comparando a abertura e o fechamento sintéticos: abertura abaixo do fechamento = altista (cor 2), abertura acima do fechamento = baixista (cor 0), caso contrário neutro (cor 1).

Regras de negociação

  1. A estratégia mantém um histórico rotativo de cores de velas sintéticas e inspeciona a barra definida por Signal Bar (padrão = barra finalizada anterior).
  2. Quando a vela sintética inspecionada se torna altista e a vela anterior não era altista:
    • Fechar qualquer posição vendida existente se Enable Sell Close for true.
    • Abrir uma nova posição comprada se Enable Buy Open for true.
  3. Quando a vela sintética inspecionada se torna baixista e a vela anterior não era baixista:
    • Fechar qualquer posição comprada existente se Enable Buy Close for true.
    • Abrir uma nova posição vendida se Enable Sell Open for true.
  4. As entradas de mercado usam o volume calculado a partir de Money Management e Margin Mode. Valores negativos para Money Management são tratados como tamanho de lote fixo. Para modos baseados em perdas, o algoritmo aproxima o risco por trade usando a distância de stop-loss configurada.
  5. StartProtection é ativado para colocar automaticamente ordens de take-profit e stop-loss de acordo com as distâncias Take Profit e Stop Loss expressas em passos de preço.

Parâmetros

  • Money Management – fração do valor da conta usada para derivar o volume da ordem (valores negativos = lote fixo). Otimizável.
  • Margin Mode – interpretação do parâmetro de gestão de dinheiro (baseado em equity, baseado em saldo, baseado em perda ou lote fixo).
  • Stop Loss – distância do stop-loss em passos de preço. Usado tanto para ordens de proteção quanto para dimensionamento de posição baseado em risco.
  • Take Profit – distância do take-profit em passos de preço. Aplicado via StartProtection.
  • Open Long / Open Short – permitir abrir posições compradas/vendidas em seus respectivos sinais.
  • Close Long / Close Short – permitir fechar posições compradas/vendidas quando o sinal oposto aparece.
  • Candle Type – período das velas usadas para o cálculo do indicador.
  • Center of Gravity Period – período base para as médias móveis simples e ponderada linearmente. Otimizável.
  • Smoothing Period – comprimento da média móvel de suavização aplicada às velas sintéticas. Otimizável.
  • Smoothing Method – tipo de média móvel usado na etapa de suavização (SMA, EMA, SMMA ou LWMA).
  • Signal Bar – índice da vela sintética usada para gerar sinais (0 = atual, 1 = anterior, etc.).

Notas

  • O cálculo do indicador é implementado em C# para reproduzir a lógica original do MetaTrader, evitando buffers manuais ou coleções históricas.
  • O cálculo do volume usa informações do portfólio do StockSharp e pode diferir ligeiramente dos resultados do MetaTrader devido a diferenças de plataforma.
  • A estratégia opera inteiramente em velas terminadas e nunca negocia em barras parciais.
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;
	}
}