Ver en GitHub

Estrategia de etiquetas de beneficios

Descripción general

La Estrategia de etiquetas de beneficios convierte al MetaTrader 5 asesor experto Etiquetas de beneficios (54352) en la API de alto nivel de StockSharp. La estrategia monitorea los cruces de la media móvil exponencial triple (TEMA) para abrir posiciones y dibuja etiquetas de ganancias en el gráfico después de cerrar una posición. Cuando la tendencia gira hacia arriba, el algoritmo abre una posición larga, y cuando la tendencia gira hacia abajo, abre una posición corta. Si una posición opuesta todavía está activa, la estrategia primero la cierra e imprime la etiqueta de beneficio realizado.

Las velas se procesan a través de una suscripción SubscribeCandles y el indicador está vinculado a través de Bind para mantener la implementación en pleno nivel alto. Las velas terminadas actualizan los valores de TEMA y desencadenan decisiones comerciales.

Reglas de trading

  1. Cruce alcista: cuando el valor actual de TEMA se mueve por encima del valor anterior mientras que las lecturas anteriores muestran una pendiente descendente, la estrategia abre una posición larga si no hay ninguna larga activa actualmente.
  2. Cruce bajista: cuando el TEMA baja de la misma manera, abre una posición corta si no hay ningún corto activo.
  3. Inversión de posición: si existe una posición opuesta en el momento de una nueva señal, la estrategia cierra la posición abierta antes de realizar una nueva orden.
  4. Etiquetas de ganancias: una vez que la posición está completamente cerrada, el PnL realizado se calcula y se muestra en el gráfico usando DrawText.

Parámetros

Nombre Predeterminado Descripción
CandleType TimeSpan.FromMinutes(1).TimeFrame() Marco de tiempo utilizado para la suscripción de velas.
TemaPeriod 6 Período de la Media Móvil Exponencial Triple.
TradeVolume 0.1 Volumen presentado con cada orden de mercado.
PlacingTrade false Habilita o deshabilita la colocación de pedidos en vivo.
LabelOffset 0 Compensación vertical aplicada a la etiqueta de ganancias por encima del precio comercial.

