Ver no GitHub

Estratégia OpenPendingorderAfterPositionGetStopLoss

Visão geral

A estratégia OpenPendingorderAfterPositionGetStopLoss transporta o MetaTrader 5 consultor especialista de mesmo nome para o StockSharp API de alto nível. Ele avalia continuamente a inclinação da linha Stochastic %K no período de tempo selecionado. Quando %K cai, ele coloca uma ordem de stop de venda abaixo do mercado, e quando %K sobe, ele coloca uma ordem de stop de compra acima do mercado. Cada entrada preenchida recebe imediatamente uma ordem protetora de stop-loss e take-profit. Se um stop-loss fechar a posição, a estratégia reinstala automaticamente a ordem pendente correspondente para que a grade de negociações de breakout seja restaurada sem esperar pela próxima vela.

Regras de negociação

  • Assine velas finalizadas do período configurado e calcule um oscilador Stochastic clássico (KPeriod, DPeriod, Slowing).
  • Compare o valor atual de %K com o valor de duas barras atrás:
    • %K(current) < %K(two bars ago) → envie um stop de venda abaixo do melhor lance.
    • %K(current) > %K(two bars ago) → envie um stop de compra acima da melhor venda.
  • As ordens pendentes são compensadas do mercado pelo spread atual mais o buffer MinStopDistancePoints definido pelo usuário, correspondendo à lógica MQL original.
  • Assim que uma ordem pendente é preenchida, a estratégia envia um stop-loss de proteção (ordem stop) e um take-profit opcional (ordem limite).
  • Quando o stop-loss de proteção é acionado, a ordem pendente correspondente é recriada imediatamente usando os preços de mercado mais recentes.
  • As ordens protetoras são canceladas automaticamente quando a posição é fechada pelo take-profit ou quando a estratégia é interrompida.

Parâmetros

Nome Descrição
OrderVolume Volume de negociação em lotes para cada ordem pendente.
StopLossPoints Distância de stop-loss em pontos de símbolo. Defina como 0 para desativar.
TakeProfitPoints Distância de lucro em pontos de símbolo. Defina como 0 para desativar.
MinStopDistancePoints Buffer de preço mínimo (em pontos) adicionado ao spread antes de colocar uma ordem pendente.
MaxPositions Número máximo de posições simultâneas por direção (as contas de compensação usam efetivamente 0 ou 1).
KPeriod Número de barras utilizadas para o cálculo de %K.
DPeriod Comprimento da linha %D de suavização.
Slowing Fator de suavização adicional aplicado a %K antes da comparação.
PendingExpiry Vida útil opcional de ordens stop pendentes. Pedidos expirados são cancelados na próxima vela.
CandleType Prazo usado para assinatura de velas e cálculos de indicadores.

Notas de implementação

  • Todo gerenciamento de pedidos depende de ajudantes de alto nível, como BuyStop, SellStop, SellLimit e BuyLimit conforme exigido por AGENTS.md.
  • Os valores do indicador são consumidos diretamente dentro do retorno de chamada SubscribeCandles().BindEx(...), evitando quaisquer chamadas GetValue.
  • A estratégia monitora eventos MyTrade para instalar e remover ordens de proteção, emulando a lógica OnTradeTransaction do Expert Advisor original.
  • Os comentários dentro do código são escritos em inglês e o recuo é feito com tabulações, obedecendo às diretrizes do repositório.
namespace StockSharp.Samples.Strategies;

using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Trades based on the slope of the Stochastic %K line.
/// Buys when %K is rising, sells when %K is falling.
/// Uses take-profit and stop-loss protection.
/// </summary>
public class OpenPendingorderAfterPositionGetStopLossStrategy : Strategy
{
	private readonly StrategyParam<int> _kPeriod;
	private readonly StrategyParam<int> _dPeriod;
	private readonly StrategyParam<int> _slowing;
	private readonly StrategyParam<decimal> _stopLossPct;
	private readonly StrategyParam<decimal> _takeProfitPct;
	private readonly StrategyParam<int> _signalCooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _lastK;
	private decimal? _prevK;
	private decimal _entryPrice;
	private int _candlesSinceTrade;

	public int KPeriod
	{
		get => _kPeriod.Value;
		set => _kPeriod.Value = value;
	}

	public int DPeriod
	{
		get => _dPeriod.Value;
		set => _dPeriod.Value = value;
	}

	public int Slowing
	{
		get => _slowing.Value;
		set => _slowing.Value = value;
	}

	public decimal StopLossPct
	{
		get => _stopLossPct.Value;
		set => _stopLossPct.Value = value;
	}

	public decimal TakeProfitPct
	{
		get => _takeProfitPct.Value;
		set => _takeProfitPct.Value = value;
	}

	public int SignalCooldownCandles
	{
		get => _signalCooldownCandles.Value;
		set => _signalCooldownCandles.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public OpenPendingorderAfterPositionGetStopLossStrategy()
	{
		_kPeriod = Param(nameof(KPeriod), 22)
			.SetDisplay("%K Period", "Number of bars for %K", "Indicators");

		_dPeriod = Param(nameof(DPeriod), 7)
			.SetDisplay("%D Period", "Smoothing period for %K", "Indicators");

		_slowing = Param(nameof(Slowing), 2)
			.SetDisplay("Slowing", "Additional smoothing factor", "Indicators");

		_stopLossPct = Param(nameof(StopLossPct), 2m)
			.SetDisplay("Stop Loss %", "Stop-loss as percentage of entry price", "Risk");

		_takeProfitPct = Param(nameof(TakeProfitPct), 3m)
			.SetDisplay("Take Profit %", "Take-profit as percentage of entry price", "Risk");

		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for indicator", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_lastK = null;
		_prevK = null;
		_entryPrice = 0;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_lastK = null;
		_prevK = null;
		_entryPrice = 0;
		_candlesSinceTrade = SignalCooldownCandles;

		var rsi = new RelativeStrengthIndex { Length = KPeriod };

		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 currentK)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var close = candle.ClosePrice;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		// Check stop-loss / take-profit on existing position
		if (Position != 0 && _entryPrice > 0)
		{
			if (Position > 0)
			{
				var pnlPct = (close - _entryPrice) / _entryPrice * 100m;
				if (pnlPct <= -StopLossPct || pnlPct >= TakeProfitPct)
				{
					SellMarket();
					_entryPrice = 0;
					_prevK = currentK;
					_lastK = currentK;
					return;
				}
			}
			else if (Position < 0)
			{
				var pnlPct = (_entryPrice - close) / _entryPrice * 100m;
				if (pnlPct <= -StopLossPct || pnlPct >= TakeProfitPct)
				{
					BuyMarket();
					_entryPrice = 0;
					_prevK = currentK;
					_lastK = currentK;
					return;
				}
			}
		}

		// Need at least 2 values to determine signal transition.
		if (_lastK is not decimal prevK)
		{
			_lastK = currentK;
			return;
		}

		_prevK = _lastK;
		_lastK = currentK;

		var crossedUp = prevK <= 45m && currentK > 45m;
		var crossedDown = prevK >= 55m && currentK < 55m;

		if (crossedUp && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			BuyMarket();
			_entryPrice = close;
			_candlesSinceTrade = 0;
		}
		else if (crossedDown && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			SellMarket();
			_entryPrice = close;
			_candlesSinceTrade = 0;
		}
	}
}