Ver en GitHub

Estrategia Alternante Cheduecoglioni

Descripción general

Esta estrategia es un port de StockSharp del asesor experto MQL5 "cheduecoglioni". Mantiene siempre al trader en el mercado alternando entre posiciones cortas y largas. Cada entrada está protegida con niveles fijos de take-profit y stop-loss definidos en pips y convertidos a desplazamientos de precio de acuerdo con la precisión del instrumento.

Reglas de trading

  • La estrategia escucha la serie de velas configurada (1 minuto por defecto) y solo reacciona una vez que una vela está completamente cerrada. Este evento reemplaza el bucle basado en ticks del asesor experto original.
  • Cuando no hay posición abierta y ninguna orden de mercado pendiente de ejecución, la estrategia envía una orden de mercado en la dirección almacenada en el estado _nextSide. La primera operación después del inicio es una venta, coincidiendo con la implementación MQL5.
  • Tan pronto como una posición se vuelve activa, el algoritmo espera a que se cierre ya sea por las órdenes de protección o intervención manual. Una vez que la posición vuelve a cero, la siguiente dirección se invierte, por lo que la siguiente operación será en la dirección opuesta.
  • Las distancias de stop-loss y take-profit son aplicadas automáticamente por StartProtection, asegurando que cada operación lleve las distancias de riesgo-recompensa configuradas.

Parámetros

  • Trade Volume – volumen usado para cada entrada de mercado. Esto refleja el input InpLots.
  • Take Profit (pips) – distancia en pips para la orden take-profit. La estrategia la convierte a distancia de precio absoluta usando el tamaño de pip detectado.
  • Stop Loss (pips) – distancia en pips para el stop loss de protección, convertida con la misma lógica de tamaño de pip.
  • Candle Type – marco temporal de las velas que impulsan el ciclo de decisión. Se puede suministrar cualquier DataType compatible.

Detalles de implementación

  • El tamaño de pip se deriva de Security.PriceStep. Para símbolos FX de 3 o 5 dígitos, el valor se multiplica por 10 para pasar del pip fraccionario al pip estándar, replicando el ajuste MQL.
  • Un indicador de espera previene órdenes de mercado duplicadas mientras una orden anterior está pendiente de ejecución. Si el broker rechaza la orden, OnOrderFailed limpia el indicador para que la siguiente vela pueda reintentar.
  • OnPositionChanged hace un seguimiento del lado de la posición activa y cambia _nextSide después de cada estado plano. Esto refleja la lógica MQL que abría el lado opuesto después de cada salida.
  • Las órdenes de protección son gestionadas por StartProtection con salidas de mercado, coincidiendo con la asignación inmediata de stop-loss y take-profit que el asesor experto realizaba al colocar la orden.

Notas

  • La versión Python no se ha creado intencionadamente aún.
  • La estrategia no modifica las pruebas unitarias.
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Alternates buy and sell market orders with fixed stop loss and take profit distances.
/// </summary>
public class CheduecoglioniAlternatingStrategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _pipSize;
	private Sides _nextSide;
	private Sides? _activeSide;

	/// <summary>
	/// Initializes a new instance of the <see cref="CheduecoglioniAlternatingStrategy"/> class.
	/// </summary>
	public CheduecoglioniAlternatingStrategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetDisplay("Trade Volume", "Volume per trade", "General")
			.SetGreaterThanZero();

		_takeProfitPips = Param(nameof(TakeProfitPips), 10m)
			.SetDisplay("Take Profit (pips)", "Distance to take profit", "Risk")
			.SetGreaterThanZero();

		_stopLossPips = Param(nameof(StopLossPips), 10m)
			.SetDisplay("Stop Loss (pips)", "Distance to stop loss", "Risk")
			.SetGreaterThanZero();

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Source candles for timing", "General");
	}

	/// <summary>
	/// Volume used for each market order.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Candle type that triggers trading decisions.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

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

		Volume = TradeVolume;
		_nextSide = Sides.Sell;
		_activeSide = null;
		_pipSize = 0m;
	}

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

		Volume = TradeVolume; // Align the base volume with the strategy parameter.

		var priceStep = Security?.PriceStep ?? 0m;
		if (priceStep <= 0m)
		{
			var decimals = Security?.Decimals ?? 4;
			priceStep = (decimal)Math.Pow(10, -decimals);
		}

		_pipSize = priceStep;

		var secDecimals = Security?.Decimals;
		if (secDecimals is int digits && (digits == 3 || digits == 5))
		{
			_pipSize *= 10m; // Convert from fractional pip to full pip for FX symbols.
		}

		if (_pipSize <= 0m)
		{
			_pipSize = 1m; // Fallback to a neutral value if the instrument metadata is missing.
		}

		StartProtection(
			takeProfit: new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute),
			stopLoss: new Unit(StopLossPips * _pipSize, UnitTypes.Absolute),
			useMarketOrders: true);

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

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

		// Strategy has no bound indicators, always allow trading.

		if (Position != 0)
			return; // Skip if a position exists.

		var volume = TradeVolume;
		if (volume <= 0m)
			return;

		if (_nextSide == Sides.Buy)
			BuyMarket();
		else
			SellMarket();

	}

	/// <inheritdoc />
	protected override void OnPositionReceived(Position position)
	{
		base.OnPositionReceived(position);

		if (Position > 0)
		{
			_activeSide = Sides.Buy;
			return;
		}

		if (Position < 0)
		{
			_activeSide = Sides.Sell;
			return;
		}

		if (_activeSide.HasValue)
		{
			_nextSide = _activeSide == Sides.Buy ? Sides.Sell : Sides.Buy; // Alternate direction after a flat position.
			_activeSide = null;
		}

	}

}