Ver en GitHub

Estrategia Nueva Aleatoria

Descripción general

La Estrategia Nueva Aleatoria emula el experto original de MetaTrader "New Random" ofreciendo tres modos distintos de selección de entrada. Abre solo una posición a la vez y espera hasta que la posición actual esté cerrada antes de generar la siguiente dirección de orden. Las entradas a mercado se activan en actualizaciones del mejor precio (datos Level 1) usando los mejores precios bid/ask como anclas de ejecución. La estrategia calcula automáticamente los offsets de stop-loss y take-profit en pips, adaptándose a cotizaciones forex de 3 y 5 dígitos de la misma manera que la versión MQL.

Modos de entrada

  1. Generador – la siguiente dirección es elegida por un generador pseudoaleatorio sembrado al inicio de la estrategia. Cada oportunidad es un lanzamiento de moneda independiente entre comprar y vender.
  2. Compra-Venta-Compra – las posiciones alternan estrictamente entre compra y venta. La primera orden es una compra, seguida de una venta, y así sucesivamente.
  3. Venta-Compra-Venta – las posiciones alternan estrictamente comenzando desde una venta, seguida de una compra, y repitiendo.

Parámetros

  • Random Mode (Mode) – selecciona uno de los tres mecanismos de entrada descritos anteriormente. Por defecto el generador aleatorio.
  • Minimal Lot Count (MinimalLotCount) – multiplica el volumen mínimo negociable del instrumento. Un valor de 1 significa que la estrategia opera exactamente Security.VolumeMin, mientras que valores más altos escalan el tamaño de la orden por múltiplos enteros.
  • Stop Loss (pips) (StopLossPips) – distancia en pips por debajo/encima del precio de llenado donde la estrategia saldrá de la posición. Establecer en 0 para deshabilitar el stop-loss.
  • Take Profit (pips) (TakeProfitPips) – distancia en pips donde la estrategia realizará ganancias. Establecer en 0 para deshabilitar el take-profit.

Lógica de trading

  1. Se suscribe a datos Level 1 para el instrumento configurado y almacena constantemente los últimos precios bid, ask y último trade.
  2. Cuando no hay posición abierta ni orden pendiente, la estrategia evalúa el modo seleccionado para determinar la siguiente dirección.
  3. Las órdenes se colocan a mercado usando la última instantánea de mejor bid/ask. Los objetivos de stop-loss y take-profit se calculan inmediatamente desde el precio de entrada usando los parámetros de distancia en pips.
  4. Solo puede existir una posición a la vez. Las entradas posteriores se suprimen hasta que la posición activa esté completamente cerrada.

Gestión de posición

  • Las posiciones largas salen anticipadamente cuando el precio actual cae al stop-loss o por debajo, o sube al take-profit o por encima.
  • Las posiciones cortas salen cuando el precio actual sube al stop-loss o por encima, o cae al take-profit o por debajo.
  • Las comparaciones de precio siempre usan la información Level 1 más fresca: el último precio de trade si está disponible, de lo contrario el mejor bid/ask para el lado respectivo.
  • Después de cerrar un trade, la estrategia reinicia el estado interno, alterna opcionalmente la siguiente dirección (para modos secuencia), y espera la próxima actualización de cotización antes de volver a entrar.

Notas

  • La estrategia nunca piramidaliza posiciones y mantiene el comportamiento determinista para los modos basados en secuencia.
  • El modo aleatorio se siembra con el conteo de ticks actual por lo que cada ejecución produce un flujo de órdenes único.
  • Todos los comentarios internos y logs están en inglés para alinearse con las pautas del repositorio.
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>
/// Randomized entry strategy that mimics the MetaTrader "New Random" expert.
/// </summary>
public class NewRandomStrategy : Strategy
{
	/// <summary>
	/// Available direction selection modes.
	/// </summary>
	public enum RandomModes
	{
		/// <summary>Use a pseudo random generator for every entry decision.</summary>
		Generator,
		/// <summary>Alternate buy-sell-buy.</summary>
		BuySellBuy,
		/// <summary>Alternate sell-buy-sell.</summary>
		SellBuySell
	}

	private readonly StrategyParam<RandomModes> _mode;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private Sides? _sequenceLastSide;
	private Sides? _positionSide;
	private decimal _entryPrice;
	private int _candleCount;

	/// <summary>Direction selection mode.</summary>
	public RandomModes Mode
	{
		get => _mode.Value;
		set => _mode.Value = value;
	}

	/// <summary>Stop loss in price steps.</summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>Take profit in price steps.</summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

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

	public NewRandomStrategy()
	{
		_mode = Param(nameof(Mode), RandomModes.Generator)
			.SetDisplay("Random Mode", "Direction selection mode", "General");

		_stopLossPoints = Param(nameof(StopLossPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pts)", "Stop loss in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pts)", "Take profit in price steps", "Risk");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_sequenceLastSide = null;
		_positionSide = null;
		_entryPrice = 0m;
		_candleCount = 0;
	}

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

		_sequenceLastSide = Mode switch
		{
			RandomModes.BuySellBuy => Sides.Sell,
			RandomModes.SellBuySell => Sides.Buy,
			_ => null
		};

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

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

		_candleCount++;
		if (_candleCount < 3)
			return;

		var step = Security?.PriceStep ?? 1m;
		var stopDistance = StopLossPoints * step;
		var takeDistance = TakeProfitPoints * step;
		var price = candle.ClosePrice;

		// Check SL/TP for current position
		if (Position != 0 && _entryPrice > 0)
		{
			var hit = false;

			if (_positionSide == Sides.Buy)
			{
				if (stopDistance > 0 && candle.LowPrice <= _entryPrice - stopDistance)
					hit = true;
				if (takeDistance > 0 && candle.HighPrice >= _entryPrice + takeDistance)
					hit = true;
			}
			else if (_positionSide == Sides.Sell)
			{
				if (stopDistance > 0 && candle.HighPrice >= _entryPrice + stopDistance)
					hit = true;
				if (takeDistance > 0 && candle.LowPrice <= _entryPrice - takeDistance)
					hit = true;
			}

			if (hit)
			{
				if (Position > 0)
					SellMarket();
				else if (Position < 0)
					BuyMarket();

				_positionSide = null;
				_entryPrice = 0m;
			}
		}

		// If flat, open new random position
		if (Position == 0 && _positionSide == null)
		{
			var side = DetermineNextSide();

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

			_positionSide = side;
			_entryPrice = price;

			_sequenceLastSide = side;
		}
	}

	private Sides DetermineNextSide()
	{
		// All modes use deterministic alternating logic
		return _sequenceLastSide == Sides.Buy ? Sides.Sell : Sides.Buy;
	}
}