Ver en GitHub

Estrategia Exp FisherCG Oscilador

Esta estrategia porta el asesor experto Exp_FisherCGOscillator de MetaTrader 5 a la API de alto nivel de StockSharp. Recrea el oscilador Fisher Center of Gravity y su línea de disparo, evalúa señales en una barra histórica configurable, y reproduce el flujo de stop/take original con órdenes de StockSharp y ayudantes de riesgo.

Cómo funciona

  1. Cadena de indicadores – cada vela terminada se pasa por el oscilador Fisher CG: los precios medianos alimentan un bucle de centro de gravedad, los valores se normalizan sobre las últimas Length barras, y una transformación de Fisher produce la línea del oscilador. La línea de disparo es simplemente el oscilador retrasado una barra.
  2. Extracción de señales – la estrategia inspecciona dos lecturas históricas definidas por SignalBar. Abre un largo cuando el valor más antiguo del oscilador (SignalBar + 1) está por encima de su disparo mientras el valor más nuevo (SignalBar) cruza de nuevo por encima del disparo, señalando un giro alcista. Los cortos reflejan esta lógica en el lado bajista.
  3. Manejo de salidas – las salidas largas ocurren tan pronto como el oscilador más antiguo cae por debajo de su disparo, mientras que las salidas cortas se activan cuando sube por encima del disparo, coincidiendo con los indicadores de cierre inmediato del EA. Las entradas opuestas cierran la posición activa antes de revertir.
  4. Procesamiento barra a barra – todo se ejecuta en velas completadas desde CandleType; no se generan operaciones intrabar, asegurando backtests deterministas y coincidiendo con la puerta de "nueva barra" del EA.

Gestión de riesgo y dimensionamiento de posición

  • Stops/objetivosStopLossPoints y TakeProfitPoints se expresan en pasos del instrumento y se traducen en distancias de precio absolutas a través de Security.PriceStep.
  • Control de volumenSizingMode = FixedVolume envía el FixedVolume constante. SizingMode = PortfolioShare convierte DepositShare del valor actual del portafolio en contratos usando el último cierre y VolumeStep.
  • Posición única – la estrategia siempre aplana antes de entrar en el lado opuesto, evitando posiciones hedgeadas simultáneas.

Parámetros

Parámetro Descripción
CandleType Marco temporal suscrito para velas y cálculos de indicadores.
Length Período del oscilador Fisher CG (también usado para la ventana de normalización).
SignalBar Número de velas cerradas hacia atrás usadas para leer señales; 1 coincide con el valor por defecto del EA.
AllowLongEntry / AllowShortEntry Alternar entradas largas/cortas.
AllowLongExit / AllowShortExit Alternar salidas automáticas para posiciones largas/cortas.
StopLossPoints / TakeProfitPoints Distancias de stop de protección y objetivo en pasos de precio. Establecer en 0 para deshabilitar.
FixedVolume Volumen usado en el modo de dimensionamiento fijo.
DepositShare Fracción del portafolio asignada por operación en el modo PortfolioShare.
SizingMode Elige entre volumen fijo y dimensionamiento basado en participación.

Notas de uso

  • Alinee CandleType y SignalBar con el marco temporal usado por el indicador original (H8 y desplazamiento de barra de 1 por defecto).
  • Permita un breve período de calentamiento para que el oscilador tenga suficiente historial para formarse; la estrategia ignora las operaciones hasta que el indicador esté completamente inicializado.
  • Los stops y objetivos operan en el cierre de la vela. Ajuste los valores de puntos para que coincidan con el tamaño del tick de su instrumento.
  • Cuando se selecciona el dimensionamiento PortfolioShare, asegúrese de que la valoración del portafolio esté disponible; de lo contrario, la estrategia vuelve al volumen fijo.

Diferencias vs EA original

  • Las órdenes se envían como órdenes de mercado sin el parámetro de deslizamiento Deviation_; StockSharp maneja la ejecución con su propia configuración de deslizamiento.
  • La gestión monetaria se simplifica a dos modos de dimensionamiento (FixedVolume y PortfolioShare). Las opciones de porcentaje de pérdida del EA se omiten intencionalmente.
  • Las marcas de tiempo de órdenes pendientes (UpSignalTime/DnSignalTime) no se usan. Las señales se ejecutan inmediatamente en la vela procesada.
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

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

using StockSharp.Algo.Candles;

