Ver en GitHub

Estrategia Master MM Droid

Descripción general

La estrategia Master MM Droid es un port multi-módulo del asesor experto original de MetaTrader 5. La implementación de StockSharp conserva las ideas centrales del robot heredado mientras usa la API de alto nivel para suscripciones a velas, vinculación de indicadores y gestión de órdenes. Cuatro bloques de gestión monetaria independientes pueden activarse o desactivarse, permitiendo a la estrategia mezclar entradas de momentum con órdenes de ruptura programadas y operaciones de gap semanales.

Módulos

  1. Bloque RSI
    • Usa un Índice de Fuerza Relativa de 14 períodos en el tipo de vela configurado.
    • Entra largo cuando el RSI cruza hacia arriba desde el umbral de sobreventa y corto cuando cruza hacia abajo desde el nivel de sobrecompra.
    • Permite piramidación con un número configurable de entradas adicionales separadas por un paso de precio fijo.
    • Aplica un stop inicial fijo basado en distancia en puntos y activa un stop móvil una vez que la posición está abierta.
  2. Bloque de ruptura de caja
    • Reconstruye cajas de ruptura tres veces al día (horas con desplazamiento de sesión 6, 12 y 20 por defecto).
    • Coloca órdenes de stop agrupadas por encima del máximo de sesión y por debajo del mínimo con un buffer configurable.
    • Cancela las órdenes pendientes y posiciones en los reinicios de sesión (horas 0, 10 y 16), imitando el comportamiento del experto original.
  3. Bloque de ruptura semanal
    • Rastrea la acción del precio del lunes y almacena el máximo/mínimo acumulado de la primera parte de la sesión.
    • Coloca órdenes de stop simétricas dentro de una ventana de activación limitada (StartHourWeeklySetupEndHour) para que la semana comience con una ruptura OCO.
    • Fuerza un estado plano los viernes por la noche para evitar exposición de fin de semana.
  4. Bloque de gap
    • Compara la nueva apertura diaria con el máximo/mínimo del día anterior (usando el calendario con desplazamiento).
    • Compra fuertes aperturas de gap bajista y vende fuertes aperturas de gap alcista.
    • Establece un stop protector a una distancia configurable y entrega la gestión adicional al motor de trailing.

Parámetros

Nombre Descripción
CandleType Marco temporal usado para los cálculos de indicadores y las verificaciones de ventanas de tiempo.
TimeShiftHours Desplazamiento de sesión aplicado a las marcas de tiempo de las velas para que el horario coincida con el EA original.
StartHour Hora de inicio base del lunes para el módulo semanal (antes de aplicar el desplazamiento).
EnableRsiModule, EnableBoxModule, EnableWeeklyModule, EnableGapModule Interruptores para los cuatro bloques independientes.
RsiPeriod, RsiLowerLevel, RsiUpperLevel Cálculo RSI y niveles de disparo.
RsiMaxEntries, RsiPyramidPoints Controles de piramidación para el bloque RSI.
RsiStopLossPoints, RsiTrailingPoints Tamaños de stop inicial y stop móvil (en puntos) para operaciones dirigidas por RSI.
BoxEntryPoints, BoxTrailingPoints Buffer de ruptura y distancia de trailing para las órdenes de caja.
WeeklyEntryPoints, WeeklySetupEndHour, WeeklyTrailingPoints Configuración de ruptura semanal.
GapStopLossPoints, GapTrailingPoints Stop protector y distancia de trailing del módulo de gap.

Todos los parámetros basados en puntos se multiplican por el TickSize del instrumento para obtener compensaciones de precio, de modo que la estrategia se adapta a diferentes símbolos.

