Indicator to share trade executions in real-time via Discord or Telegram Share Services

Hi,
here I send an indicator I have developed with help of Perplexity and DeepSeek and which can be used in Discord or Telegram channels/groups for automatic account trade entries and exits in realtime, for example for sharing in a community or for own journalling in RT.
The indicator uses Discord or Telegram Share Services, which are configured via the indicator properties, to send trade context information as well as chart of the asset, depending on user configuration.
I will also upload separately the required Discord and Telegram Share Services in different topics. For information about the Share Services, I recommend looking for the specific entry in the forum.

DiscordTradesLight — Short Summary

What it does

A NinjaTrader 8 indicator that detects trade executions on your account and sends formatted notifications (with or without chart screenshot) to Discord and/or Telegram via NT8’s ShareServices.

Core functionality

Trade tracking (FIFO)

  • Maintains two FIFO queues per instrument: one for long lots, one for short lots.

  • Each execution is mapped: closing executions consume the opposite queue (FIFO), remaining quantity is enqueued as a new opening lot.

  • Handles partial fills, reversals, and multi-lot positions correctly.

  • Produces Position Open and Position Closed messages with entry/exit time, price, PnL (net of commissions), and points/ticks.

Message formatting

  • Monospaced, column-aligned blocks using PadDiscord (handles Discord’s math-bold surrogate pairs).

  • Discord format: wrapped in **bold** + ``` code blocks.

  • Telegram format: plain text (separate method BuildOpenMessageT / BuildCloseMessageT).

Commission handling

  • Uses e.Execution.Commission (real value from account’s commission template).

  • Fallback to NinjaTrader Free plan rates ($1.29 standard / $0.39 micro) if execution commission is 0.

  • Optional EUR/USD conversion factor (EurUsdRate, default 1.14) for €-denominated instruments.

UI buttons (on-chart)

Button States Behavior
CURRENT / ALL / NONE 3-state cycle CURRENT: only this chart’s instrument, sends with screenshot, brings chart to front. ALL: all instruments (indicator executing in a single chart and retrieving all executions independent of the chart asset), text only. NONE: sends nothing.
Discord / Telegram / Off 3-state cycle Selects destination ShareService amond Discord and Telegram or disables sending.
Off PnL / On PnL Toggle Enables/disables end-of-day PnL report at a particular, configurable EoD time.

Configuration (indicator properties)

  • Start/End time for EOD report trigger (format HHMMSS).

  • Discord Shareservice Name — name of the Discord ShareService configured in NT8.

  • Telegram Shareservice Name — name of the Telegram ShareService configured in NT8.

  • EUR/USD Rate — conversion factor for fallback commission on € instruments.

What it does NOT do

  • No OCO/bracket awareness — designed for entries/exits only (no position management via OCO since it is OCO-agnostic) and for any order type/name.

  • No strategy integration — pure execution listener.

  • No historical processing — only reacts to real-time executions.

I hope you all find it as useful as I do.
Edit: now I see that I cannot upload non-image files and that the only option is uploading via the previous Ninjatrader Ecosystem. I hope the sharing functionality is soon added to these forums. I will add here a link to the associated user app share page when available.
Edit#2: I have managed to enter the full code of the indicator and shared services in the forum so that they can be retrieved easily. See messages that follow as well as the Discord and Telegram entries in the Desktop SDK - Indicators section.
@ProyectoROI25

#region Using declarationsusing System;using System.Collections.Generic;using System.ComponentModel;using System.ComponentModel.DataAnnotations;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Input;using System.Windows.Media;using System.Xml.Serialization;using NinjaTrader.Cbi;using NinjaTrader.Gui;using NinjaTrader.Gui.Chart;using NinjaTrader.Gui.SuperDom;using NinjaTrader.Gui.Tools;using NinjaTrader.Data;using NinjaTrader.NinjaScript;using NinjaTrader.Core.FloatingPoint;using NinjaTrader.NinjaScript.DrawingTools;using System.Threading;using System.Net;using System.Windows.Media.Imaging;using System.Net.Mail;using System.Net.Mime;using System.Windows.Controls;#endregion

namespace NinjaTrader.NinjaScript.Indicators{public class DiscordTradesLight : Indicator{private Account a;private System.Windows.Controls.Button btnCurrentAll;private System.Windows.Controls.Button btnEmail_1;private System.Windows.Controls.Button btnPnl;private System.Windows.Controls.Grid   myGrid;private NinjaTrader.Gui.Tools.AccountSelector xAlselector;private bool handlers_added=false;private bool oneTime=true;private BitmapFrame outputFrame;private int counter = 0;

	// FIFO por instrumento
	private class OpenLot
	{
		public DateTime Time;
		public double   Price;
		public int      Qty;
		public string   OrderName;
	}
	private Dictionary<string, Queue<OpenLot>> longLots  = new Dictionary<string, Queue<OpenLot>>();
	private Dictionary<string, Queue<OpenLot>> shortLots = new Dictionary<string, Queue<OpenLot>>();

	protected override void OnStateChange()
	{
		if (State == State.SetDefaults)
		{
			Description					= @"DiscordTradesLight - Entradas/Salidas de operaciones";
			Name						= "DiscordTradesLight ROI";
			Calculate					= Calculate.OnEachTick;
			IsOverlay					= true;
			DisplayInDataBox			= true;
			DrawOnPricePanel			= true;
			DrawHorizontalGridLines		= true;
			DrawVerticalGridLines		= true;
			PaintPriceMarkers			= true;
			ScaleJustification			= NinjaTrader.Gui.Chart.ScaleJustification.Right;
			IsSuspendedWhileInactive	= false;

			StartTime					= 215400;
			EndTime						= 215700;

			DiscordShareserviceName		= "MyDiscord";
			TelegramShareserviceName	= "MyTelegram";

			EurUsdRate					= 1.14;
		}
		else if (State == State.Configure)
		{
			if (handlers_added)
			{
				a.ExecutionUpdate -= OnExecutionUpdate;
				handlers_added = false;
			}
			longLots.Clear();
			shortLots.Clear();
		}
		else if (State == State.DataLoaded)
		{
			if (ChartControl == null) return;
			ChartControl.Dispatcher.Invoke((() =>
			{
				xAlselector = Window.GetWindow(ChartControl.Parent)
					.FindFirst("ChartTraderControlAccountSelector")
					as NinjaTrader.Gui.Tools.AccountSelector;
				if (xAlselector != null) a = xAlselector.SelectedAccount;
			}));

			if (a != null && !handlers_added)
			{
				a.ExecutionUpdate += OnExecutionUpdate;
				handlers_added = true;
			}
		}
		else if (State == State.Historical)
		{
			ChartControl.Dispatcher.InvokeAsync((() =>
			{
				if (UserControlCollection.Contains(myGrid)) return;

				myGrid = new System.Windows.Controls.Grid
				{
					Name = "MyCustomGrid",
					HorizontalAlignment = HorizontalAlignment.Left,
					VerticalAlignment   = VerticalAlignment.Top,
				};
				for (int i = 0; i < 3; i++)
					myGrid.ColumnDefinitions.Add(new System.Windows.Controls.ColumnDefinition());

				btnCurrentAll = new System.Windows.Controls.Button
				{
					Name = "btnCurrentAll", Content = "CURRENT",
					Foreground = Brushes.Gainsboro, Background = Brushes.Green,
					ToolTip = "CURRENT: solo el activo de este chart. Envía el mensaje CON captura del gráfico y trae el chart al frente.\n" +
							  "ALL: todos los activos. Envía el mensaje SIN gráfico y sin traer ningún chart al frente.\n" +
							  "NONE: no envía ningún mensaje."
				};

				btnEmail_1 = new System.Windows.Controls.Button
				{
					Name = "btnEmail_1", Content = "Discord",
					Foreground = Brushes.Gainsboro, Background = Brushes.Green,
					ToolTip = "Discord: los mensajes se enviarán a la cuenta configurada en 'Discord Shareservice Name'.\n" +
							  "Telegram: los mensajes se enviarán a la cuenta configurada en 'Telegram Shareservice Name'.\n" +
							  "Off: no se envía ningún mensaje.\n\n" +
							  "(Ambos nombres se configuran en la ventana de propiedades del indicador, grupo 'Shareservices')"
				};

				btnPnl = new System.Windows.Controls.Button
				{
					Name = "btnPnl", Content = "Off PnL",
					Foreground = Brushes.Gainsboro, Background = Brushes.Red,
					ToolTip = "Habilitar/Deshabilitar envío del PnL de fin de día"
				};

				btnCurrentAll.Click += OnbtnCurrentAllClick;
				btnEmail_1.Click    += OnbtnEmail_1Click;
				btnPnl.Click        += OnbtnPnlClick;

				System.Windows.Controls.Grid.SetColumn(btnCurrentAll, 0);
				System.Windows.Controls.Grid.SetColumn(btnEmail_1,    1);
				System.Windows.Controls.Grid.SetColumn(btnPnl,        2);

				myGrid.Children.Add(btnCurrentAll);
				myGrid.Children.Add(btnEmail_1);
				myGrid.Children.Add(btnPnl);

				UserControlCollection.Add(myGrid);
			}));
		}
		else if (State == State.Terminated)
		{
			if (a != null && handlers_added)
			{
				a.ExecutionUpdate -= OnExecutionUpdate;
				handlers_added = false;
			}
			if (ChartControl == null) return;
			ChartControl.Dispatcher.InvokeAsync((() =>
			{
				if (myGrid == null) return;
				if (btnCurrentAll != null) { myGrid.Children.Remove(btnCurrentAll); btnCurrentAll.Click -= OnbtnCurrentAllClick; btnCurrentAll = null; }
				if (btnEmail_1    != null) { myGrid.Children.Remove(btnEmail_1);    btnEmail_1.Click    -= OnbtnEmail_1Click;    btnEmail_1    = null; }
				if (btnPnl        != null) { myGrid.Children.Remove(btnPnl);        btnPnl.Click        -= OnbtnPnlClick;        btnPnl        = null; }
				myGrid = null;
			}));
		}
	}

	
	private double GetCommissionTotal(Execution execution, int qty, Instrument instrument)
	{
		double commissionTotal = execution.Commission;

		if (commissionTotal > 0.0)
			return commissionTotal;

		bool isMicro = instrument.MasterInstrument.InstrumentType == InstrumentType.Future
					   && instrument.MasterInstrument.Name.StartsWith("M", StringComparison.OrdinalIgnoreCase);
		double perSide = isMicro ? 0.39 : 1.29;

		if (IsEurPairOrFuture(instrument.MasterInstrument.Name))
			perSide = perSide / EurUsdRate;

		return perSide * qty * 2; 
	}

	private bool IsEurPairOrFuture(string name)
	{
		return name == "FDAX" || name == "FGBL" || name == "FGBX" || name == "FBTP" ||
			   name == "FGBS" || name == "FGBM" || name == "FDXM" || name == "FESX" || name == "FSTX";
	}

	
	private void OnExecutionUpdate(object sender, ExecutionEventArgs e)
	{
		if (e.Execution == null || e.Execution.Order == null) return;

		string btnCurrentAll_text = "";
		string btnEmail_1_text    = "";
		ChartControl.Dispatcher.Invoke((() =>
		{
			btnCurrentAll_text = btnCurrentAll.Content.ToString();
			btnEmail_1_text    = btnEmail_1.Content.ToString();
		}));

		// ---- Modo NONE: no envía nada ----
		if (btnCurrentAll_text == "NONE") return;

		// ---- Modo OFF de canal: no procesa nada ----
		if (btnEmail_1_text == "Off") return;

		Order order = e.Execution.Order;

		// ---- Filtro por instrumento: solo CURRENT filtra ----
		if (btnCurrentAll_text == "CURRENT" && order.Instrument.FullName != Instrument.FullName) return;

		// ---- Focus y envío con gráfico: solo CURRENT ----
		bool withChart = (btnCurrentAll_text == "CURRENT");

		
		if (order.OrderState != OrderState.Filled && order.OrderState != OrderState.PartFilled) return;

		string   instrument = order.Instrument.FullName;
		int      qty        = e.Execution.Quantity;   // <-- cantidad de ESTA ejecución
		double   price      = e.Execution.Price;      // <-- precio de ESTA ejecución
		DateTime time       = order.Time;
		string   name       = order.Name;
		double   pointValue = order.Instrument.MasterInstrument.PointValue;
		double   tickSize   = order.Instrument.MasterInstrument.TickSize;
		string   masterName = order.Instrument.MasterInstrument.Name;
		double   commissionTotal = GetCommissionTotal(e.Execution, qty, order.Instrument);

		bool isBuy  = (order.OrderAction == OrderAction.Buy || order.OrderAction == OrderAction.BuyToCover);
		bool isSell = (order.OrderAction == OrderAction.Sell || order.OrderAction == OrderAction.SellShort);
		if (!isBuy && !isSell) return;

		if (!longLots.ContainsKey(instrument))  longLots[instrument]  = new Queue<OpenLot>();
		if (!shortLots.ContainsKey(instrument)) shortLots[instrument] = new Queue<OpenLot>();

		int remaining = qty;

		if (isBuy)
		{
			while (remaining > 0 && shortLots[instrument].Count > 0)
			{
				OpenLot lot = shortLots[instrument].Peek();
				int consume = Math.Min(remaining, lot.Qty);
				string msg ="";
				if (btnEmail_1_text == "Telegram")
					{
						msg = BuildCloseMessageT(instrument, "SHORT", consume,
											   lot.Price, lot.Time, price, time,
											   name, pointValue, tickSize, masterName, commissionTotal);
					}
				else msg = BuildCloseMessage(instrument, "SHORT", consume, lot.Price, lot.Time, price, time, name, pointValue, tickSize, masterName, commissionTotal);
				DispatchMessage(btnEmail_1_text, msg, withChart);
				lot.Qty   -= consume;
				remaining -= consume;
				if (lot.Qty == 0) shortLots[instrument].Dequeue();
			}
			if (remaining > 0)
			{
				longLots[instrument].Enqueue(new OpenLot { Time = time, Price = price, Qty = remaining, OrderName = name });
				string msg ="";
				if (btnEmail_1_text == "Telegram")
					{
					msg = BuildOpenMessageT(instrument, "BUY", "LONG", remaining, price, time, name);
					}
				else msg = BuildOpenMessage(instrument, "BUY", "LONG", remaining, price, time, name);
				DispatchMessage(btnEmail_1_text, msg, withChart);
			}
		}
		else
		{
			while (remaining > 0 && longLots[instrument].Count > 0)
			{
				OpenLot lot = longLots[instrument].Peek();
				int consume = Math.Min(remaining, lot.Qty);
				string msg ="";
				if (btnEmail_1_text == "Telegram")
					{
					msg = BuildCloseMessageT(instrument, "LONG", consume,
											   lot.Price, lot.Time, price, time,
											   name, pointValue, tickSize, masterName, commissionTotal);
					}
				else msg = BuildCloseMessage(instrument, "LONG", consume,
											   lot.Price, lot.Time, price, time,
											   name, pointValue, tickSize, masterName, commissionTotal);
				DispatchMessage(btnEmail_1_text, msg, withChart);
				lot.Qty   -= consume;
				remaining -= consume;
				if (lot.Qty == 0) longLots[instrument].Dequeue();
			}
			if (remaining > 0)
			{
				shortLots[instrument].Enqueue(new OpenLot { Time = time, Price = price, Qty = remaining, OrderName = name });
				string msg ="";
				if (btnEmail_1_text == "Telegram")
					{
					msg = BuildOpenMessageT(instrument, "SELL", "SHORT", remaining, price, time, name);
					}
				else msg = BuildOpenMessage(instrument, "SELL", "SHORT", remaining, price, time, name);
				DispatchMessage(btnEmail_1_text, msg, withChart);
			}
		}
	}


	private string BuildOpenMessage(string Symbol, string Action, string Direction,
									int Lots, double Openprice, DateTime OpenTime, string Typeaction)
	{
		string nl = "\n";
		string triggertypeO = " @ " + OpenTime.ToString("HH:mm:ss") + " " + Direction;
		return "**Position Open**" + triggertypeO +
			   "```" + nl +
			   PadDiscord("SYMBOL", 16) + PadDiscord("ACTION", 18) + PadDiscord("LOTS", 18) + nl +
			   PadDiscord(Symbol, 16) + PadDiscord(Action, 18) + PadDiscord(Lots.ToString(), 18) + nl + nl +
			   PadDiscord("OPEN DATE", 16) + PadDiscord("OPEN TIME", 18) + PadDiscord("OPEN PRICE", 18) + nl +
			   PadDiscord(OpenTime.ToString("yyyy-MM-dd"), 16)
			 + PadDiscord(OpenTime.ToString("HH:mm:ss"), 18)
			 + PadDiscord(Openprice.ToString(), 18) + nl +
			   "```";
	}
	
	private string BuildOpenMessageT(string Symbol, string Action, string Direction,
									int Lots, double Openprice, DateTime OpenTime, string Typeaction)
	{
		string nl = "\n";
		string triggertypeO = " @ " + OpenTime.ToString("HH:mm:ss") + " " + Direction;
		return "Position Open" + triggertypeO + nl +
			   nl +
			   PadDiscord("SYMBOL", 16) + PadDiscord("ACTION", 18) + PadDiscord("LOTS", 18) + nl +
			   PadDiscord(Symbol, 16) + PadDiscord(Action, 18) + PadDiscord(Lots.ToString(), 18) + nl + nl +
			   PadDiscord("OPEN DATE", 16) + PadDiscord("OPEN TIME", 18) + PadDiscord("OPEN PRICE", 18) + nl +
			   PadDiscord(OpenTime.ToString("yyyy-MM-dd"), 16)
			 + PadDiscord(OpenTime.ToString("HH:mm:ss"), 18)
			 + PadDiscord(Openprice.ToString(), 18);
	}

	private string BuildCloseMessage(string Symbol, string Direction, int Lots,
									 double EntryPrice, DateTime EntryTime,
									 double ExitPrice,  DateTime ExitTime,
									 string Typeaction, double pointValue, double tickSize,
									 string masterName, double commissionTotal)
	{
		string PnL, Points;
		bool isEur = IsEurPairOrFuture(masterName);
		string cur = isEur ? "€" : "$";

		if (Direction == "LONG")
		{
			double pnl = ((ExitPrice - EntryPrice) * Lots * pointValue) - commissionTotal;
			PnL    = cur + pnl.ToString("N2");
			Points = ((ExitPrice - EntryPrice) * Lots / tickSize).ToString("N0") + " ticks";
		}
		else 
		{
			double pnl = ((EntryPrice - ExitPrice) * Lots * pointValue) - commissionTotal;
			PnL    = cur + pnl.ToString("N2");
			Points = ((EntryPrice - ExitPrice) * Lots / tickSize).ToString("N0") + " ticks";
		}

		string triggertype = "Close @ " + ExitTime.ToString("HH:mm:ss") + " - Closing " + Direction;

		string nl = "\n";
		string closeAction = (Direction == "LONG") ? "SELL" : "BUY";

		return "**Position Closed**" + nl +
			   triggertype + nl + nl +
			   "```" + nl +
			   PadDiscord("SYMBOL", 16) + PadDiscord("ACTION", 18) + PadDiscord("LOTS", 18) + nl +
			   PadDiscord(Symbol, 16) + PadDiscord(closeAction, 18) + PadDiscord(Lots.ToString(), 18) + nl + nl +
			   PadDiscord("OPEN DATE", 16) + PadDiscord("OPEN TIME", 18) + PadDiscord("OPEN PRICE", 18) + nl +
			   PadDiscord(EntryTime.ToString("yyyy-MM-dd"), 16) + PadDiscord(EntryTime.ToString("HH:mm:ss"), 18) + PadDiscord(EntryPrice.ToString(), 18) + nl + nl +
			   PadDiscord("CLOSE DATE", 16) + PadDiscord("CLOSE TIME", 18) + PadDiscord("CLOSE PRICE", 18) + nl +
			   PadDiscord(ExitTime.ToString("yyyy-MM-dd"), 16) + PadDiscord(ExitTime.ToString("HH:mm:ss"), 18) + PadDiscord(ExitPrice.ToString(), 18) + nl + nl +
			   PadDiscord("PnL", 16) + PadDiscord("POINTS", 18) + PadDiscord("Commissions", 18) + nl +
			   PadDiscord(PnL, 16) + PadDiscord(Points, 18)  + PadDiscord(commissionTotal.ToString(), 18) + nl +
			   "```";
	}
	
	private string BuildCloseMessageT(string Symbol, string Direction, int Lots,
									 double EntryPrice, DateTime EntryTime,
									 double ExitPrice,  DateTime ExitTime,
									 string Typeaction, double pointValue, double tickSize,
									 string masterName, double commissionTotal)
	{
		string PnL, Points;
		bool isEur = IsEurPairOrFuture(masterName);
		string cur = isEur ? "€" : "$";

		if (Direction == "LONG")
		{
			double pnl = ((ExitPrice - EntryPrice) * Lots * pointValue) - commissionTotal;
			PnL    = cur + pnl.ToString("N2");
			Points = ((ExitPrice - EntryPrice) * Lots / tickSize).ToString("N0") + " ticks";
		}
		else 
		{
			double pnl = ((EntryPrice - ExitPrice) * Lots * pointValue) - commissionTotal;
			PnL    = cur + pnl.ToString("N2");
			Points = ((EntryPrice - ExitPrice) * Lots / tickSize).ToString("N0") + " ticks";
		}

		string triggertype = " @ " + ExitTime.ToString("HH:mm:ss") + " - Closing " + Direction;

		string nl = "\n";
		string closeAction = (Direction == "LONG") ? "SELL" : "BUY";

		return "Position Closed" + triggertype + nl + nl +
			   PadDiscord("SYMBOL", 16) + PadDiscord("ACTION", 18) + PadDiscord("LOTS", 18) + nl +
			   PadDiscord(Symbol, 16) + PadDiscord(closeAction, 18) + PadDiscord(Lots.ToString(), 18) + nl + nl +
			   PadDiscord("OPEN DATE", 16) + PadDiscord("OPEN TIME", 18) + PadDiscord("OPEN PRICE", 18) + nl +
			   PadDiscord(EntryTime.ToString("yyyy-MM-dd"), 16) + PadDiscord(EntryTime.ToString("HH:mm:ss"), 18) + PadDiscord(EntryPrice.ToString(), 18) + nl + nl +
			   PadDiscord("CLOSE DATE", 16) + PadDiscord("CLOSE TIME", 18) + PadDiscord("CLOSE PRICE", 18) + nl +
			   PadDiscord(ExitTime.ToString("yyyy-MM-dd"), 16) + PadDiscord(ExitTime.ToString("HH:mm:ss"), 18) + PadDiscord(ExitPrice.ToString(), 18) + nl + nl +
			   PadDiscord("PnL", 16) + PadDiscord("POINTS", 18) + PadDiscord("Commissions", 18) + nl +
			   PadDiscord(PnL, 16) + PadDiscord(Points, 18) + PadDiscord(commissionTotal.ToString(), 18);
	}


	private void DispatchMessage(string btnEmail_1_text, string message, bool withChart)
	{
		if (withChart)
		{
			// CURRENT: focus() + envío con captura del gráfico
			if (btnEmail_1_text == "Telegram")
			{
				focus();
				SendChart(TelegramShareserviceName, message);
			}
			else if (btnEmail_1_text == "Discord")
			{
				focus();
				SendChart(DiscordShareserviceName, message);
			}
		}
		else
		{
			// ALL: solo texto, sin focus ni gráfico
			if (btnEmail_1_text == "Telegram")
			{
				string telegramMsg = message.Replace("\\n", "\n");
				Share(TelegramShareserviceName, telegramMsg);
			}
			else if (btnEmail_1_text == "Discord")
			{
				Share(DiscordShareserviceName, message);
			}
		}
	}


	protected override void OnBarUpdate()
	{
		if (State == State.Historical || State != State.Realtime) return;

		if (ToTime(DateTime.Now) > StartTime && ToTime(DateTime.Now) <= EndTime)
		{
			string btnEmail_1_text = "";
			string btnPnl_text     = "";
			ChartControl.Dispatcher.InvokeAsync((Action)(() =>
			{
				btnEmail_1_text = btnEmail_1.Content.ToString();
				btnPnl_text     = btnPnl.Content.ToString();
			})).Wait();

			if (btnPnl_text != "On PnL") return;
			if (btnEmail_1_text == "Off") return;

			if (oneTime && btnPnl_text == "On PnL")
			{
				oneTime = false;
				try
				{
					double RealizedProfitLoss = a.GetAccountItem(AccountItem.RealizedProfitLoss, Currency.UsDollar).Value;
					double CashValue          = a.GetAccountItem(AccountItem.CashValue,          Currency.UsDollar).Value;
					string PnLper = string.Format("€{0:N2}", (CashValue / (CashValue - RealizedProfitLoss)) * 100 - 100);

					string messagePnL =
						"**Trading Account**\n" +
						"End of day message \n\n" +
						"```\n" +
						PadDiscord("DailyPnL", 16) + PadDiscord("DailyPnL(%)", 18) + "\n" +
						PadDiscord("$" + RealizedProfitLoss.ToString(), 16) + PadDiscord(PnLper, 18) + "\n\n" +
						PadDiscord("Account Value", 16) + "\n" +
						PadDiscord(CashValue.ToString(), 16) + "\n\n" +
						"```";

					string messageTPnL =
						"**Trading Account**\n" +
						"End of day message \n\n" +
						"```\n" +
						PadDiscord("𝐃𝐚𝐢𝐥𝐲𝐏𝐧𝐋", 16) + PadDiscord("𝐃𝐚𝐢𝐥𝐲𝐏𝐧𝐋(%)", 18) + "\n" +
						PadDiscord(RealizedProfitLoss.ToString(), 16) + PadDiscord(((CashValue / (CashValue - RealizedProfitLoss)) * 100 - 100).ToString(), 18) + "\n\n" +
						PadDiscord("𝐀𝐜𝐜𝐨𝐮𝐧𝐭 𝐕𝐚𝐥𝐮𝐞", 16) + "\n" +
						PadDiscord(CashValue.ToString(), 16) + "\n\n" +
						"```";

					if (btnEmail_1_text == "Discord")
						Share(DiscordShareserviceName, messagePnL);
					else if (btnEmail_1_text == "Telegram")
						Share(TelegramShareserviceName, messageTPnL);
				}
				catch (Exception ex)
				{
					Print(ex.Message);
				}
			}
		}
		else
		{
			oneTime = true;
		}
	}


	private void OnbtnCurrentAllClick(object sender, RoutedEventArgs rea)
	{
		var button = sender as System.Windows.Controls.Button;
		if (button == null) return;
		string c = button.Content.ToString();

		if (c == "CURRENT")
			button.Content = "ALL";
		else if (c == "ALL")
			button.Content = "NONE";
		else
			button.Content = "CURRENT";

		button.FontWeight = FontWeights.Bold;
		button.Foreground = Brushes.Gainsboro;
		button.Background = Brushes.Green;
	}

	private void OnbtnEmail_1Click(object sender, RoutedEventArgs rea)
	{
		var button = sender as System.Windows.Controls.Button;
		if (button == null) return;
		string c = button.Content.ToString();

		if (c == "Discord")
		{
			button.Content    = "Telegram";
			button.Background = Brushes.Green;
			button.FontWeight = FontWeights.Bold;
		}
		else if (c == "Telegram")
		{
			button.Content    = "Off";
			button.Background = Brushes.Red;
			button.FontWeight = FontWeights.Regular;
		}
		else
		{
			button.Content    = "Discord";
			button.Background = Brushes.Green;
			button.FontWeight = FontWeights.Bold;
		}
		button.Foreground = Brushes.Gainsboro;
	}

	private void OnbtnPnlClick(object sender, RoutedEventArgs rea)
	{
		var button = sender as System.Windows.Controls.Button;
		if (button == null) return;
		if (button.Content.ToString() == "Off PnL")
		{
			button.Content    = "On PnL";
			button.Background = Brushes.Green;
			button.FontWeight = FontWeights.Bold;
		}
		else
		{
			button.Content    = "Off PnL";
			button.Background = Brushes.Red;
			button.FontWeight = FontWeights.Regular;
		}
		button.Foreground = Brushes.Gainsboro;
	}

	private void SendChart(string Cuenta, string Body)
	{
		new Thread(new ThreadStart(delegate
		{
			ChartControl.Dispatcher.InvokeAsync(new Action(() =>
			{
				try
				{
					NinjaTrader.Gui.Chart.Chart chart = Window.GetWindow(ChartControl) as Chart;
					if (chart != null)
					{
						RenderTargetBitmap screenCapture = chart.GetScreenshot(ShareScreenshotType.Chart);
						outputFrame = BitmapFrame.Create(screenCapture);
						if (screenCapture != null)
						{
							counter++;
							string outPath = System.IO.Path.GetTempPath() + "\\"
										   + Instrument.FullName + Cuenta + counter + "image.png";
							PngBitmapEncoder png2 = new PngBitmapEncoder();
							png2.Frames.Add(outputFrame);
							using (var fileStream = new System.IO.FileStream(outPath, System.IO.FileMode.Create))
								png2.Save(fileStream);
							Share(Cuenta, Body, outPath);
						}
					}
				}
				catch (Exception ex) { Print("Error al enviar chart - " + ex); }
			}));
		})).Start();
	}

	void focus()
	{
		new Thread(new ThreadStart(delegate
		{
			ChartControl.Dispatcher.InvokeAsync((Action)(() =>
			{
				int myIndex = 0;
				Gui.Chart.Chart chartWindow = Chart.GetWindow(this.ChartControl.Parent) as Chart;
				if (chartWindow != null && ChartControl != null)
				{
					foreach (TabItem ti in chartWindow.MainTabControl.Items)
					{
						Gui.Chart.ChartTab tab = ti.Content as Gui.Chart.ChartTab;
						if (tab.ChartControl == ChartControl)
						{
							myIndex = chartWindow.MainTabControl.Items.IndexOf(ti);
							break;
						}
					}
				}
				chartWindow.MainTabControl.SelectedIndex = myIndex;
				Window.GetWindow(ChartControl).Activate();
			}));
		})).Start();
	}

	private string PadDiscord(string text, int width)
	{
		string safeText = text ?? "";
		int visualLength = 0;
		for (int i = 0; i < safeText.Length; i++)
		{
			visualLength++;
			if (char.IsSurrogatePair(safeText, i)) i++;
		}
		int spaces = width - visualLength;
		return safeText + (spaces > 0 ? new string(' ', spaces) : "");
	}

	#region Properties
	[NinjaScriptProperty]
	[Display(Name="StartTime", Description="Start time for EOD message triggering detection.  Format is Hours Minutes Seconds.  For 24h use 000000/235959.", Order=7, GroupName="Parameters")]
	public int StartTime { get; set; }

	[NinjaScriptProperty]
	[Display(Name="EndTime", Description="End time for EOD message triggering detection.  Format is Hours Minutes Seconds.  For 24h use 000000/235959.", Order=8, GroupName="Parameters")]
	public int EndTime { get; set; }

	[NinjaScriptProperty]
	[Display(Name="Discord Shareservice Name", Description="Nombre exacto de la cuenta configurada en ShareServices para Discord (ej: Discord5mins).", Order=9, GroupName="Shareservices")]
	public string DiscordShareserviceName { get; set; }

	[NinjaScriptProperty]
	[Display(Name="Telegram Shareservice Name", Description="Nombre exacto de la cuenta configurada en ShareServices para Telegram (ej: Telegram).", Order=10, GroupName="Shareservices")]
	public string TelegramShareserviceName { get; set; }

	[NinjaScriptProperty]
	[Display(Name="EUR/USD Rate", Description="Factor de conversión EUR/USD usado para expresar la comisión fallback en euros cuando el instrumento cotiza en € (por defecto 1.14). Solo se aplica si la cuenta no tiene template de comisiones.", Order=11, GroupName="Parameters")]
	public double EurUsdRate { get; set; }
	#endregion
}

}

