Category Other  Published on 24/08/2021

VFX Margin Visualizer v1.4

Description

VFX Margin Visualizer allows you to visualize your actual margin level on the on-chart scale together with 100% and Stop-Out level lines and highlighted areas.
You can also simulate effect of a custom leverage set via the Leverage parameter or see the impact of a increased/reduced account balance.


Version history:
v1.0    - initial release
v1.1    - minor enhancements
v1.2    - fixed error causing deletion of all chart objects
v1.3    - minor visual enhancements
v1.4    - balance change input added


//  Margin Visualizer v1.4 by VFX
//  https://ctrader.com/algos/indicators/show/2629

//  Version history:
//  v1.0    - initial release
//  v1.1    - minor enhancements
//  v1.2    - fixed error causing deletion of all chart objects
//  v1.3    - minor visual enhancements
//  v1.4    - balance change input added


using System;
using cAlgo.API;
using cAlgo.API.Internals;
using System.Linq;

namespace cAlgo.Indicators
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class MarginVisualizer : Indicator
    {
        [Parameter("Leverage (0=real)", Group = "General", DefaultValue = 0, MinValue = 0, MaxValue = 1000, Step = 50)]
        public int parLeverage { get; set; }

        [Parameter("Balance +/- (0=real)", Group = "General", DefaultValue = 0, Step = 100)]
        public int parBalanceChange { get; set; }

        [Parameter("Stop-Out Level", Group = "General", DefaultValue = 50, MaxValue = 100, MinValue = 10, Step = 5)]
        public int parStopOutLevel { get; set; }

        [Parameter("Show Warning Line (100%)", Group = "General", DefaultValue = false)]
        public bool parShowML100 { get; set; }

        [Parameter("Show Warning Area", Group = "General", DefaultValue = true)]
        public bool parShowWarningArea { get; set; }

        [Parameter("Show Stop-Out Line", Group = "General", DefaultValue = true)]
        public bool parShowMLStop { get; set; }

        [Parameter("Show Stop-Out Area", Group = "General", DefaultValue = true)]
        public bool parShowStopArea { get; set; }

        [Parameter("Averaging Factor", Group = "General", DefaultValue = 0.5, MinValue = 0, MaxValue = 0.99)]
        public double parAvgFactor { get; set; }

        [Parameter("Show Scale", Group = "Scale", DefaultValue = true)]
        public bool parShowScale { get; set; }

        [Parameter("Show Stop-Out Label", Group = "Scale", DefaultValue = false)]
        public bool parShowMLStopLabel { get; set; }

        [Parameter("Scale Alignment", Group = "Scale", DefaultValue = AlignmentType.Right)]
        public AlignmentType parAlignment { get; set; }

        [Parameter("Scale Indent", Group = "Scale", DefaultValue = 0, MinValue = 0, MaxValue = 500, Step = 5)]
        public int parHindent { get; set; }

        [Parameter("Scale Step Factor", Group = "Scale", DefaultValue = 10, MinValue = 1, MaxValue = 100, Step = 1)]
        public int parScaleStepFactor { get; set; }

        [Parameter("Scale Length Factor", Group = "Scale", DefaultValue = 2, MinValue = 1, MaxValue = 10, Step = 1)]
        public int parScaleLengthFactor { get; set; }

        [Parameter("Show Leverage Needed", Group = "Scale", DefaultValue = true)]
        public bool parShowLevNeeded { get; set; }

        [Parameter("Line Thickness", Group = "Format", DefaultValue = 1, MinValue = 1)]
        public int parThickness { get; set; }

        [Parameter("Scale Color", Group = "Format", DefaultValue = "DimGray")]
        public string parScaleColor { get; set; }

        [Parameter("Warning Color", Group = "Format", DefaultValue = "Pink")]
        public string parWarningColor { get; set; }

        [Parameter("Stop-out Color", Group = "Format", DefaultValue = "DarkRed")]
        public string parStopOutColor { get; set; }

        [Parameter("Areas Opacity", Group = "Format", DefaultValue = 40, MinValue = 10, MaxValue = 100, Step = 10)]
        public int parAreasOpacity { get; set; }

        [Parameter("Warning Area Gradient Steps", Group = "Format", DefaultValue = 20, MinValue = 0, MaxValue = 50, Step = 5)]
        public int parGradientSteps { get; set; }


        private Functions Fn = new Functions();

        // Global variables
        private double avgPrice, avgEquity, mlStopPrice, ml100Price;
        private double currNetProfit, currentSymbolVolume;
        private Color clr100, clrStop;

        protected override void Initialize()
        {
            clr100 = Color.FromArgb(128, Color.FromName(parWarningColor));
            clrStop = Color.FromArgb(128, Color.FromName(parStopOutColor));
            avgPrice = Symbol.Bid;
            avgEquity = Account.Equity;

            Positions.Opened += OnPositionsOpened;
            Positions.Closed += OnPositionsClosed;
            Positions.Modified += OnPositionsModified;
            Chart.ScrollChanged += OnChartScrollChanged;
            Chart.SizeChanged += OnChartSizeChanged;
            Chart.ZoomChanged += OnChartZoomChanged;

            UpdatePositionsData();
            DrawMarginLevels();
            Timer.Start(1);
        }

        protected override void OnTimer()
        {
            DrawMarginLevels();
        }

        void UpdatePositionsData()
        {
            currentSymbolVolume = 0;
            var positions = Positions.Where(p => p.SymbolName == Symbol.Name).ToList();
            if (positions.Count > 0)
            {
                currentSymbolVolume = positions.Sum(t => t.VolumeInUnits * (t.TradeType == TradeType.Buy ? 1 : -1));
                currNetProfit = positions.Sum(t => t.NetProfit);
            }
        }

        void DrawMarginLevels()
        {
            double accCurrRate = Symbol.PipSize / Symbol.PipValue;

            if (currentSymbolVolume != 0)
            {
                double marginUsed = Account.Margin * (parLeverage == 0 ? 1 : Math.Min(Account.PreciseLeverage, Symbol.DynamicLeverage[0].Leverage) / parLeverage);
                avgPrice = parAvgFactor * avgPrice + (1.0 - parAvgFactor) * (currentSymbolVolume > 0 ? Symbol.Bid : Symbol.Ask);
                avgEquity = parAvgFactor * avgEquity + (1.0 - parAvgFactor) * Account.Equity;
                ml100Price = avgPrice - (avgEquity - 1.0 * marginUsed) / currentSymbolVolume * accCurrRate;
                mlStopPrice = avgPrice - (avgEquity - parStopOutLevel / 100.0 * marginUsed) / currentSymbolVolume * accCurrRate;

                if (parShowML100)
                    Chart.DrawHorizontalLine("ML100", ml100Price, clr100, parThickness, LineStyle.Solid);

                if (parShowMLStop)
                    Chart.DrawHorizontalLine("MLStop", mlStopPrice, clrStop, parThickness, LineStyle.Solid);

                if (parShowWarningArea)
                {
                    double priceStep = (ml100Price - mlStopPrice) / (parGradientSteps + 1);
                    for (int i = 0; i <= parGradientSteps; i++)
                    {
                        ChartRectangle warningArea = Chart.DrawRectangle("ML100Area" + i, Bars.OpenTimes[0], mlStopPrice + i * priceStep, Bars.OpenTimes[Chart.LastVisibleBarIndex].AddYears(10), mlStopPrice + (i + 1) * priceStep, Color.FromArgb((int)((double)parAreasOpacity * (1.0 - 0.9 * i / (double)(parGradientSteps + 1))), parWarningColor));
                        warningArea.IsFilled = true;
                        warningArea.Thickness = 0;
                        warningArea.ZIndex = -1000;
                    }
                }

                if (parShowStopArea)
                {
                    ChartRectangle stopArea = Chart.DrawRectangle("MLStopArea", Bars.OpenTimes[0], mlStopPrice, Bars.OpenTimes[Chart.LastVisibleBarIndex].AddYears(10), avgPrice - avgEquity / currentSymbolVolume * accCurrRate, Color.FromArgb(parAreasOpacity, clrStop));
                    stopArea.IsFilled = true;
                    stopArea.Thickness = 0;
                    stopArea.ZIndex = -1000;
                }

                int positionIndex = Chart.FirstVisibleBarIndex + (parAlignment == AlignmentType.Right ? Chart.MaxVisibleBars - parHindent - 1 : parHindent + 1);
                if (parShowScale)
                {
                    double scale100Size = avgPrice / (parLeverage == 0 ? Math.Min(Account.PreciseLeverage, Symbol.DynamicLeverage[0].Leverage) : parLeverage);
//                    int scaleStep = Fn.ToSignificantStep((Chart.TopY - Chart.BottomY) / parScaleStepFactor / scale100Size * 100);
                    int scaleStep = Fn.ToSignificantStep((Chart.TopY - Chart.BottomY) / parScaleStepFactor / scale100Size * 100);
                    int scaleMax = 100 + scaleStep * parScaleStepFactor * parScaleLengthFactor;

                    for (int scaleLevel = 100; scaleLevel <= scaleMax; scaleLevel += scaleStep)
                        DrawScaleStep(positionIndex, scaleLevel, marginUsed);

//                    for (int scaleLevel = 100 - scaleStep; scaleLevel > parStopOutLevel + scaleStep; scaleLevel -= scaleStep)
//                        DrawScaleStep(positionIndex, scaleLevel, marginUsed);

                    DrawScaleStep(positionIndex, 100, marginUsed, parShowML100);
                    if (parShowMLStopLabel)
                        DrawScaleStep(positionIndex, parStopOutLevel, marginUsed, parShowMLStop || parShowStopArea || parShowWarningArea);
                }

                string stepSignRight = parAlignment == AlignmentType.Right ? new string((char)160, 4) : string.Empty;
                string stepSignLeft = parAlignment == AlignmentType.Left ? string.Empty : new string((char)160, 4);
                if (parShowLevNeeded || parLeverage > 0)
                {
                    ChartText text = Chart.DrawText("MLUSED", (parLeverage > 0 ? stepSignLeft + "TEST Leverage: 1:" + parLeverage.ToString() : "Leverage Needed: 1:" + Fn.ToRoundedString((double)(Math.Min(Account.PreciseLeverage, Symbol.DynamicLeverage[0].Leverage) * 100.0 / Account.MarginLevel), 1)) + stepSignRight, positionIndex, mlStopPrice, parLeverage > 0 ? Color.Red : parScaleColor);
                    text.HorizontalAlignment = parAlignment == AlignmentType.Right ? HorizontalAlignment.Left : HorizontalAlignment.Right;
                    text.VerticalAlignment = VerticalAlignment.Bottom;
                }
                if (parBalanceChange != 0)
                {
                    ChartText text = Chart.DrawText("BALANCEBIAS", stepSignLeft + "Acc.Balance " + (parBalanceChange > 0 ? "+" : "") + parBalanceChange.ToString() + stepSignRight, positionIndex, mlStopPrice, Color.Red);
                    text.HorizontalAlignment = parAlignment == AlignmentType.Right ? HorizontalAlignment.Left : HorizontalAlignment.Right;
                    text.VerticalAlignment = VerticalAlignment.Top;
                }
            }
        }

        void DrawScaleStep(int positionIndex, int scaleLevel, double marginUsed, bool isShifted = false)
        {
            string stepSignRight = parAlignment == AlignmentType.Right ? (isShifted ? new string((char)160, 4) : " —") : string.Empty;
            string stepSignLeft = parAlignment == AlignmentType.Left ? (isShifted ? new string((char)160, 4) : "— ") : string.Empty;
            double accCurrRate = Symbol.PipSize / Symbol.PipValue;
            double stepPrice = avgPrice - (avgEquity - (double)scaleLevel * marginUsed / 100.0) / currentSymbolVolume * accCurrRate;
            ChartText text = Chart.DrawText("MLScale" + scaleLevel.ToString("F0"), stepSignLeft + Fn.ToRoundedString(scaleLevel, 0) + "%" + stepSignRight, positionIndex, stepPrice, parScaleColor);
            text.HorizontalAlignment = parAlignment == AlignmentType.Right ? HorizontalAlignment.Left : HorizontalAlignment.Right;
            text.VerticalAlignment = isShifted ? VerticalAlignment.Top : VerticalAlignment.Center;
        }

        void OnChartZoomChanged(ChartZoomEventArgs obj)
        {
            RemoveObjects("ML");
            DrawMarginLevels();
        }

        void OnChartSizeChanged(ChartSizeEventArgs obj)
        {
            RemoveObjects("ML");
            DrawMarginLevels();
        }

        void OnChartScrollChanged(ChartScrollEventArgs obj)
        {
            RemoveObjects("ML");
            DrawMarginLevels();
        }

        void OnPositionsOpened(PositionOpenedEventArgs obj)
        {
            UpdatePositionsData();
            RemoveObjects("ML");
            DrawMarginLevels();
        }

        void OnPositionsClosed(PositionClosedEventArgs obj)
        {
            UpdatePositionsData();
            RemoveObjects("ML");
            DrawMarginLevels();
        }

        void OnPositionsModified(PositionModifiedEventArgs obj)
        {
            UpdatePositionsData();
            RemoveObjects("ML");
            DrawMarginLevels();
        }

        private void RemoveObjects(string startsWith)
        {
            var cobjects = Chart.Objects.Where(o => o.Name.StartsWith(startsWith)).ToList();
            foreach (var cobj in cobjects)
                Chart.RemoveObject(cobj.Name);
        }

        public override void Calculate(int index)
        {
        }

        public enum AlignmentType
        {
            Left,
            Right
        }

        public class Functions
        {
            public int ToSignificantStep(double num)
            {
                double step = Math.Abs(num);

                if (num == 0)
                    return 1;

                int numorder = (int)Math.Pow(10, (int)Math.Floor(Math.Log10(step)));
                step /= numorder;

                int sigstep = 10;
                if (step < 1.5)
                    sigstep = 1;
                else if (step < 3)
                    sigstep = 2;
                else if (step < 7)
                    sigstep = 5;

                return sigstep * numorder * Math.Sign(num);
            }

            public string ToRoundedString(double num, int type, bool fix = false)
            {
                if (fix && (Math.Abs(num) < 1.0))
                    return num.ToString("#,##0." + new string('0', type));
                else
                {
                    if (Math.Abs(num) < 1E-05)
                        num = 0;

                    int shift = 0;

                    if ((type != 0) && (num != 0))
                        shift = (int)Math.Floor(Math.Log10(Math.Abs(num))) + ((Math.Abs(num) < 1) ? 1 : 0);

                    if (type - shift > 0)
                        return num.ToString("#,##0." + new string('0', type - shift));
                    else
                        return num.ToString("#,##0");
                }
            }
        }
    }
}


