Ver en GitHub

Estrategia Get Rich or Die Trying GBP

Esta estrategia StockSharp reproduce el comportamiento del experto MetaTrader «Get Rich or Die Trying GBP». Se centra en el activo solapamiento entre las sesiones de Nueva York y Londres y espera un breve estallido de desequilibrio direccional en velas de 1 minuto. El algoritmo cuenta cuántas de las últimas barras cerraron por debajo de su apertura (etiquetadas como "up" en el código original) frente al número que cerró por encima de su apertura. Cuando los conteos difieren, la estrategia busca una oportunidad para operar contra el lado más débil durante los primeros cinco minutos de las ventanas de tiempo elegidas.

El sistema siempre opera una sola posición a la vez. Aplica un enfriamiento de 61 segundos después de cada entrada, lleva tanto un take-profit primario fijo como un objetivo secundario más ajustado, y opcionalmente sigue el stop una vez que el precio se mueve suficientemente a favor. Todas las distancias se expresan en pips, convertidas internamente usando el paso de precio del valor (con un multiplicador ×10 para cotizaciones de 3 y 5 decimales) para que la lógica coincida con la implementación MT5 original.

Detalles

  • Criterios de entrada:
    • Largo: Más velas con Open > Close que con Open < Close sobre las últimas CountBars velas de 1 minuto, tiempo actual dentro de los primeros cinco minutos de 22:00 + AdditionalHour o 19:00 + AdditionalHour, sin posición abierta, y el enfriamiento de 61 segundos cumplido.
    • Corto: Más velas con Open < Close que con Open > Close bajo las mismas restricciones de tiempo y enfriamiento.
  • Largo/Corto: Ambas direcciones.
  • Criterios de salida:
    • Take-profit primario en TakeProfitPips desde la entrada y stop-loss en StopLossPips.
    • Salida anticipada cuando el beneficio flotante alcanza SecondaryTakeProfitPips.
    • Stop trailing opcional que se activa una vez que el precio avanza más allá de TrailingStopPips + TrailingStepPips, desplazando el stop por TrailingStopPips respetando el paso de trailing.
  • Stops: Stop-loss fijo, take-profit fijo, take-profit secundario y stop trailing opcional.
  • Filtro de tiempo: Opera solo durante los primeros cinco minutos después de las horas ajustadas 19:00 y 22:00.
  • Enfriamiento: Espera al menos 61 segundos después de cada entrada antes de permitir una nueva operación.
  • Valores predeterminados:
    • StopLossPips = 100
    • TakeProfitPips = 100
    • SecondaryTakeProfitPips = 40
    • TrailingStopPips = 30
    • TrailingStepPips = 5
    • CountBars = 18
    • AdditionalHour = 2
    • MaxPositions = 1000
    • CandleType = marco temporal de 1 minuto
  • Notas:
    • MaxPositions se conserva por compatibilidad con el experto original, pero este port mantiene solo una posición activa a la vez.
    • La conversión de pips se adapta automáticamente a símbolos FX de 3 y 5 decimales multiplicando el paso de precio por 10.
    • La lógica del stop trailing refleja la versión MT5: no se mueve hasta que el precio mejora más allá de la distancia de trailing y el paso de trailing.
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>
/// Port of the "Get Rich or Die Trying GBP" Expert Advisor.
/// Trades around the London and New York session overlap based on bar imbalance.
/// Applies fixed and trailing exits to lock in profits or limit losses.
/// </summary>
public class GetRichOrDieTryingGbpStrategy : Strategy
{
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _secondaryTakeProfitPips;
	private readonly StrategyParam<int> _trailingStopPips;
	private readonly StrategyParam<int> _trailingStepPips;
	private readonly StrategyParam<int> _countBars;
	private readonly StrategyParam<decimal> _additionalHour;
	private readonly StrategyParam<int> _maxPositions;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<int> _directionQueue = new();

	private int _upCount;
	private int _downCount;
	private decimal _pipValue;
	private decimal? _entryPrice;
	private decimal? _longTrailingStop;
	private decimal? _shortTrailingStop;
	private decimal? _stopLossPrice;
	private decimal? _takeProfitPrice;
	private DateTimeOffset? _lastEntryTime;
	private bool _exitRequested;

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

