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
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.
Cruce bajista: cuando el TEMA baja de la misma manera, abre una posición corta si no hay ningún corto activo.
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.
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}");
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import TripleExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class profit_labels_strategy(Strategy):
def __init__(self):
super(profit_labels_strategy, self).__init__()
self._tema_period = self.Param("TemaPeriod", 6)
self._trade_volume = self.Param("TradeVolume", 1.0)
self._placing_trade = self.Param("PlacingTrade", True)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._last_signal_time = None
self._prev_trade_buy = False
self._prev_trade_sell = False
self._tema0 = None
self._tema1 = None
self._tema2 = None
self._tema3 = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def TradeVolume(self):
return self._trade_volume.Value
@TradeVolume.setter
def TradeVolume(self, value):
self._trade_volume.Value = value
@property
def PlacingTrade(self):
return self._placing_trade.Value
@PlacingTrade.setter
def PlacingTrade(self, value):
self._placing_trade.Value = value
def OnReseted(self):
super(profit_labels_strategy, self).OnReseted()
self._last_signal_time = None
self._prev_trade_buy = False
self._prev_trade_sell = False
self._tema0 = None
self._tema1 = None
self._tema2 = None
self._tema3 = None
def OnStarted2(self, time):
super(profit_labels_strategy, self).OnStarted2(time)
self.Volume = float(self.TradeVolume)
tema = TripleExponentialMovingAverage()
tema.Length = self._tema_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(tema, self._process_candle).Start()
def _process_candle(self, candle, tema_val):
if candle.State != CandleStates.Finished:
return
self._tema3 = self._tema2
self._tema2 = self._tema1
self._tema1 = self._tema0
self._tema0 = float(tema_val)
if self._tema3 is None or self._tema2 is None or self._tema1 is None or self._tema0 is None:
return
trend_up = self._tema2 < self._tema3 and self._tema0 > self._tema1
trend_down = self._tema2 > self._tema3 and self._tema0 < self._tema1
if not self.PlacingTrade:
return
if trend_up:
self._handle_long_signal(candle)
elif trend_down:
self._handle_short_signal(candle)
def _handle_long_signal(self, candle):
if self._last_signal_time == candle.OpenTime:
return
self._last_signal_time = candle.OpenTime
if self.Position < 0:
self.BuyMarket(abs(float(self.Position)))
self._prev_trade_buy = False
self._prev_trade_sell = False
return
if self.Position != 0 or self._prev_trade_buy:
return
self.BuyMarket(float(self.TradeVolume))
self._prev_trade_buy = True
self._prev_trade_sell = False
def _handle_short_signal(self, candle):
if self._last_signal_time == candle.OpenTime:
return
self._last_signal_time = candle.OpenTime
if self.Position > 0:
self.SellMarket(float(self.Position))
self._prev_trade_buy = False
self._prev_trade_sell = False
return
if self.Position != 0 or self._prev_trade_sell:
return
self.SellMarket(float(self.TradeVolume))
self._prev_trade_buy = False
self._prev_trade_sell = True
def CreateClone(self):
return profit_labels_strategy()