vitofx's avatar
vitofx

Joined on 08.03.2021

  • Distribution: Free
  • Language: C#
  • Trading platform: cTrader Automate
  • File name: VFX_Margin_Visualizer_v1.4.algo
  • Rating: 5
  • Installs: 1528
  • Modified: 13/10/2021 09:54
Comments
Log in to add a comment.
TH
The_Scalper · 1 year ago

Hello VitoFX. 

Such a cool indicator. Well done Mate. Very usefull. 

I wanted to ask though if you have tested it on V4.5.1 ? 

Do you think there is a chance you might consider adding in " what if scenarios " ? 

Thanks again for this. Nice one. 

 

YevgeniiyKonstantin's avatar
YevgeniiyKonstantin · 2 years ago

Не работает на Индексах!)

MA
Maury080FX · 3 years ago

I would suggest inserting the possibility of carrying out a simulation through input of a virtual order( both market and pending). would be useful for doing analysis. i.e. thank you for this amazing indicator.

WA
Wazzup · 3 years ago

Thank you very much !

I really needed this !

You saved me a lot of time with this great indicator.

AS
Astroke · 3 years ago

Works perfectly thanks!

vitofx's avatar
vitofx · 3 years ago

Hi Astroke - thank you! Download it again, it should be fixed now

AS
Astroke · 3 years ago

Hi, wow nice work!

First issues i can't set 100% for Stop out lvl and my broker apply this setting. In any case very good idea.

Regards.