Ver no GitHub

Estratégia Up3x1 Investor

A estratégia Up3x1 Investor porta o clássico assessor especialista do MetaTrader que reage a velas de expansão forte. Ela observa a última barra completada no período configurado e abre uma nova posição na barra seguinte se o range anterior e o corpo foram suficientemente amplos na direção do fechamento.

A estratégia é projetada para mercados discricionários como os principais pares de forex no gráfico H1, mas os limites podem ser ajustados para outros instrumentos. Apenas uma posição é mantida por vez e cada ordem usa a propriedade Volume da estratégia como tamanho da operação.

Lógica de Trading

  • Fonte de sinais – velas de período completadas de CandleType (padrão: 1 hora).
  • Critérios de entrada
    • Calcular o range máximo–mínimo e o corpo absoluto da vela anterior.
    • Entrar comprado se a vela fechou acima da abertura e tanto o range quanto o corpo excedem seus respectivos limites em pips.
    • Entrar vendido se a vela fechou abaixo da abertura e tanto o range quanto o corpo excedem os limites.
    • Ignorar novas entradas enquanto qualquer posição estiver aberta.
  • Gestão de posições
    • Os níveis opcionais de stop-loss e take-profit são convertidos de pips para unidades de preço usando Security.PriceStep.
    • Um trailing stop é ativado assim que o preço avança TrailingStopPips + TrailingStepPips a partir da entrada.
    • O trailing stop só se move se o novo nível estiver pelo menos TrailingStepPips mais próximo do preço do que o nível de trailing anterior.
    • A estratégia sai de uma posição quando o preço toca os níveis de stop-loss, take-profit ou trailing stop.

Parâmetros

Parâmetro Descrição
CandleType Tipo de dados das velas usadas para sinais (padrão: período de 1 hora).
RangeThresholdPips Distância mínima máximo–mínimo da vela anterior, expressa em pips.
BodyThresholdPips Distância mínima abertura–fechamento da vela anterior, expressa em pips.
StopLossPips Distância de stop-loss em pips. Definir como 0 para desativar.
TakeProfitPips Distância de take-profit em pips. Definir como 0 para desativar.
TrailingStopPips Distância mantida atrás do preço no trailing. Definir como 0 para desativar o trailing.
TrailingStepPips Movimento adicional em pips necessário antes que o trailing stop seja ajustado.

Nota: Os limites em pips são multiplicados por Security.PriceStep. Certifique-se de que o instrumento tem um PriceStep válido para que as conversões de pips reflitam corretamente o seu instrumento.

Notas de Uso

  1. Atribua o Security alvo e o conector de trading antes de iniciar a estratégia.
  2. Ajuste os limites em pips para refletir a volatilidade do seu mercado. Pares de forex com cotações de 5 dígitos normalmente usam 10 pips = 0.0010.
  3. Defina o Volume da estratégia para o tamanho de ordem desejado. A lógica de dimensionamento de posição do EA original é intencionalmente simplificada para manter a versão StockSharp transparente.
  4. Como os sinais são avaliados em velas fechadas, as entradas são enviadas imediatamente após a confirmação da vela de expansão.
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>
/// Range breakout strategy based on the Up3x1 Investor expert advisor.
/// </summary>
public class Up3x1InvestorStrategy : Strategy
{
	private readonly StrategyParam<decimal> _rangeThresholdPips;
	private readonly StrategyParam<decimal> _bodyThresholdPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevOpen;
	private decimal _prevClose;
	private decimal _prevHigh;
	private decimal _prevLow;
	private bool _hasPreviousCandle;
	private decimal? _entryPrice;
	private decimal _highestPrice;
	private decimal _lowestPrice;
	private decimal? _trailingStopPrice;