Notas

  • La estrategia se basa únicamente en velas terminadas y no accede directamente a los buffers del indicador.
  • Los niveles protectores de stop-loss y take-profit de la versión MQL no se replican; Las posiciones se invierten cuando llega una señal opuesta.
  • Las etiquetas de ganancias utilizan la moneda de seguridad siempre que esté disponible y, de lo contrario, recurren a valores brutos.
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Profit Labels translation (54352).
/// Draws realized PnL labels after trades close and opens positions on TEMA trend changes.
/// </summary>
public class ProfitLabelsStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _temaPeriod;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<bool> _placingTrade;
	private readonly StrategyParam<decimal> _labelOffset;

	private DateTimeOffset? _lastSignalTime;
	private bool _previousTradeBuy;
	private bool _previousTradeSell;
	private decimal? _entryPrice;
	private Sides? _entrySide;
	private decimal _positionVolume;

	private decimal? _tema0;
	private decimal? _tema1;
	private decimal? _tema2;
	private decimal? _tema3;

	/// <summary>
	/// Initializes a new instance of <see cref="ProfitLabelsStrategy"/>.
	/// </summary>
	public ProfitLabelsStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to process", "General");

		_temaPeriod = Param(nameof(TemaPeriod), 6)
			.SetDisplay("TEMA Period", "Period used for the triple EMA", "Indicator");

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetDisplay("Trade Volume", "Volume used for each position", "Trading");

		_placingTrade = Param(nameof(PlacingTrade), true)
			.SetDisplay("Enable Trading", "Place live orders on signals", "Trading")
			;

		_labelOffset = Param(nameof(LabelOffset), 0m)
			.SetDisplay("Label Offset", "Vertical offset for profit labels", "Visualization")
			;
	}

	/// <summary>
	/// Candle type to process.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Period used for the triple EMA calculation.
	/// </summary>
	public int TemaPeriod
	{
		get => _temaPeriod.Value;
		set => _temaPeriod.Value = value;
	}

	/// <summary>
	/// Volume used for each position.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Defines whether live orders should be placed.
	/// </summary>
	public bool PlacingTrade
	{
		get => _placingTrade.Value;
		set => _placingTrade.Value = value;
	}

	/// <summary>
	/// Vertical offset applied to profit labels.
	/// </summary>
	public decimal LabelOffset
	{
		get => _labelOffset.Value;
		set => _labelOffset.Value = value;
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_lastSignalTime = null;
		_previousTradeBuy = false;
		_previousTradeSell = false;
		_entryPrice = null;
		_entrySide = null;
		_positionVolume = 0m;
		_tema0 = null;
		_tema1 = null;
		_tema2 = null;
		_tema3 = null;
	}

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

		Volume = TradeVolume;

		var tema = new TripleExponentialMovingAverage
		{
			Length = TemaPeriod
		};

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

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

	// Process each finished candle and react to TEMA trend flips.
	private void ProcessCandle(ICandleMessage candle, decimal temaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_tema3 = _tema2;
		_tema2 = _tema1;
		_tema1 = _tema0;
		_tema0 = temaValue;

		if (_tema3 is null || _tema2 is null || _tema1 is null || _tema0 is null)
			return;

		var trendUp = _tema2 < _tema3 && _tema0 > _tema1;
		var trendDown = _tema2 > _tema3 && _tema0 < _tema1;

		if (!PlacingTrade)
			return;

		if (trendUp)
		{
			HandleLongSignal(candle);
		}
		else if (trendDown)
		{
			HandleShortSignal(candle);
		}
	}

	// React to a bullish TEMA crossover.
	private void HandleLongSignal(ICandleMessage candle)
	{
		if (_lastSignalTime == candle.OpenTime)
			return;

		_lastSignalTime = candle.OpenTime;

		if (Position < 0m)
		{
			BuyMarket(Math.Abs(Position));
			_previousTradeBuy = false;
			_previousTradeSell = false;
			return;
		}

		if (Position != 0m || _previousTradeBuy)
			return;

		BuyMarket(TradeVolume);
		_previousTradeBuy = true;
		_previousTradeSell = false;
	}

	// React to a bearish TEMA crossover.
	private void HandleShortSignal(ICandleMessage candle)
	{
		if (_lastSignalTime == candle.OpenTime)
			return;

		_lastSignalTime = candle.OpenTime;

		if (Position > 0m)
		{
			SellMarket(Position);
			_previousTradeBuy = false;
			_previousTradeSell = false;
			return;
		}

		if (Position != 0m || _previousTradeSell)
			return;

		SellMarket(TradeVolume);
		_previousTradeBuy = false;
		_previousTradeSell = true;
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		if (Position != 0m && _entrySide is null)
		{
			_entrySide = Position > 0m ? Sides.Buy : Sides.Sell;
			_entryPrice = trade.Trade.Price;
			_positionVolume = Math.Abs(Position);
			return;
		}

		if (Position == 0m && _entrySide != null && _entryPrice.HasValue)
		{
			var exitPrice = trade.Trade.Price;
			var profit = CalculateProfit(_entrySide.Value, _entryPrice.Value, exitPrice, _positionVolume);

			DrawProfitLabel(profit, trade.Trade.ServerTime, exitPrice);

			_entrySide = null;
			_entryPrice = null;
			_positionVolume = 0m;
		}
	}

	// Calculate realized PnL using instrument parameters when possible.
	private decimal CalculateProfit(Sides entrySide, decimal entryPrice, decimal exitPrice, decimal volume)
	{
		if (volume <= 0m)
			return 0m;

		var priceStep = Security?.PriceStep;
		var stepPrice = GetSecurityValue<decimal?>(Level1Fields.StepPrice);

		if (priceStep.HasValue && priceStep.Value > 0m && stepPrice.HasValue && stepPrice.Value > 0m)
		{
			var points = (exitPrice - entryPrice) / priceStep.Value;
			var direction = entrySide == Sides.Buy ? 1m : -1m;
			return points * stepPrice.Value * volume * direction;
		}

		var rawDifference = entrySide == Sides.Buy
			? exitPrice - entryPrice
			: entryPrice - exitPrice;

		return rawDifference * volume;
	}

	// Draw a label with realized profit information.
	private void DrawProfitLabel(decimal profit, DateTimeOffset time, decimal price)
	{
		// DrawText not available, log instead
		LogInfo($"Profit: {profit:F2} at {time:yyyy-MM-dd HH:mm} price {price}");
	}
}