What makes this version "Pro":
Dynamic Direction Detection: It automatically knows if you are planning a Long or Short based on where you drag the TP and SL lines relative to the Entry. If you drag them to the wrong sides (e.g., TP and SL below entry), it warns you.
Auto-Position Sizing: You input your risk percentage (e.g., 1.0 for 1%). It calculates the exact lot size/units you need to trade based on the exact pip distance of your Stop Loss.
Native HUD Dashboard: Replaces standard floating text with a styled UI panel (dark mode background, rounded borders) that stays perfectly anchored.
Code: Select all
using System;
using cAlgo.API;
namespace cAlgo
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class ZeroPnLVisualizerPro : Indicator
{
[Parameter("Risk Percentage (%)", Group = "Risk Management", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1)]
public double RiskPercentage { get; set; }
[Parameter("Zone Opacity (0-255)", Group = "Visuals", DefaultValue = 50)]
public int ZoneOpacity { get; set; }
// Object references
private ChartHorizontalLine _entryLine;
private ChartHorizontalLine _slLine;
private ChartHorizontalLine _tpLine;
// UI Dashboard references
private Border _dashboard;
private TextBlock _txtStatus;
private TextBlock _txtRR;
private TextBlock _txtPips;
private TextBlock _txtVolume;
protected override void Initialize()
{
double ask = Symbol.Ask;
double defaultDistance = Symbol.PipSize * 20;
// 1. Draw interactive lines
_entryLine = Chart.DrawHorizontalLine("ZeroPnL_Entry", ask, Color.Gray, 2, LineStyle.Solid);
_entryLine.IsInteractive = true;
_slLine = Chart.DrawHorizontalLine("ZeroPnL_SL", ask - defaultDistance, Color.Crimson, 2, LineStyle.Solid);
_slLine.IsInteractive = true;
_tpLine = Chart.DrawHorizontalLine("ZeroPnL_TP", ask + defaultDistance, Color.MediumSeaGreen, 2, LineStyle.Solid);
_tpLine.IsInteractive = true;
// 2. Setup Native UI Dashboard
InitializeDashboard();
// 3. Subscribe to events
Chart.ObjectUpdated += OnChartObjectUpdated;
UpdateVisuals();
}
public override void Calculate(int index)
{
if (IsLastBar)
UpdateVisuals();
}
private void OnChartObjectUpdated(ChartObjectUpdatedEventArgs args)
{
if (args.ChartObject.Name.StartsWith("ZeroPnL_"))
UpdateVisuals();
}
private void InitializeDashboard()
{
var stackPanel = new StackPanel { Orientation = Orientation.Vertical, Margin = new Thickness(10) };
_txtStatus = new TextBlock { FontSize = 14, FontWeight = FontWeight.Bold, Margin = new Thickness(0, 0, 0, 5) };
_txtRR = new TextBlock { FontSize = 12, Foreground = Color.LightGray, Margin = new Thickness(0, 0, 0, 2) };
_txtPips = new TextBlock { FontSize = 12, Foreground = Color.LightGray, Margin = new Thickness(0, 0, 0, 2) };
_txtVolume = new TextBlock { FontSize = 13, FontWeight = FontWeight.ExtraBold, Foreground = Color.Gold, Margin = new Thickness(0, 5, 0, 0) };
stackPanel.AddChild(_txtStatus);
stackPanel.AddChild(_txtRR);
stackPanel.AddChild(_txtPips);
stackPanel.AddChild(_txtVolume);
_dashboard = new Border
{
BackgroundColor = Color.FromArgb(220, 15, 15, 15),
BorderColor = Color.FromArgb(100, 128, 128, 128),
BorderThickness = new Thickness(1),
CornerRadius = 5,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom,
Margin = new Thickness(20),
Child = stackPanel
};
Chart.AddControl(_dashboard);
}
private void UpdateVisuals()
{
if (_entryLine == null || _slLine == null || _tpLine == null)
return;
double entry = _entryLine.Y;
double sl = _slLine.Y;
double tp = _tpLine.Y;
// Determine setup direction
bool isLong = tp > entry && sl < entry;
bool isShort = tp < entry && sl > entry;
Color riskColor = Color.Transparent;
Color rewardColor = Color.Transparent;
// Process valid setups
if (isLong || isShort)
{
double risk = Math.Abs(entry - sl);
double reward = Math.Abs(tp - entry);
double slPips = Math.Round(risk / Symbol.PipSize, 1);
double tpPips = Math.Round(reward / Symbol.PipSize, 1);
double rrRatio = risk > 0 ? Math.Round(reward / risk, 2) : 0;
// Position Sizing Math
double accountRiskAmount = Account.Balance * (RiskPercentage / 100.0);
// Volume = Risk / (SL Pips * Pip Value per Unit)
double exactUnits = 0;
if (slPips > 0 && Symbol.PipValue > 0)
{
exactUnits = accountRiskAmount / (slPips * Symbol.PipValue);
}
// Normalize to broker limits
double safeVolume = Symbol.NormalizeVolumeInUnits(exactUnits, RoundingMode.Down);
double safeLots = Symbol.VolumeInUnitsToQuantity(safeVolume);
// Update UI Text
_txtStatus.Text = isLong ? "LONG SETUP" : "SHORT SETUP";
_txtStatus.Foreground = isLong ? Color.DeepSkyBlue : Color.Tomato;
_txtRR.Text = $"R:R Ratio = 1 : {rrRatio:F2}";
_txtPips.Text = $"SL: {slPips} pips | TP: {tpPips} pips";
_txtVolume.Text = $"EXECUTE: {safeLots} Lots ({safeVolume} Units)";
// Update Colors
riskColor = Color.FromArgb(ZoneOpacity, Color.Crimson);
rewardColor = Color.FromArgb(ZoneOpacity, Color.MediumSeaGreen);
}
else
{
// Invalid state (lines crossed incorrectly)
_txtStatus.Text = "INVALID SETUP";
_txtStatus.Foreground = Color.DarkGray;
_txtRR.Text = "Check line placement";
_txtPips.Text = "-";
_txtVolume.Text = "-";
}
// Draw Background Rectangles (Stretch infinitely forward)
int startIndex = 0;
int endIndex = Bars.Count + 1000;
var riskBox = Chart.DrawRectangle("ZeroPnL_RiskBox", startIndex, entry, endIndex, sl, riskColor);
riskBox.IsFilled = true;
riskBox.IsInteractive = false;
var rewardBox = Chart.DrawRectangle("ZeroPnL_RewardBox", startIndex, entry, endIndex, tp, rewardColor);
rewardBox.IsFilled = true;
rewardBox.IsInteractive = false;
}
}
}