Die Profit Labels-Strategie wandelt den MetaTrader 5 Expert Advisor Profit Labels (54352) in den StockSharp High-Level API um. Die Strategie überwacht die Übergänge des Triple Exponential Moving Average (TEMA) zu offenen Positionen und zeichnet Gewinnmarkierungen auf dem Diagramm auf, nachdem eine Position geschlossen wurde. Wenn der Trend nach oben umschlägt, eröffnet der Algorithmus eine Long-Position, und wenn der Trend nach unten umschlägt, eröffnet er eine Short-Position. Wenn noch eine Gegenposition aktiv ist, schließt die Strategie diese zunächst und druckt das Etikett des realisierten Gewinns aus.
Kerzen werden über ein SubscribeCandles-Abonnement verarbeitet und der Indikator ist über Bind gebunden, um die Implementierung vollständig auf hohem Niveau zu halten. Fertige Kerzen aktualisieren die TEMA-Werte und lösen Handelsentscheidungen aus.
Handelsregeln
Bullish Crossover: Wenn der aktuelle TEMA-Wert über den vorherigen Wert steigt, während die älteren Messwerte einen Abwärtstrend zeigen, eröffnet die Strategie eine Long-Position, wenn derzeit keine Long-Position aktiv ist.
Bearish Crossover: Wenn der TEMA auf die gleiche Weise nach unten dreht, eröffnet er eine Short-Position, sofern kein Short aktiv ist.
Positionsumkehr: Wenn zum Zeitpunkt eines neuen Signals eine entgegengesetzte Position besteht, schließt die Strategie die offene Position, bevor eine neue Order platziert wird.
Gewinnbezeichnungen: Sobald die Position vollständig geschlossen ist, wird der realisierte PnL berechnet und mit DrawText im Diagramm angezeigt.
Parameter
Name
Standard
Beschreibung
CandleType
TimeSpan.FromMinutes(1).TimeFrame()
Zeitrahmen für das Kerzenabonnement.
TemaPeriod
6
Periode des dreifachen exponentiellen gleitenden Durchschnitts.
TradeVolume
0.1
Mit jeder Market-Order übermitteltes Volumen.
PlacingTrade
false
Aktiviert oder deaktiviert die Live-Auftragserteilung.
LabelOffset
0
Vertikaler Offset, der auf das Gewinnetikett über dem Handelspreis angewendet wird.
Notizen
Die Strategie basiert ausschließlich auf fertigen Kerzen und greift nicht direkt auf Indikatorpuffer zu.
Schützende Stop-Loss- und Take-Profit-Levels aus der MQL-Version werden nicht repliziert; Die Positionen werden umgekehrt, wenn ein entgegengesetztes Signal eintrifft.
Gewinnetiketten verwenden die Sicherheitswährung, wann immer diese verfügbar ist, und greifen andernfalls auf Rohwerte zurück.
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()