Ver no GitHub

Estratégia Master MM Droid

Visão geral

A estratégia Master MM Droid é um port multi-módulo do consultor especialista original do MetaTrader 5. A implementação do StockSharp mantém as ideias centrais do robô legado enquanto usa a API de alto nível para assinaturas de velas, vinculação de indicadores e gestão de ordens. Quatro blocos de gestão de capital independentes podem ser ativados ou desativados, permitindo que a estratégia misture entradas de momentum com ordens de rompimento agendadas e operações de gap semanais.

Módulos

  1. Bloco RSI
    • Usa um Índice de Força Relativa de 14 períodos no tipo de vela configurado.
    • Entra comprado quando o RSI cruza para cima a partir do limiar de sobrevenda e vendido quando cruza para baixo a partir do nível de sobrecompra.
    • Permite pirâmide com um número configurável de entradas adicionais separadas por um passo de preço fixo.
    • Aplica um stop inicial fixo baseado na distância em pontos e ativa um stop móvel assim que a posição está aberta.
  2. Bloco de rompimento de caixa
    • Reconstrói caixas de rompimento três vezes por dia (horas com deslocamento de sessão 6, 12 e 20 por padrão).
    • Coloca ordens de stop agrupadas acima da máxima da sessão e abaixo da mínima com um buffer configurável.
    • Cancela todas as ordens pendentes e posições nos reinícios de sessão (horas 0, 10 e 16), imitando o comportamento do especialista original.
  3. Bloco de rompimento semanal
    • Rastreia a ação de preço de segunda-feira e armazena a máxima/mínima acumulada da primeira parte da sessão.
    • Coloca ordens de stop simétricas dentro de uma janela de ativação limitada (StartHourWeeklySetupEndHour) para que a semana comece com um rompimento OCO.
    • Força um estado plano nas noites de sexta-feira para evitar exposição de fim de semana.
  4. Bloco de gap
    • Compara a nova abertura diária com a máxima/mínima do dia anterior (usando o calendário com deslocamento).
    • Compra fortes aberturas de gap de baixa e vende fortes aberturas de gap de alta.
    • Estabelece um stop protetor a uma distância configurável e entrega a gestão adicional ao motor de trailing.

Parâmetros

Nome Descrição
CandleType Período de tempo usado para cálculos de indicadores e verificações de janelas de tempo.
TimeShiftHours Deslocamento de sessão aplicado aos timestamps de velas para que o horário corresponda ao EA original.
StartHour Hora de início base da segunda-feira para o módulo semanal (antes de aplicar o deslocamento).
EnableRsiModule, EnableBoxModule, EnableWeeklyModule, EnableGapModule Interruptores para os quatro blocos independentes.
RsiPeriod, RsiLowerLevel, RsiUpperLevel Cálculo RSI e níveis de disparo.
RsiMaxEntries, RsiPyramidPoints Controles de pirâmide para o bloco RSI.
RsiStopLossPoints, RsiTrailingPoints Tamanhos de stop inicial e stop móvel (em pontos) para trades dirigidos por RSI.
BoxEntryPoints, BoxTrailingPoints Buffer de rompimento e distância de trailing para as ordens de caixa.
WeeklyEntryPoints, WeeklySetupEndHour, WeeklyTrailingPoints Configuração de rompimento semanal.
GapStopLossPoints, GapTrailingPoints Stop protetor e distância de trailing do módulo de gap.

Todos os parâmetros baseados em pontos são multiplicados pelo TickSize do instrumento para obter deslocamentos de preço, de modo que a estratégia se adapta a diferentes símbolos.

Lógica de trading

  • Vinculação de indicadores: Um único indicador RSI está vinculado à assinatura de velas. Cada vela concluída aciona ProcessCandle, que despacha os valores para os quatro manipuladores de módulo.
  • Rastreamento do estado diário: A estratégia agrega abertura/máxima/mínima para cada dia com deslocamento para suportar a lógica de gap e manter uma referência histórica para o módulo semanal.
  • Colocação de ordens: As ordens são enviadas através de BuyMarket, SellMarket, BuyStop, SellStop de acordo com as melhores práticas da API de alto nível. Os módulos agendados sempre cancelam as ordens ativas antes de se rearmar para evitar duplicatas.
  • Gestão de trailing: Uma vez que uma posição está ativa, _activeTrailingPoints armazena a distância específica do módulo. O método UpdateTrailing move ordens de stop apenas na direção favorável.

Gerenciamento de risco

  • Apenas as ordens de mercado criadas pelos módulos RSI e gap são protegidas por um stop imediato calculado em pontos.
  • Os módulos de rompimento dependem do motor de trailing após a ativação; podem ser combinados com proteção de portfólio externo se necessário.
  • Chamar ClosePosition() é a forma canônica de achatar, preservando a compatibilidade com as ferramentas de risco do StockSharp.

Notas de uso

  • A estratégia opera em um único instrumento e usa o valor global Volume para dimensionamento. Ajuste a proteção de portfólio separadamente se precisar de limites de risco por posição.
  • Os tempos de sessão são avaliados após aplicar TimeShiftHours. Por exemplo, com o valor padrão 2, o reinício de caixa na hora 0 corresponde às 02:00 do servidor.
  • Como as estratégias do StockSharp gerenciam posições líquidas, cestas longas/vendidas simultâneas (possíveis em contas de cobertura do MetaTrader) são consolidadas. Esta é a principal diferença de comportamento em relação ao EA original e deve ser considerada durante a validação.

Registro e monitoramento

  • Cada módulo redefine seus flags internos assim que a posição retorna a zero, ajudando os operadores a diagnosticar qual bloco produziu uma operação.
  • Adicione gráficos opcionais ou registro através das instalações do StockSharp se análises detalhadas forem necessárias.
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;
			}
		}
	}
}