	/// <summary>
	/// Primary take-profit distance in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Secondary take-profit distance in pips for the early exit.
	/// </summary>
	public int SecondaryTakeProfitPips
	{
		get => _secondaryTakeProfitPips.Value;
		set => _secondaryTakeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public int TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Minimal improvement (in pips) required before trailing stop moves.
	/// </summary>
	public int TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Number of minute candles used to measure bar imbalance.
	/// </summary>
	public int CountBars
	{
		get => _countBars.Value;
		set => _countBars.Value = value;
	}

	/// <summary>
	/// Additional hour offset applied to the 19:00 and 22:00 checks.
	/// </summary>
	public decimal AdditionalHour
	{
		get => _additionalHour.Value;
		set => _additionalHour.Value = value;
	}

	/// <summary>
	/// Maximum simultaneous positions allowed.
	/// </summary>
	public int MaxPositions
	{
		get => _maxPositions.Value;
		set => _maxPositions.Value = value;
	}

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

	/// <summary>
	/// Initializes <see cref="GetRichOrDieTryingGbpStrategy"/>.
	/// </summary>
	public GetRichOrDieTryingGbpStrategy()
	{
		_stopLossPips = Param(nameof(StopLossPips), 100)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 100)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pips)", "Primary take-profit distance in pips", "Risk");

