Ver no GitHub

Estratégia JS Sistem 2

Visão geral

JS Sistem 2 é um sistema de seguimento de tendência originalmente escrito para MetaTrader 5. O port para StockSharp mantém o bloco de confirmação multi-indicador do expert advisor e opera em velas fechadas do período selecionado. As ordens têm volume fixo e podem ser opcionalmente bloqueadas se o saldo do portfólio conectado cair abaixo de um limite configurável. O risco é controlado por meio de distâncias rígidas de stop-loss e take-profit expressas em pips, juntamente com um trailing stop adaptativo que segue as sombras das velas.

Indicadores e filtros

  • EMA(55), EMA(89), EMA(144) – formam um filtro direcional. As configurações compradas exigem a EMA rápida acima da média e a média acima da linha lenta, enquanto a distância entre as curvas rápida e lenta deve permanecer abaixo de MinDifferencePips.
  • Histograma MACD (OsMA) – usa comprimentos de EMA rápida, lenta e sinal idênticos à versão MQL. Uma operação comprada requer que o histograma seja positivo, uma operação vendida requer que seja negativo.
  • Índice de Vigor Relativo (RVI) – calculado com período RviPeriod e suavizado por uma média móvel simples adicional com RviSignalLength. Operações compradas precisam que o RVI esteja acima de sua linha de sinal e acima do limiar RviMax; vendidas precisam do inverso.
  • Envelopes de swing mais alto/mais baixo – rastreiam o máximo mais alto e o mínimo mais baixo durante VolatilityPeriod velas. Esses valores impulsionam a lógica do trailing stop e replicam o modo de trailing por sombras do expert advisor original.

Lógica de trading

  1. A estratégia processa apenas velas terminadas do CandleType configurado.
  2. Antes de avaliar entradas, atualiza o trailing stop para posições existentes usando os últimos extremos de swing e, em seguida, verifica se os níveis de stop-loss ou take-profit foram atingidos durante a vela.
  3. Condições de entrada comprada:
    • O saldo do portfólio está acima de MinBalance.
    • EMA55 > EMA89 > EMA144 e a diferença entre EMA55 e EMA144 está abaixo de MinDifferencePips (convertida para unidades de preço através do tamanho de pip do instrumento).
    • O histograma MACD (macdLine) é maior que zero.
    • O RVI está acima de sua linha de sinal e a linha de sinal está em ou acima de RviMax.
    • Não há posição comprada existente (Position <= 0). Quando existe uma posição vendida, ela é nivelada antes de abrir a comprada.
  4. As condições de entrada vendida espelham as regras compradas com comparações invertidas e usam o limiar RviMin.
  5. Ao entrar, a estratégia armazena o preço de fechamento da vela como referência, coloca níveis virtuais de stop-loss e take-profit deslocando esse preço por StopLossPips e TakeProfitPips, e redefine o estado de trailing.

Gestão de saída e trailing

  • Stop-loss / take-profit rígido: Sempre que o range da vela se sobreponha com o nível de stop ou alvo armazenado, a estratégia fecha toda a posição imediatamente.
  • Trailing stop: Quando TrailingEnabled é verdadeiro, a estratégia tenta mover o stop na direção do lucro. Para comprados, o stop é elevado para o mínimo mais baixo das últimas VolatilityPeriod velas, uma vez que esse mínimo esteja acima tanto do preço de entrada quanto do stop anterior em pelo menos TrailingIndentPips. Vendidos seguem a regra simétrica usando o máximo mais alto. Isso reproduz o "trailing por sombras" do advisor MQL e impede que os stops se apertem prematuramente.
  • Proteção de saldo: Se o valor atual do portfólio cair abaixo de MinBalance, a estratégia se abstém de enviar novas ordens, mas ainda gerencia operações abertas e trailing stops.

Parâmetros