#region NinjaScript generated code. Neither change nor remove.

namespace NinjaTrader.NinjaScript.Indicators{public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase{private DiscordTradesLight cacheDiscordTradesLight;public DiscordTradesLight DiscordTradesLight(int startTime, int endTime, string discordShareserviceName, string telegramShareserviceName, double eurUsdRate){return DiscordTradesLight(Input, startTime, endTime, discordShareserviceName, telegramShareserviceName, eurUsdRate);}

	public DiscordTradesLight DiscordTradesLight(ISeries<double> input, int startTime, int endTime, string discordShareserviceName, string telegramShareserviceName, double eurUsdRate)
	{
		if (cacheDiscordTradesLight != null)
			for (int idx = 0; idx < cacheDiscordTradesLight.Length; idx++)
				if (cacheDiscordTradesLight[idx] != null && cacheDiscordTradesLight[idx].StartTime == startTime && cacheDiscordTradesLight[idx].EndTime == endTime && cacheDiscordTradesLight[idx].DiscordShareserviceName == discordShareserviceName && cacheDiscordTradesLight[idx].TelegramShareserviceName == telegramShareserviceName && cacheDiscordTradesLight[idx].EurUsdRate == eurUsdRate && cacheDiscordTradesLight[idx].EqualsInput(input))
					return cacheDiscordTradesLight[idx];
		return CacheIndicator<DiscordTradesLight>(new DiscordTradesLight(){ StartTime = startTime, EndTime = endTime, DiscordShareserviceName = discordShareserviceName, TelegramShareserviceName = telegramShareserviceName, EurUsdRate = eurUsdRate }, input, ref cacheDiscordTradesLight);
	}
}

}

namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns{public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase{public Indicators.DiscordTradesLight DiscordTradesLight(int startTime, int endTime, string discordShareserviceName, string telegramShareserviceName, double eurUsdRate){return indicator.DiscordTradesLight(Input, startTime, endTime, discordShareserviceName, telegramShareserviceName, eurUsdRate);}

	public Indicators.DiscordTradesLight DiscordTradesLight(ISeries<double> input , int startTime, int endTime, string discordShareserviceName, string telegramShareserviceName, double eurUsdRate)
	{
		return indicator.DiscordTradesLight(input, startTime, endTime, discordShareserviceName, telegramShareserviceName, eurUsdRate);
	}
}

}

namespace NinjaTrader.NinjaScript.Strategies{public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase{public Indicators.DiscordTradesLight DiscordTradesLight(int startTime, int endTime, string discordShareserviceName, string telegramShareserviceName, double eurUsdRate){return indicator.DiscordTradesLight(Input, startTime, endTime, discordShareserviceName, telegramShareserviceName, eurUsdRate);}

public Indicators.DiscordTradesLight DiscordTradesLight(ISeries<double> input , int startTime, int endTime, string discordShareserviceName, string telegramShareserviceName, double eurUsdRate)
	{
		return indicator.DiscordTradesLight(input, startTime, endTime, discordShareserviceName, telegramShareserviceName, eurUsdRate);
	}
}

}

#endregion

How to install (quick start for beginners)

  1. In NinjaTrader 8, open New → NinjaScript Editor.

  2. In the left panel, right-click the Indicators folder → New Indicator.

  3. Name it DiscordTradesLight (or any name you like) and click OK. A template file opens.

  4. Select everything in the file (Ctrl+A) and delete it.

  5. Paste the full code from the code block above (Ctrl+V). Make sure you include the entire file — the #region NinjaScript generated code block at the bottom is part of it and must not be omitted.

  6. Press F5 to compile. A green check in the toolbar means success. If any error appears, it is almost always because a piece of the code was cut off during copy — re-select and paste again.

  7. On any chart: right-click → Indicators → select DiscordTradesLight → Add.

  8. In the indicator’s properties, enter the exact names of your Discord and Telegram ShareServices (as configured under Tools → Options → Share Services). If you leave the defaults, no messages are sent.

  9. The three control buttons appear in the top-left corner of the chart. Click them to cycle their state.

Note on renaming: if you rename the file, also update these two lines in the code — public class DiscordTradesLight : Indicator and Name = "DiscordTradesLight";. Everything else can stay as-is.