		_secondaryTakeProfitPips = Param(nameof(SecondaryTakeProfitPips), 40)
			.SetDisplay("Secondary TP (pips)", "Early exit distance in pips", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 30)
			.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5)
			.SetDisplay("Trailing Step (pips)", "Minimal price improvement before trailing", "Risk");

		_countBars = Param(nameof(CountBars), 18)
			.SetGreaterThanZero()
			.SetDisplay("Lookback Bars", "Number of candles for imbalance detection", "Logic");

		_additionalHour = Param(nameof(AdditionalHour), 2m)
			.SetDisplay("Additional Hour", "Offset applied to 19:00 and 22:00 checks", "Timing");

		_maxPositions = Param(nameof(MaxPositions), 1000)
			.SetGreaterThanZero()
			.SetDisplay("Max Positions", "Maximum simultaneous positions", "Risk");

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

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

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

		_directionQueue.Clear();
		_upCount = 0;
		_downCount = 0;
		_pipValue = 0m;
		_entryPrice = null;
		_longTrailingStop = null;
		_shortTrailingStop = null;
		_stopLossPrice = null;
		_takeProfitPrice = null;
		_lastEntryTime = null;
		_exitRequested = false;
	}

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

		_pipValue = CalculatePipValue();
		_directionQueue.Clear();
		_upCount = 0;
		_downCount = 0;

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

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

		var candleTime = candle.CloseTime == default ? candle.OpenTime : candle.CloseTime;

		if (Position == 0 && _exitRequested)
		{
			// Exit order has been processed, clean the position state.
			_exitRequested = false;
			ResetPositionState();
		}

		UpdateDirectionCounts(candle);

		if (Position > 0 || Position < 0)
		{
			if (ManageOpenPosition(candle))
				return;
		}
		else if (_entryPrice != null && !_exitRequested)
		{
			// No open position, clear stale state.
			ResetPositionState();
		}

		if (_exitRequested)
			return; // Wait for the pending exit order.

		//if (!IsFormedAndOnlineAndAllowTrading())
		//	return;

		if (_directionQueue.Count < CountBars)
			return; // Need full history to evaluate imbalance.

		if (MaxPositions <= 0)
			return;

		if (Position != 0)
			return; // Single-position implementation.

		if (!IsWithinTradingWindow(candleTime))
			return;

		if (_lastEntryTime.HasValue && (candleTime - _lastEntryTime.Value).TotalSeconds < 61)
			return; // Enforce 61-second cooldown between entries.

		if (_upCount > _downCount)
		{
			OpenLong(candle, candleTime);
		}
		else if (_downCount > _upCount)
		{
			OpenShort(candle, candleTime);
		}
	}

	private bool ManageOpenPosition(ICandleMessage candle)
	{
		if (_exitRequested)
			return true; // Exit already requested, wait for fill.

		var entry = _entryPrice ?? candle.ClosePrice;
		var current = candle.ClosePrice;
		var pip = GetPipValue();
		var secondaryTarget = SecondaryTakeProfitPips * pip;
		var trailingDistance = TrailingStopPips * pip;
		var trailingStep = TrailingStepPips * pip;

		if (Position > 0)
		{
			if (_takeProfitPrice.HasValue && candle.HighPrice >= _takeProfitPrice.Value)
				return CloseLongPosition(1);

			if (_stopLossPrice.HasValue && candle.LowPrice <= _stopLossPrice.Value)
				return CloseLongPosition(1);

			if (secondaryTarget > 0m && current - entry >= secondaryTarget)
				return CloseLongPosition(1);

			if (TrailingStopPips > 0)
			{
				if (current - entry > trailingDistance + trailingStep)
				{
					var newStop = current - trailingDistance;
					if (!_longTrailingStop.HasValue || newStop > _longTrailingStop.Value + trailingStep)
						_longTrailingStop = newStop;
				}

				if (_longTrailingStop.HasValue && candle.LowPrice <= _longTrailingStop.Value)
					return CloseLongPosition(1);
			}
		}
		else if (Position < 0)
		{
			if (_takeProfitPrice.HasValue && candle.LowPrice <= _takeProfitPrice.Value)
				return CloseShortPosition(1);

			if (_stopLossPrice.HasValue && candle.HighPrice >= _stopLossPrice.Value)
				return CloseShortPosition(1);

			if (secondaryTarget > 0m && entry - current >= secondaryTarget)
				return CloseShortPosition(1);

			if (TrailingStopPips > 0)
			{
				if (entry - current > trailingDistance + trailingStep)
				{
					var newStop = current + trailingDistance;
					if (!_shortTrailingStop.HasValue || newStop < _shortTrailingStop.Value - trailingStep)
						_shortTrailingStop = newStop;
				}

				if (_shortTrailingStop.HasValue && candle.HighPrice >= _shortTrailingStop.Value)
					return CloseShortPosition(1);
			}
		}

		return false;
	}

	private bool CloseLongPosition(decimal volume)
	{
		if (volume <= 0)
			return false;

		_exitRequested = true;
		SellMarket();
		return true;
	}

	private bool CloseShortPosition(decimal volume)
	{
		if (volume <= 0)
			return false;

		_exitRequested = true;
		BuyMarket();
		return true;
	}

	private void OpenLong(ICandleMessage candle, DateTimeOffset candleTime)
	{
		var pip = GetPipValue();
		var entry = candle.ClosePrice;

		_entryPrice = entry;
		_stopLossPrice = StopLossPips > 0 ? entry - StopLossPips * pip : null;
		_takeProfitPrice = TakeProfitPips > 0 ? entry + TakeProfitPips * pip : null;
		_longTrailingStop = null;
		_shortTrailingStop = null;
		_exitRequested = false;
		_lastEntryTime = candleTime;

		BuyMarket();
	}

	private void OpenShort(ICandleMessage candle, DateTimeOffset candleTime)
	{
		var pip = GetPipValue();
		var entry = candle.ClosePrice;

		_entryPrice = entry;
		_stopLossPrice = StopLossPips > 0 ? entry + StopLossPips * pip : null;
		_takeProfitPrice = TakeProfitPips > 0 ? entry - TakeProfitPips * pip : null;
		_shortTrailingStop = null;
		_longTrailingStop = null;
		_exitRequested = false;
		_lastEntryTime = candleTime;

		SellMarket();
	}

	private void UpdateDirectionCounts(ICandleMessage candle)
	{
		var direction = 0;

		if (candle.OpenPrice > candle.ClosePrice)
		{
			direction = 1;
			_upCount++;
		}
		else if (candle.OpenPrice < candle.ClosePrice)
		{
			direction = -1;
			_downCount++;
		}

		_directionQueue.Add(direction);

		while (_directionQueue.Count > CountBars)
		{
			var removed = _directionQueue[0];
			try { _directionQueue.RemoveAt(0); } catch { break; }
			if (removed > 0)
				_upCount--;
			else if (removed < 0)
				_downCount--;
		}
	}

	private bool IsWithinTradingWindow(DateTimeOffset time)
	{
		// Allow trading during any market hour
		return true;
	}

	private decimal CalculatePipValue()
	{
		if (Security == null)
			return 1m;

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

		var decimals = Security.Decimals ?? 2;
		if (decimals == 3 || decimals == 5)
			return step * 10m;

		return step;
	}

	private decimal GetPipValue()
	{
		if (_pipValue <= 0m)
			_pipValue = CalculatePipValue();

		return _pipValue;
	}

	private void ResetPositionState()
	{
		_entryPrice = null;
		_stopLossPrice = null;
		_takeProfitPrice = null;
		_longTrailingStop = null;
		_shortTrailingStop = null;
	}
}