Auf GitHub ansehen

Center-of-Gravity-Candle-Strategie

Diese Strategie repliziert den MetaTrader-Experten "Exp_CenterOfGravityCandle" unter Verwendung der StockSharp High-Level-API. Der Experte handelt synthetische Kerzen, die vom Center of Gravity Candle Indikator generiert werden. Jede synthetische Kerze wird durch Anwendung von John Ehlers' Center-of-Gravity-Berechnung auf die Eröffnungs-, Hoch-, Tief- und Schlusskurse und anschließende Glättung der Ergebnisse mit einem konfigurierbaren gleitenden Durchschnitt erstellt. Die Farbe der synthetischen Kerze (bullisch, bärisch oder neutral) ist das einzige Handelssignal.

Indikatorlogik

  1. Jede eingehende Marktkerze wird nach vollständigem Schließen verarbeitet.
  2. Für jede Preiskomponente (Eröffnung, Hoch, Tief, Schluss) berechnet die Strategie zwei gleitende Durchschnitte: einen einfachen MA und einen linear gewichteten MA mit dem durch Period definierten Zeitraum.
  3. Das Produkt dieser beiden Durchschnitte wird durch den Preisschritt des Instruments dividiert und mit der konfigurierten Methode (Ma Method) und Länge (Smooth Period) geglättet.
  4. Das synthetische Hoch und Tief werden so erzwungen, dass sie die synthetische Eröffnung/Schluss einschließen, damit die Kerzenkörper konsistent mit der MetaTrader-Implementierung bleiben.
  5. Die Kerzenfarbe wird durch Vergleich der synthetischen Eröffnung und des Schlusses bestimmt: Eröffnung unter Schluss = bullisch (Farbe 2), Eröffnung über Schluss = bärisch (Farbe 0), sonst neutral (Farbe 1).

Handelsregeln

  1. Die Strategie führt einen rollierenden Verlauf der synthetischen Kerzenfarben und untersucht die durch Signal Bar definierte Kerze (Standard = vorherige abgeschlossene Kerze).
  2. Wenn die untersuchte synthetische Kerze bullisch wird und die vorherige Kerze nicht bullisch war:
    • Eine bestehende Short-Position schließen, wenn Enable Sell Close true ist.
    • Eine neue Long-Position öffnen, wenn Enable Buy Open true ist.
  3. Wenn die untersuchte synthetische Kerze bärisch wird und die vorherige Kerze nicht bärisch war:
    • Eine bestehende Long-Position schließen, wenn Enable Buy Close true ist.
    • Eine neue Short-Position öffnen, wenn Enable Sell Open true ist.
  4. Markteinstiege verwenden das aus Money Management und Margin Mode berechnete Volumen. Negative Werte für Money Management werden als feste Losgröße behandelt. Bei verlustbasierten Modi approximiert der Algorithmus das Risiko pro Trade anhand der konfigurierten Stop-Loss-Distanz.
  5. StartProtection wird aktiviert, um automatisch Take-Profit- und Stop-Loss-Orders gemäß den Distanzen Take Profit und Stop Loss in Preisschritten zu platzieren.

Parameter

  • Money Management – Anteil des Kontowerts zur Ableitung des Ordervolumens (negative Werte = festes Los). Optimierbar.
  • Margin Mode – Interpretation des Money-Management-Parameters (eigenkapitalbasiert, saldobasiert, verlustbasiert oder festes Los).
  • Stop Loss – Stop-Loss-Distanz in Preisschritten. Wird sowohl für Schutzorders als auch für risikobasiertes Positionssizing verwendet.
  • Take Profit – Take-Profit-Distanz in Preisschritten. Angewendet über StartProtection.
  • Open Long / Open Short – Eröffnung von Long/Short-Positionen bei entsprechenden Signalen erlauben.
  • Close Long / Close Short – Schließen von Long/Short-Positionen beim gegenteiligen Signal erlauben.
  • Candle Type – Zeitrahmen der Kerzen für die Indikatorberechnung.
  • Center of Gravity Period – Basisperiode für den einfachen und linear gewichteten gleitenden Durchschnitt. Optimierbar.
  • Smoothing Period – Länge des auf die synthetischen Kerzen angewendeten Glättungs-MA. Optimierbar.
  • Smoothing Method – Gleitender-Durchschnitt-Typ in der Glättungsphase (SMA, EMA, SMMA oder LWMA).
  • Signal Bar – Index der synthetischen Kerze zur Signalgenerierung (0 = aktuell, 1 = vorherig usw.).

Hinweise

  • Die Indikatorberechnung ist in C# implementiert, um die originale MetaTrader-Logik zu reproduzieren, ohne manuelle Puffer oder historische Sammlungen.
  • Die Volumenberechnung verwendet StockSharp-Portfolioinformationen und kann aufgrund von Plattformunterschieden leicht von MetaTrader-Ergebnissen abweichen.
  • Die Strategie arbeitet vollständig auf abgeschlossenen Kerzen und handelt niemals auf Teilbalken.
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;
	}
}