Auf GitHub ansehen

Stochastic CG Oscillator Strategy

This strategy ports the Exp_StochasticCGOscillator MetaTrader 5 expert advisor to StockSharp. The conversion keeps the original logic of the Stochastic Center of Gravity oscillator, rebuilds the trigger line smoothing, and executes trades using StockSharp's high-level strategy API.

How It Works

  1. Indicator pipeline – every finished candle from CandleType feeds the custom Stochastic CG oscillator. Median prices drive a center-of-gravity loop, values are normalised over the last Length bars, and a weighted rolling window produces the oscillator line. The trigger line is recreated through the same 0.96 * (previous + 0.02) smoothing that the EA applies.
  2. Signal sampling – the strategy inspects two historical readings separated by SignalBar. A buy is prepared when the older reading (shift SignalBar + 1) is above the trigger while the newer reading (shift SignalBar) crosses back below it. Shorts mirror the logic in the opposite direction.
  3. Position management – long positions are closed as soon as the older reading drops below the trigger, while short positions exit when the older reading climbs above it. When a fresh entry appears on the opposite side, the current position is flattened before the reversal order is sent.
  4. Risk handling – optional stop-loss and take-profit distances are expressed in instrument steps and evaluated on the closing price of each processed candle. They mirror the EA's protective inputs without relying on pending orders.
  5. Warm-up control – the strategy waits until the indicator is fully initialised (enough history for the CG loop and the four-value smoothing buffer) before emitting signals, guaranteeing deterministic backtests.

Risk Management & Position Sizing

  • Stops/targetsStopLossPoints and TakeProfitPoints translate into absolute distances using Security.PriceStep. A value of 0 disables the respective limit.
  • Single active position – the algorithm never keeps both long and short exposure at the same time. Opposite signals trigger an explicit close before entering the new direction.
  • Position sizingSizingMode = FixedVolume always trades FixedVolume. SizingMode = PortfolioShare converts DepositShare of the portfolio value into contracts using the latest close and Security.VolumeStep.

Parameters

Parameter Description
CandleType Timeframe subscribed for candles and indicator calculations.
Length Period of the Stochastic CG oscillator (affects CG and normalisation windows).
SignalBar Number of closed candles back used to evaluate signals (1 reproduces the EA default).
AllowLongEntry / AllowShortEntry Toggle long/short entries.
AllowLongExit / AllowShortExit Toggle automatic exits for long/short positions.
StopLossPoints / TakeProfitPoints Protective distances in price steps. Set to 0 to disable.
FixedVolume Order size used when sizing mode is fixed volume.
DepositShare Portfolio fraction used in share-based sizing.
SizingMode Chooses between fixed volume and share-based position sizing.

Usage Notes

  • Align CandleType with the timeframe used by the original indicator (H8 in the MQL version). Larger SignalBar values require a longer warm-up because the indicator history buffer must cover the shift.
  • Stops and targets act on candle closes; they are not intrabar orders. Adjust the point values to suit the instrument's tick size.
  • When PortfolioShare sizing is enabled, ensure that portfolio valuation is available; otherwise the strategy falls back to the fixed volume.
  • The indicator outputs values in the [-1, 1] range like the original implementation, allowing you to reuse familiar threshold-based filters if desired.

Differences vs Original EA

  • Market orders are sent immediately without the Deviation_ parameter; slippage handling is delegated to the StockSharp execution layer.
  • Money management is simplified to two modes (FixedVolume and PortfolioShare). The EA's additional margin-based sizing options are not reproduced.
  • Pending order timestamps (UpSignalTime / DnSignalTime) are unnecessary because StockSharp strategies work on completed candles and execute synchronously.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Stochastic CG Oscillator strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class StochasticCgOscillatorStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public StochasticCgOscillatorStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}