	public decimal RangeThresholdPips { get => _rangeThresholdPips.Value; set => _rangeThresholdPips.Value = value; }
	public decimal BodyThresholdPips { get => _bodyThresholdPips.Value; set => _bodyThresholdPips.Value = value; }
	public decimal StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
	public decimal TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
	public decimal TrailingStopPips { get => _trailingStopPips.Value; set => _trailingStopPips.Value = value; }
	public decimal TrailingStepPips { get => _trailingStepPips.Value; set => _trailingStepPips.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public Up3x1InvestorStrategy()
	{
		_rangeThresholdPips = Param(nameof(RangeThresholdPips), 2m)
			.SetDisplay("Range Threshold (pips)", "Minimum previous candle range in pips", "Signals");

		_bodyThresholdPips = Param(nameof(BodyThresholdPips), 1m)
			.SetDisplay("Body Threshold (pips)", "Minimum previous candle body in pips", "Signals");

		_stopLossPips = Param(nameof(StopLossPips), 5m)
			.SetDisplay("Stop Loss (pips)", "Distance of protective stop in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 5m)
			.SetDisplay("Take Profit (pips)", "Distance of profit target in pips", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 3m)
			.SetDisplay("Trailing Stop (pips)", "Distance kept behind price when trailing", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
			.SetDisplay("Trailing Step (pips)", "Increment required to move trailing stop", "Risk");

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

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

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

		_prevOpen = 0m;
		_prevClose = 0m;
		_prevHigh = 0m;
		_prevLow = 0m;
		_hasPreviousCandle = false;
		ResetPositionTracking();
	}

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

		// Subscribe to the configured timeframe and process finished candles.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Work only with fully formed candles to keep logic aligned with the original EA.
		if (candle.State != CandleStates.Finished)
			return;

		// If position was closed externally, reset tracking.
		if (Position == 0 && _entryPrice != null)
			ResetPositionTracking();

		var pipSize = GetPipSize();
		var stopLossDistance = StopLossPips > 0 ? StopLossPips * pipSize : 0m;
		var takeProfitDistance = TakeProfitPips > 0 ? TakeProfitPips * pipSize : 0m;
		var trailingStopDistance = TrailingStopPips > 0 ? TrailingStopPips * pipSize : 0m;
		var trailingStepDistance = TrailingStepPips > 0 ? TrailingStepPips * pipSize : 0m;

		// Manage existing trades before searching for a new signal.
		if (Position != 0 && _entryPrice != null)
		{
			if (ManageOpenPosition(candle, stopLossDistance, takeProfitDistance, trailingStopDistance, trailingStepDistance))
			{
				_prevOpen = candle.OpenPrice;
				_prevClose = candle.ClosePrice;
				_prevHigh = candle.HighPrice;
				_prevLow = candle.LowPrice;
				_hasPreviousCandle = true;
				return;
			}
		}

		// no indicators bound, skip IsFormedAndOnlineAndAllowTrading

		if (Position != 0)
		{
			_prevOpen = candle.OpenPrice;
			_prevClose = candle.ClosePrice;
			_prevHigh = candle.HighPrice;
			_prevLow = candle.LowPrice;
			_hasPreviousCandle = true;
			return;
		}

		var refOpen = _hasPreviousCandle ? _prevOpen : candle.OpenPrice;
		var refClose = _hasPreviousCandle ? _prevClose : candle.ClosePrice;
		var refHigh = _hasPreviousCandle ? _prevHigh : candle.HighPrice;
		var refLow = _hasPreviousCandle ? _prevLow : candle.LowPrice;

		var range = refHigh - refLow;
		var body = Math.Abs(refClose - refOpen);
		var rangeThreshold = RangeThresholdPips * pipSize;
		var bodyThreshold = BodyThresholdPips * pipSize;

		// Bullish setup: strong bullish candle with large range and body.
		if (range > rangeThreshold && body > bodyThreshold && refClose > refOpen)
		{
			BuyMarket();
			InitializePositionTracking(candle.ClosePrice);
		}
		// Bearish setup: strong bearish candle with large range and body.
		else if (range > rangeThreshold && body > bodyThreshold && refClose < refOpen)
		{
			SellMarket();
			InitializePositionTracking(candle.ClosePrice);
		}

		_prevOpen = candle.OpenPrice;
		_prevClose = candle.ClosePrice;
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_hasPreviousCandle = true;
	}

	private bool ManageOpenPosition(ICandleMessage candle, decimal stopLossDistance, decimal takeProfitDistance, decimal trailingStopDistance, decimal trailingStepDistance)
	{
		if (_entryPrice == null)
			return false;

		if (Position > 0)
		{
			// Update the highest price reached by the long position.
			_highestPrice = Math.Max(_highestPrice, candle.HighPrice);

			// Check stop loss.
			if (stopLossDistance > 0m && candle.LowPrice <= _entryPrice.Value - stopLossDistance)
			{
				SellMarket();
				ResetPositionTracking();
				return true;
			}

			// Check take profit.
			if (takeProfitDistance > 0m && candle.HighPrice >= _entryPrice.Value + takeProfitDistance)
			{
				SellMarket();
				ResetPositionTracking();
				return true;
			}

			// Update trailing stop level when the move is large enough.
			if (trailingStopDistance > 0m && _highestPrice - _entryPrice.Value >= trailingStopDistance + trailingStepDistance)
			{
				var candidate = _highestPrice - trailingStopDistance;
				if (_trailingStopPrice == null || candidate - _trailingStopPrice.Value >= trailingStepDistance)
				_trailingStopPrice = candidate;
			}

			// Exit if price returned to the trailing stop.
			if (_trailingStopPrice != null && candle.LowPrice <= _trailingStopPrice.Value)
			{
				SellMarket();
				ResetPositionTracking();
				return true;
			}
		}
		else if (Position < 0)
		{
			// Update the lowest price reached by the short position.
			_lowestPrice = Math.Min(_lowestPrice, candle.LowPrice);

			// Check stop loss for short trades.
			if (stopLossDistance > 0m && candle.HighPrice >= _entryPrice.Value + stopLossDistance)
			{
				BuyMarket();
				ResetPositionTracking();
				return true;
			}

			// Check take profit for short trades.
			if (takeProfitDistance > 0m && candle.LowPrice <= _entryPrice.Value - takeProfitDistance)
			{
				BuyMarket();
				ResetPositionTracking();
				return true;
			}

			// Update trailing stop for the short side.
			if (trailingStopDistance > 0m && _entryPrice.Value - _lowestPrice >= trailingStopDistance + trailingStepDistance)
			{
				var candidate = _lowestPrice + trailingStopDistance;
				if (_trailingStopPrice == null || _trailingStopPrice.Value - candidate >= trailingStepDistance)
				_trailingStopPrice = candidate;
			}

			// Exit once the trailing stop is touched.
			if (_trailingStopPrice != null && candle.HighPrice >= _trailingStopPrice.Value)
			{
				BuyMarket();
				ResetPositionTracking();
				return true;
			}
		}

		return false;
	}

	private void InitializePositionTracking(decimal entryPrice)
	{
		// Store entry information to evaluate stops and trailing logic.
		_entryPrice = entryPrice;
		_highestPrice = entryPrice;
		_lowestPrice = entryPrice;
		_trailingStopPrice = null;
	}

	private void ResetPositionTracking()
	{
		_entryPrice = null;
		_highestPrice = 0m;
		_lowestPrice = 0m;
		_trailingStopPrice = null;
	}

	private decimal GetPipSize()
	{
		var step = Security?.PriceStep;
		if (step == null || step == 0m)
			return 1m;

		return step.Value;
	}
}