Parâmetro Descrição Padrão
MinBalance Saldo mínimo do portfólio necessário para novas entradas. 100
Volume Volume da ordem enviado com cada operação. 1
StopLossPips Distância do stop-loss medida em pips. Definir como 0 para desabilitar. 35
TakeProfitPips Distância do take-profit medida em pips. Definir como 0 para desabilitar. 40
MinDifferencePips Diferencial máximo permitido entre a EMA rápida e lenta em pips. 28
VolatilityPeriod Número de velas usadas para calcular máximos e mínimos de swing para o trailing stop. 15
TrailingEnabled Habilita ou desabilita a lógica do trailing stop. true
TrailingIndentPips Intervalo mínimo entre preço, entrada e stop ao atualizar o trailing stop. 1
MaFastPeriod Período para a EMA rápida. 55
MaMediumPeriod Período para a EMA média. 89
MaSlowPeriod Período para a EMA lenta. 144
OsmaFastPeriod Comprimento da EMA rápida para o histograma MACD. 13
OsmaSlowPeriod Comprimento da EMA lenta para o histograma MACD. 55
OsmaSignalPeriod Comprimento de suavização do sinal para o histograma MACD. 21
RviPeriod Período do Índice de Vigor Relativo. 44
RviSignalLength Comprimento da SMA aplicada ao RVI para obter sua linha de sinal. 4
RviMax Limite superior que o sinal RVI deve atingir antes que as entradas compradas sejam permitidas. 0.04
RviMin Limite inferior que o sinal RVI deve atingir antes que as entradas vendidas sejam permitidas. -0.04
CandleType Período das velas usadas para todos os cálculos. Velas de 5 minutos

Notas de implementação

  • A distância de pip é derivada do passo de preço do instrumento. Instrumentos cotados com 3 ou 5 casas decimais usam um pip igual a dez passos de preço, correspondendo à lógica MQL original.
  • O gerenciamento de stop e alvo ocorre dentro do loop da estratégia porque o StockSharp não envia automaticamente ordens do lado do servidor para eles neste template.
  • A estratégia chama StartProtection() durante a inicialização para que a classe base possa monitorar desconexões inesperadas e posições pendentes.
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>
/// JS Sistem 2 trend-following strategy converted from MetaTrader 5.
/// Combines exponential moving averages, MACD histogram (OsMA), and Relative Vigor Index filters.
/// Includes trailing stop based on recent candle shadows and configurable stop/target distances.
/// </summary>
public class JsSistem2Strategy : Strategy
{
	private readonly StrategyParam<decimal> _minBalance;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _minDifferencePips;
	private readonly StrategyParam<int> _volatilityPeriod;
	private readonly StrategyParam<bool> _trailingEnabled;
	private readonly StrategyParam<int> _trailingIndentPips;
	private readonly StrategyParam<int> _maFastPeriod;
	private readonly StrategyParam<int> _maMediumPeriod;
	private readonly StrategyParam<int> _maSlowPeriod;
	private readonly StrategyParam<int> _osmaFastPeriod;
	private readonly StrategyParam<int> _osmaSlowPeriod;
	private readonly StrategyParam<int> _osmaSignalPeriod;
	private readonly StrategyParam<int> _rviPeriod;
	private readonly StrategyParam<int> _rviSignalLength;
	private readonly StrategyParam<decimal> _rviMax;
	private readonly StrategyParam<decimal> _rviMin;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _emaFast = null!;
	private ExponentialMovingAverage _emaMedium = null!;
	private ExponentialMovingAverage _emaSlow = null!;
	private MovingAverageConvergenceDivergence _macd = null!;
	private Highest _highest = null!;
	private Lowest _lowest = null!;
	private RelativeVigorIndex _rvi = null!;

	private decimal? _stopPrice;
	private decimal? _takePrice;
	private decimal _entryPrice;

	/// <summary>
	/// Minimum account balance required to allow new entries.
	/// </summary>
	public decimal MinBalance
	{
		get => _minBalance.Value;
		set => _minBalance.Value = value;
	}


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

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

	/// <summary>
	/// Maximum allowed spread between fast and slow EMA in pips.
	/// </summary>
	public int MinDifferencePips
	{
		get => _minDifferencePips.Value;
		set => _minDifferencePips.Value = value;
	}

	/// <summary>
	/// Lookback for trailing stop based on candle shadows.
	/// </summary>
	public int VolatilityPeriod
	{
		get => _volatilityPeriod.Value;
		set => _volatilityPeriod.Value = value;
	}

	/// <summary>
	/// Enables trailing stop management.
	/// </summary>
	public bool TrailingEnabled
	{
		get => _trailingEnabled.Value;
		set => _trailingEnabled.Value = value;
	}

	/// <summary>
	/// Offset applied when updating trailing stop levels.
	/// </summary>
	public int TrailingIndentPips
	{
		get => _trailingIndentPips.Value;
		set => _trailingIndentPips.Value = value;
	}

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int MaFastPeriod
	{
		get => _maFastPeriod.Value;
		set => _maFastPeriod.Value = value;
	}

