Ver en GitHub

Estrategia de Stochastic CG Oscillator

Esta estrategia porta el asesor experto MetaTrader 5 Exp_StochasticCGOscillator a StockSharp. La conversión mantiene la lógica original del oscilador Stochastic Center of Gravity, reconstruye el suavizado de la línea de disparo y ejecuta operaciones usando la API de estrategia de alto nivel de StockSharp.

Cómo funciona

  1. Pipeline de indicadores – cada vela finalizada de CandleType alimenta el oscilador Stochastic CG personalizado. Los precios medios impulsan un bucle center-of-gravity, los valores se normalizan sobre las últimas Length barras, y una ventana deslizante ponderada produce la línea del oscilador. La línea de disparo se recrea mediante el mismo suavizado 0.96 * (previous + 0.02) que aplica el EA.
  2. Muestreo de señal – la estrategia inspecciona dos lecturas históricas separadas por SignalBar. Se prepara una compra cuando la lectura más antigua (desplazamiento SignalBar + 1) está por encima del trigger mientras la más reciente (desplazamiento SignalBar) cruza por debajo. Los cortos reflejan la lógica en dirección opuesta.
  3. Gestión de posición – las posiciones largas se cierran en cuanto la lectura más antigua cae por debajo del trigger, mientras que las posiciones cortas salen cuando la lectura más antigua sube por encima de él. Cuando aparece una nueva entrada en el lado opuesto, la posición actual se aplana antes de enviar la orden de reversión.
  4. Manejo de riesgo – las distancias opcionales de stop-loss y take-profit se expresan en pasos del instrumento y se evalúan sobre el precio de cierre de cada vela procesada. Reflejan los inputs protectores del EA sin depender de órdenes pendientes.
  5. Control de calentamiento – la estrategia espera hasta que el indicador esté completamente inicializado (suficiente historial para el bucle CG y el buffer de suavizado de cuatro valores) antes de emitir señales, garantizando backtests deterministas.

Gestión de riesgo y dimensionamiento de posición

  • Stops/objetivosStopLossPoints y TakeProfitPoints se traducen en distancias absolutas usando Security.PriceStep. Un valor de 0 deshabilita el límite respectivo.
  • Posición activa única – el algoritmo nunca mantiene exposición larga y corta al mismo tiempo. Las señales opuestas activan un cierre explícito antes de entrar en la nueva dirección.
  • Dimensionamiento de posiciónSizingMode = FixedVolume siempre opera con FixedVolume. SizingMode = PortfolioShare convierte DepositShare del valor del portafolio en contratos usando el último cierre y Security.VolumeStep.

Parámetros

Parámetro Descripción
CandleType Marco temporal suscrito para velas y cálculos de indicadores.
Length Período del oscilador Stochastic CG (afecta las ventanas CG y de normalización).
SignalBar Número de velas cerradas atrás usadas para evaluar señales (1 reproduce el valor predeterminado del EA).
AllowLongEntry / AllowShortEntry Activa/desactiva entradas largas/cortas.
AllowLongExit / AllowShortExit Activa/desactiva salidas automáticas para posiciones largas/cortas.
StopLossPoints / TakeProfitPoints Distancias protectoras en pasos de precio. Establezca en 0 para deshabilitar.
FixedVolume Tamaño de orden cuando el modo de dimensionamiento es volumen fijo.
DepositShare Fracción del portafolio usada en el dimensionamiento basado en participación.
SizingMode Elige entre volumen fijo y dimensionamiento de posición basado en participación.

Notas de uso

  • Alinee CandleType con el marco temporal usado por el indicador original (H8 en la versión MQL). Valores de SignalBar más grandes requieren un calentamiento más largo porque el buffer de historial del indicador debe cubrir el desplazamiento.
  • Los stops y objetivos actúan sobre los cierres de velas; no son órdenes intrabarra. Ajuste los valores de puntos para adaptarse al tamaño del tick del instrumento.
  • Cuando el dimensionamiento PortfolioShare está habilitado, asegúrese de que la valoración del portafolio esté disponible; de lo contrario la estrategia recurre al volumen fijo.
  • El indicador produce valores en el rango [-1, 1] como la implementación original, permitiendo reutilizar filtros familiares basados en umbrales si se desea.

Diferencias con el EA original

  • Las órdenes de mercado se envían inmediatamente sin el parámetro Deviation_; el manejo del deslizamiento se delega a la capa de ejecución de StockSharp.
  • La gestión de dinero se simplifica a dos modos (FixedVolume y PortfolioShare). Las opciones de dimensionamiento adicionales basadas en margen del EA no se reproducen.
  • Las marcas de tiempo de órdenes pendientes (UpSignalTime / DnSignalTime) son innecesarias porque las estrategias de StockSharp trabajan en velas completadas y se ejecutan sincrónicamente.
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;
	}
}