A Estratégia de rótulos de lucro converte o MetaTrader 5 consultor especialista Rótulos de lucro (54352) em StockSharp API de alto nível. A estratégia monitora os cruzamentos da média móvel exponencial tripla (TEMA) para abrir posições e desenha rótulos de lucro no gráfico após o fechamento de uma posição. Quando a tendência sobe, o algoritmo abre uma posição longa e, quando a tendência cai, ele abre uma posição curta. Se uma posição oposta ainda estiver ativa, a estratégia primeiro a fecha e imprime a etiqueta de lucro realizado.
As velas são processadas por meio de uma assinatura SubscribeCandles, e o indicador é vinculado por meio de Bind para manter a implementação totalmente de alto nível. As velas finalizadas atualizam os valores TEMA e acionam decisões de negociação.
Regras de negociação
Cruzamento de alta: quando o valor TEMA atual se move acima do valor anterior enquanto as leituras mais antigas mostram uma inclinação descendente, a estratégia abre uma posição longa se nenhuma posição longa estiver ativa no momento.
Cruzamento de baixa: quando o TEMA cai da mesma maneira, ele abre uma posição curta se nenhuma venda estiver ativa.
Reversão de posição: se existir uma posição oposta no momento de um novo sinal, a estratégia fecha a posição aberta antes de colocar uma nova ordem.
Rótulos de lucro: assim que a posição for totalmente fechada, o PnL realizado é calculado e exibido no gráfico usando DrawText.
Parâmetros
Nome
Padrão
Descrição
CandleType
TimeSpan.FromMinutes(1).TimeFrame()
Período usado para assinatura de velas.
TemaPeriod
6
Período da média móvel exponencial tripla.
TradeVolume
0.1
Volume enviado com cada ordem de mercado.
PlacingTrade
false
Ativa ou desativa a colocação de pedidos em tempo real.
LabelOffset
0
Deslocamento vertical aplicado ao rótulo de lucro acima do preço comercial.
Notas
A estratégia depende apenas de velas finalizadas e não acessa diretamente os buffers do indicador.
Os níveis protetores de stop-loss e take-profit da versão MQL não são replicados; as posições são invertidas quando chega um sinal oposto.
Os rótulos de lucro usam a moeda de segurança sempre que ela está disponível e, caso contrário, voltam aos 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()