/// <summary>
/// Fisher Center of Gravity oscillator crossover strategy.
/// </summary>
public class ExpFisherCgOscillatorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly List<decimal> _medianPrices = new();
	private readonly List<decimal> _cgValues = new();
	private readonly decimal[] _valueBuffer = new decimal[4];
	private int _valueCount;
	private decimal? _previousFisher;

	private readonly List<(decimal Main, decimal Trigger)> _oscillatorHistory = new();
	private decimal? _entryPrice;
	private int _length = 10;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public ExpFisherCgOscillatorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_medianPrices.Clear();
		_cgValues.Clear();
		Array.Clear(_valueBuffer);
		_valueCount = 0;
		_previousFisher = null;
		_oscillatorHistory.Clear();
		_entryPrice = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		OnReseted();

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Calculate Fisher CG oscillator inline
		var price = (candle.HighPrice + candle.LowPrice) / 2m;
		_medianPrices.Add(price);
		while (_medianPrices.Count > _length)
			_medianPrices.RemoveAt(0);

		if (_medianPrices.Count < _length)
			return;

		decimal num = 0m;
		decimal denom = 0m;
		var weight = 1;

		for (var index = _medianPrices.Count - 1; index >= 0; index--)
		{
			var median = _medianPrices[index];
			num += weight * median;
			denom += median;
			weight++;
		}

		decimal cg;
		if (denom != 0m)
			cg = -num / denom + (_length + 1m) / 2m;
		else
			cg = 0m;

		_cgValues.Add(cg);
		while (_cgValues.Count > _length)
			_cgValues.RemoveAt(0);

		var high = cg;
		var low = cg;
		for (var i = 0; i < _cgValues.Count; i++)
		{
			var v = _cgValues[i];
			if (v > high) high = v;
			if (v < low) low = v;
		}

		decimal normalized;
		if (high != low)
			normalized = (cg - low) / (high - low);
		else
			normalized = 0m;

		var limit = Math.Min(_valueCount, 3);
		for (var shift = limit; shift > 0; shift--)
			_valueBuffer[shift] = _valueBuffer[shift - 1];

		_valueBuffer[0] = normalized;
		if (_valueCount < 4)
			_valueCount++;

		if (_valueCount < 4)
			return;

		var value2 = (4m * _valueBuffer[0] + 3m * _valueBuffer[1] + 2m * _valueBuffer[2] + _valueBuffer[3]) / 10m;
		var x = 1.98m * (value2 - 0.5m);
		if (x > 0.999m)
			x = 0.999m;
		else if (x < -0.999m)
			x = -0.999m;

		var numerator = 1m + x;
		var denominator = 1m - x;
		if (denominator == 0m)
			denominator = 0.0000001m;

		var ratio = numerator / denominator;
		if (ratio <= 0m)
			ratio = 0.0000001m;

		var fisher = 0.5m * (decimal)Math.Log((double)ratio);
		var trigger = _previousFisher ?? fisher;
		_previousFisher = fisher;

		// Store history
		_oscillatorHistory.Add((fisher, trigger));
		while (_oscillatorHistory.Count > 10)
			_oscillatorHistory.RemoveAt(0);

		if (_oscillatorHistory.Count < 3)
			return;

		// Handle risk management
		HandleRiskManagement(candle.ClosePrice);

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var current = _oscillatorHistory[^1];
		var previous = _oscillatorHistory[^2];

		var previousAbove = previous.Main > previous.Trigger;
		var previousBelow = previous.Main < previous.Trigger;

		var buyOpen = previousAbove && current.Main <= current.Trigger;
		var sellOpen = previousBelow && current.Main >= current.Trigger;

		var buyClose = previousBelow;
		var sellClose = previousAbove;

		if (sellClose && Position < 0)
		{
			BuyMarket();
			_entryPrice = null;
		}

		if (buyClose && Position > 0)
		{
			SellMarket();
			_entryPrice = null;
		}

		if (buyOpen && Position <= 0)
		{
			if (Position < 0)
			{
				BuyMarket();
				_entryPrice = null;
				return;
			}

			BuyMarket();
			_entryPrice = candle.ClosePrice;
		}
		else if (sellOpen && Position >= 0)
		{
			if (Position > 0)
			{
				SellMarket();
				_entryPrice = null;
				return;
			}

			SellMarket();
			_entryPrice = candle.ClosePrice;
		}
	}

	private void HandleRiskManagement(decimal closePrice)
	{
		if (_entryPrice is null || Position == 0)
			return;

		var step = Security?.PriceStep ?? 1m;
		if (step <= 0m) step = 1m;

		var stopDistance = 1000 * step;
		var takeDistance = 2000 * step;

		if (Position > 0)
		{
			if (closePrice <= _entryPrice.Value - stopDistance)
			{
				SellMarket();
				_entryPrice = null;
				return;
			}
			if (closePrice >= _entryPrice.Value + takeDistance)
			{
				SellMarket();
				_entryPrice = null;
			}
		}
		else if (Position < 0)
		{
			if (closePrice >= _entryPrice.Value + stopDistance)
			{
				BuyMarket();
				_entryPrice = null;
				return;
			}
			if (closePrice <= _entryPrice.Value - takeDistance)
			{
				BuyMarket();
				_entryPrice = null;
			}
		}
	}
}