Lógica de trading

  • Vinculación de indicadores: Un único indicador RSI está vinculado a la suscripción de velas. Cada vela terminada dispara ProcessCandle, que envía los valores a los cuatro manejadores de módulo.
  • Seguimiento del estado diario: La estrategia agrega apertura/máximo/mínimo para cada día con desplazamiento para soportar la lógica de gap y mantener una referencia histórica para el módulo semanal.
  • Colocación de órdenes: Las órdenes se envían a través de BuyMarket, SellMarket, BuyStop, SellStop de acuerdo con las mejores prácticas de la API de alto nivel. Los módulos programados siempre cancelan las órdenes activas antes de rearmarse para evitar duplicados.
  • Gestión de trailing: Una vez que una posición está activa, _activeTrailingPoints almacena la distancia específica del módulo. El método UpdateTrailing mueve las órdenes de stop solo en la dirección favorable.

Gestión del riesgo

  • Solo las órdenes de mercado creadas por los módulos RSI y gap están protegidas por un stop inmediato calculado en puntos.
  • Los módulos de ruptura dependen del motor de trailing después de la activación; pueden combinarse con protección de portafolio externo si es necesario.
  • Llamar a ClosePosition() es la forma canónica de aplanar, preservando la compatibilidad con las herramientas de riesgo de StockSharp.

Notas de uso

  • La estrategia opera en un único instrumento y usa el valor global Volume para el dimensionamiento. Ajuste la protección de portafolio por separado si necesita límites de riesgo por posición.
  • Los tiempos de sesión se evalúan después de aplicar TimeShiftHours. Por ejemplo, con el valor predeterminado 2, el reinicio de caja a la hora 0 corresponde a las 02:00 hora del servidor.
  • Dado que las estrategias de StockSharp gestionan posiciones netas, las cestas largas/cortas simultáneas (posibles en cuentas de MetaTrader con cobertura) se consolidan. Esta es la principal diferencia de comportamiento con respecto al EA original y debe considerarse durante la validación.