	/// <summary>
	/// Medium EMA period.
	/// </summary>
	public int MaMediumPeriod
	{
		get => _maMediumPeriod.Value;
		set => _maMediumPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int MaSlowPeriod
	{
		get => _maSlowPeriod.Value;
		set => _maSlowPeriod.Value = value;
	}

	/// <summary>
	/// Fast EMA length for the MACD/OsMA filter.
	/// </summary>
	public int OsmaFastPeriod
	{
		get => _osmaFastPeriod.Value;
		set => _osmaFastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA length for the MACD/OsMA filter.
	/// </summary>
	public int OsmaSlowPeriod
	{
		get => _osmaSlowPeriod.Value;
		set => _osmaSlowPeriod.Value = value;
	}

	/// <summary>
	/// Signal length for the MACD/OsMA filter.
	/// </summary>
	public int OsmaSignalPeriod
	{
		get => _osmaSignalPeriod.Value;
		set => _osmaSignalPeriod.Value = value;
	}

	/// <summary>
	/// Relative Vigor Index period.
	/// </summary>
	public int RviPeriod
	{
		get => _rviPeriod.Value;
		set => _rviPeriod.Value = value;
	}

	/// <summary>
	/// Smoothing length for the RVI signal line.
	/// </summary>
	public int RviSignalLength
	{
		get => _rviSignalLength.Value;
		set => _rviSignalLength.Value = value;
	}

	/// <summary>
	/// Upper threshold for the RVI signal line.
	/// </summary>
	public decimal RviMax
	{
		get => _rviMax.Value;
		set => _rviMax.Value = value;
	}

	/// <summary>
	/// Lower threshold for the RVI signal line.
	/// </summary>
	public decimal RviMin
	{
		get => _rviMin.Value;
		set => _rviMin.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the <see cref="JsSistem2Strategy"/> class.
	/// </summary>
	public JsSistem2Strategy()
	{
		_minBalance = Param(nameof(MinBalance), 100m)
			.SetDisplay("Min Balance", "Minimum balance to allow trading", "Risk")
			;


		_stopLossPips = Param(nameof(StopLossPips), 200)
			.SetDisplay("Stop Loss", "Stop-loss distance in pips", "Risk")
			;

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

		_minDifferencePips = Param(nameof(MinDifferencePips), 5000)
			.SetGreaterThanZero()
			.SetDisplay("EMA Spread", "Maximum fast-slow EMA spread", "Filters")
			;

		_volatilityPeriod = Param(nameof(VolatilityPeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("Trailing Range", "Number of candles for trailing", "Risk")
			;

		_trailingEnabled = Param(nameof(TrailingEnabled), true)
			.SetDisplay("Trailing", "Enable trailing stop", "Risk");

		_trailingIndentPips = Param(nameof(TrailingIndentPips), 1)
			.SetGreaterThanZero()
			.SetDisplay("Trailing Offset", "Indent from candle shadows", "Risk")
			;

		_maFastPeriod = Param(nameof(MaFastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
			;

		_maMediumPeriod = Param(nameof(MaMediumPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("Medium EMA", "Medium EMA period", "Indicators")
			;

		_maSlowPeriod = Param(nameof(MaSlowPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
			;

		_osmaFastPeriod = Param(nameof(OsmaFastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("OsMA Fast", "Fast EMA for MACD", "Indicators")
			;

		_osmaSlowPeriod = Param(nameof(OsmaSlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("OsMA Slow", "Slow EMA for MACD", "Indicators")
			;

		_osmaSignalPeriod = Param(nameof(OsmaSignalPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("OsMA Signal", "Signal period for MACD", "Indicators")
			;

		_rviPeriod = Param(nameof(RviPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("RVI Period", "Relative Vigor Index period", "Indicators")
			;

		_rviSignalLength = Param(nameof(RviSignalLength), 4)
			.SetGreaterThanZero()
			.SetDisplay("RVI Signal", "Smoothing for RVI signal", "Indicators")
			;

		_rviMax = Param(nameof(RviMax), 0.02m)
			.SetDisplay("RVI Max", "Upper threshold for RVI signal", "Filters")
			;

		_rviMin = Param(nameof(RviMin), -0.02m)
			.SetDisplay("RVI Min", "Lower threshold for RVI signal", "Filters")
			;

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

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

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

		_stopPrice = null;
		_takePrice = null;
		_entryPrice = 0m;
	}

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

		_emaFast = new ExponentialMovingAverage { Length = MaFastPeriod };
		_emaMedium = new ExponentialMovingAverage { Length = MaMediumPeriod };
		_emaSlow = new ExponentialMovingAverage { Length = MaSlowPeriod };
		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = OsmaFastPeriod },
			LongMa = { Length = OsmaSlowPeriod },
		};
		_highest = new Highest { Length = VolatilityPeriod };
		_lowest = new Lowest { Length = VolatilityPeriod };
		_rvi = new RelativeVigorIndex();
		_rvi.Average.Length = RviPeriod;
		_rvi.Signal.Length = RviSignalLength;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_emaFast, _emaMedium, _emaSlow, _macd, _highest, _lowest, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaFast, decimal emaMedium, decimal emaSlow, decimal macdLine, decimal highestValue, decimal lowestValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_emaFast.IsFormed || !_emaMedium.IsFormed || !_emaSlow.IsFormed || !_macd.IsFormed || !_highest.IsFormed || !_lowest.IsFormed)
			return;

		var step = CalculatePipSize();
		if (step == 0m)
		{
			step = Security.PriceStep ?? 0m;
		}
		if (step == 0m)
			step = 1m;

		var stopDistance = StopLossPips > 0 ? StopLossPips * step : 0m;
		var takeDistance = TakeProfitPips > 0 ? TakeProfitPips * step : 0m;
		var minDifference = MinDifferencePips * step;
		var indent = TrailingIndentPips * step;

		UpdateTrailingStops(candle, highestValue, lowestValue, indent);
		HandleStopsAndTargets(candle);

		var canTrade = (Portfolio?.CurrentValue ?? decimal.MaxValue) >= MinBalance;

		var emaOrderLong = emaFast > emaMedium && emaMedium > emaSlow;
		var emaOrderShort = emaFast < emaMedium && emaMedium < emaSlow;
		var emaSpreadLong = Math.Abs(emaFast - emaSlow) < minDifference;
		var emaSpreadShort = Math.Abs(emaSlow - emaFast) < minDifference;

		var longCondition = canTrade && emaOrderLong && emaSpreadLong && macdLine > 0m;
		var shortCondition = canTrade && emaOrderShort && emaSpreadShort && macdLine < 0m;

		if (longCondition && Position <= 0)
		{
			if (Position < 0)
			{
				BuyMarket(Math.Abs(Position));
				ResetOrders();
			}

			if (Volume > 0m)
			{
				BuyMarket(Volume);
				_entryPrice = candle.ClosePrice;
				_stopPrice = stopDistance > 0m ? _entryPrice - stopDistance : null;
				_takePrice = takeDistance > 0m ? _entryPrice + takeDistance : null;
			}
		}
		else if (shortCondition && Position >= 0)
		{
			if (Position > 0)
			{
				SellMarket(Math.Abs(Position));
				ResetOrders();
			}

			if (Volume > 0m)
			{
				SellMarket(Volume);
				_entryPrice = candle.ClosePrice;
				_stopPrice = stopDistance > 0m ? _entryPrice + stopDistance : null;
				_takePrice = takeDistance > 0m ? _entryPrice - takeDistance : null;
			}
		}
	}

	private void UpdateTrailingStops(ICandleMessage candle, decimal highestValue, decimal lowestValue, decimal indent)
	{
		if (!TrailingEnabled)
			return;

		if (Position > 0)
		{
			var newStop = lowestValue;
			if (newStop > 0m && candle.ClosePrice - newStop > indent && newStop - _entryPrice > indent)
			{
				if (!_stopPrice.HasValue || newStop - _stopPrice.Value > indent)
				{
					_stopPrice = newStop;
				}
			}
		}
		else if (Position < 0)
		{
			var newStop = highestValue;
			if (newStop > 0m && newStop - candle.ClosePrice > indent && _entryPrice - newStop > indent)
			{
				if (!_stopPrice.HasValue || _stopPrice.Value - newStop > indent)
				{
					_stopPrice = newStop;
				}
			}
		}
	}

	private void HandleStopsAndTargets(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetOrders();
				return;
			}

			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetOrders();
			}
		}
		else if (Position < 0)
		{
			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetOrders();
				return;
			}

			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetOrders();
			}
		}
	}

	private void ResetOrders()
	{
		_stopPrice = null;
		_takePrice = null;
		_entryPrice = 0m;
	}

	private decimal CalculatePipSize()
	{
		var security = Security;
		if (security is null)
			return 0m;

		var step = security.PriceStep ?? 0m;
		if (step == 0m)
			return 0m;

		var decimals = security.Decimals;
		return decimals == 3 || decimals == 5 ? step * 10m : step;
	}
}