Registro y monitoreo

  • Cada módulo restablece sus flags internos una vez que la posición vuelve a cero, ayudando a los operadores a diagnosticar qué bloque produjo una operación.
  • Agregue gráficos opcionales o registro a través de las instalaciones de StockSharp si se requieren análisis detallados.
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 Master MM Droid strategy with modular money management blocks.
/// Uses RSI crossover signals with pyramiding, daily gap detection, and
/// box/weekly breakout modules - all implemented via candle-based checks.
/// </summary>
public class MasterMmDroidStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiLowerLevel;
	private readonly StrategyParam<decimal> _rsiUpperLevel;
	private readonly StrategyParam<int> _rsiMaxEntries;
	private readonly StrategyParam<decimal> _rsiPyramidSteps;
	private readonly StrategyParam<decimal> _stopLossSteps;
	private readonly StrategyParam<decimal> _trailingSteps;
	private readonly StrategyParam<int> _boxLookback;
	private readonly StrategyParam<decimal> _boxEntrySteps;

	private RelativeStrengthIndex _rsi = null!;

	private decimal _previousRsi;
	private bool _hasPreviousRsi;
	private decimal? _lastEntryPrice;
	private int _entryCount;

	private decimal? _activeStopPrice;
	private decimal _bestPrice;

	private decimal _boxHigh;
	private decimal _boxLow;
	private int _boxBarsCount;

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

	/// <summary>
	/// RSI period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// RSI oversold level.
	/// </summary>
	public decimal RsiLowerLevel
	{
		get => _rsiLowerLevel.Value;
		set => _rsiLowerLevel.Value = value;
	}

	/// <summary>
	/// RSI overbought level.
	/// </summary>
	public decimal RsiUpperLevel
	{
		get => _rsiUpperLevel.Value;
		set => _rsiUpperLevel.Value = value;
	}

	/// <summary>
	/// Maximum pyramiding entries.
	/// </summary>
	public int RsiMaxEntries
	{
		get => _rsiMaxEntries.Value;
		set => _rsiMaxEntries.Value = value;
	}

	/// <summary>
	/// Price steps between pyramid entries.
	/// </summary>
	public decimal RsiPyramidSteps
	{
		get => _rsiPyramidSteps.Value;
		set => _rsiPyramidSteps.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public decimal StopLossSteps
	{
		get => _stopLossSteps.Value;
		set => _stopLossSteps.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in price steps.
	/// </summary>
	public decimal TrailingSteps
	{
		get => _trailingSteps.Value;
		set => _trailingSteps.Value = value;
	}

	/// <summary>
	/// Number of candles for box high/low calculation.
	/// </summary>
	public int BoxLookback
	{
		get => _boxLookback.Value;
		set => _boxLookback.Value = value;
	}

	/// <summary>
	/// Breakout distance above/below the box in price steps.
	/// </summary>
	public decimal BoxEntrySteps
	{
		get => _boxEntrySteps.Value;
		set => _boxEntrySteps.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="MasterMmDroidStrategy"/>.
	/// </summary>
	public MasterMmDroidStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe", "General");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI calculation period", "RSI")
			.SetOptimize(7, 21, 7);

		_rsiLowerLevel = Param(nameof(RsiLowerLevel), 25m)
			.SetDisplay("RSI Oversold", "RSI oversold threshold", "RSI");

		_rsiUpperLevel = Param(nameof(RsiUpperLevel), 75m)
			.SetDisplay("RSI Overbought", "RSI overbought threshold", "RSI");

		_rsiMaxEntries = Param(nameof(RsiMaxEntries), 2)
			.SetGreaterThanZero()
			.SetDisplay("Max Entries", "Maximum pyramiding steps", "RSI");

		_rsiPyramidSteps = Param(nameof(RsiPyramidSteps), 250m)
			.SetGreaterThanZero()
			.SetDisplay("Pyramid Steps", "Price steps between entries", "RSI");

		_stopLossSteps = Param(nameof(StopLossSteps), 500m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss Steps", "Stop-loss distance in price steps", "Risk");

		_trailingSteps = Param(nameof(TrailingSteps), 700m)
			.SetGreaterThanZero()
			.SetDisplay("Trailing Steps", "Trailing distance in price steps", "Risk");

		_boxLookback = Param(nameof(BoxLookback), 16)
			.SetGreaterThanZero()
			.SetDisplay("Box Lookback", "Candles for box high/low", "Box");

		_boxEntrySteps = Param(nameof(BoxEntrySteps), 180m)
			.SetGreaterThanZero()
			.SetDisplay("Box Entry Steps", "Breakout distance in price steps", "Box");
	}

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

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

		_previousRsi = 0m;
		_hasPreviousRsi = false;
		_lastEntryPrice = null;
		_entryCount = 0;
		_activeStopPrice = null;
		_bestPrice = 0m;
		_boxHigh = 0m;
		_boxLow = decimal.MaxValue;
		_boxBarsCount = 0;
	}

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

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

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

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

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

		var step = Security?.PriceStep ?? 1m;
		var enteredThisCandle = false;

		// Update box tracking
		UpdateBox(candle);

		// Manage trailing stop
		ManageTrailing(candle, step);

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_previousRsi = rsiValue;
			_hasPreviousRsi = true;
			return;
		}

		// Check box breakout entries
		if (Position == 0 && _boxBarsCount >= BoxLookback)
		{
			var boxOffset = BoxEntrySteps * step;
			if (candle.ClosePrice > _boxHigh + boxOffset)
			{
				BuyMarket(Volume);
				_lastEntryPrice = candle.ClosePrice;
				_entryCount = 1;
				_activeStopPrice = candle.ClosePrice - StopLossSteps * step;
				_bestPrice = candle.ClosePrice;
				enteredThisCandle = true;
			}
			else if (candle.ClosePrice < _boxLow - boxOffset)
			{
				SellMarket(Volume);
				_lastEntryPrice = candle.ClosePrice;
				_entryCount = 1;
				_activeStopPrice = candle.ClosePrice + StopLossSteps * step;
				_bestPrice = candle.ClosePrice;
				enteredThisCandle = true;
			}
		}

		// RSI crossover signals
		if (!enteredThisCandle && _hasPreviousRsi && _rsi.IsFormed)
		{
			var rsiCrossUp = _previousRsi <= RsiLowerLevel && rsiValue > RsiLowerLevel;
			var rsiCrossDown = _previousRsi >= RsiUpperLevel && rsiValue < RsiUpperLevel;

			if (rsiCrossUp && Position <= 0)
			{
				var vol = Volume + (Position < 0 ? Math.Abs(Position) : 0);
				BuyMarket(vol);
				_lastEntryPrice = candle.ClosePrice;
				_entryCount = 1;
				_activeStopPrice = candle.ClosePrice - StopLossSteps * step;
				_bestPrice = candle.ClosePrice;
			}
			else if (rsiCrossDown && Position >= 0)
			{
				var vol = Volume + (Position > 0 ? Position : 0);
				SellMarket(vol);
				_lastEntryPrice = candle.ClosePrice;
				_entryCount = 1;
				_activeStopPrice = candle.ClosePrice + StopLossSteps * step;
				_bestPrice = candle.ClosePrice;
			}

			// Pyramiding
			var pyramidDist = RsiPyramidSteps * step;
			if (Position > 0 && _entryCount < RsiMaxEntries && _lastEntryPrice.HasValue)
			{
				if (candle.ClosePrice >= _lastEntryPrice.Value + pyramidDist)
				{
					BuyMarket(Volume);
					_lastEntryPrice = candle.ClosePrice;
					_entryCount++;
				}
			}
			else if (Position < 0 && _entryCount < RsiMaxEntries && _lastEntryPrice.HasValue)
			{
				if (candle.ClosePrice <= _lastEntryPrice.Value - pyramidDist)
				{
					SellMarket(Volume);
					_lastEntryPrice = candle.ClosePrice;
					_entryCount++;
				}
			}
		}

		_previousRsi = rsiValue;
		_hasPreviousRsi = true;
	}

	private void UpdateBox(ICandleMessage candle)
	{
		_boxBarsCount++;
		if (_boxBarsCount <= BoxLookback)
		{
			_boxHigh = Math.Max(_boxHigh, candle.HighPrice);
			_boxLow = Math.Min(_boxLow, candle.LowPrice);
		}
		else
		{
			// Shift the window - approximate by using recent candle
			_boxHigh = Math.Max(_boxHigh, candle.HighPrice);
			_boxLow = Math.Min(_boxLow, candle.LowPrice);
		}
	}

	private void ManageTrailing(ICandleMessage candle, decimal step)
	{
		if (Position == 0)
		{
			_activeStopPrice = null;
			return;
		}

		if (!_activeStopPrice.HasValue)
			return;

		var trailDist = TrailingSteps * step;

		if (Position > 0)
		{
			if (candle.ClosePrice > _bestPrice)
				_bestPrice = candle.ClosePrice;

			var trailStop = _bestPrice - trailDist;
			if (trailStop > _activeStopPrice.Value)
				_activeStopPrice = trailStop;

			if (candle.LowPrice <= _activeStopPrice.Value)
			{
				SellMarket(Position);
				_activeStopPrice = null;
				_lastEntryPrice = null;
				_entryCount = 0;
			}
		}
		else
		{
			if (candle.ClosePrice < _bestPrice || _bestPrice == 0m)
				_bestPrice = candle.ClosePrice;

			var trailStop = _bestPrice + trailDist;
			if (trailStop < _activeStopPrice.Value)
				_activeStopPrice = trailStop;

			if (candle.HighPrice >= _activeStopPrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				_activeStopPrice = null;
				_lastEntryPrice = null;
				_entryCount = 0;
			}
		}
	}
}