How in ctrader make indicator position right to price axis

Created at 01 Dec 2024, 10:30
ED

EDG777

Joined 05.11.2024

How in ctrader make indicator position right to price axis
01 Dec 2024, 10:30


Greetz!
How in cTrader make the indicator position right to price axis, and not to time separator, maximum I could figure out is to do DateTime endTime = lastBarTime.AddMinutes(20);


but if I zoom in or out, how to make it adapt to the price axis? I'm using Chart.DrawRectangle, can't find any method, to stick it to price ;(


@EDG777
Replies

firemyst
01 Dec 2024, 14:30 ( Updated at: 02 Dec 2024, 06:51 )

On every bar, try redrawing the indicator at the “LastVisibleBarIndex”.

Documented here:

https://help.ctrader.com/ctrader-algo/references/Chart/Chart/#lastvisiblebarindex

 


@firemyst

EDG777
02 Dec 2024, 07:00 ( Updated at: 02 Dec 2024, 07:01 )

not working ;\


@EDG777

PanagiotisCharalampous
02 Dec 2024, 07:52

RE: How in ctrader make indicator position right to price axis

EDG777 said: 

not working ;\

Hi there,

Please share your indicator code so that we can help you further.

Best regards,

Panagiotis


@PanagiotisCharalampous

EDG777
02 Dec 2024, 10:51 ( Updated at: 02 Dec 2024, 14:15 )

Basicly I'm trying to copy this indicator:
https://www.tradingview.com/support/solutions/43000703076-visible-range-volume-profile/

using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Indicators
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class EnhancedVolumeProfile : Indicator
    {
        [Parameter("Number of Rows", DefaultValue = 24, MinValue = 10, MaxValue = 100)]
        public int RowSize { get; set; }

        [Parameter("Width (Minutes)", DefaultValue = 30, MinValue = 5, MaxValue = 200)]
        public int WidthInMinutes { get; set; }

        [Parameter("Lookback Bars", DefaultValue = 500, MinValue = 10, MaxValue = 5000)]
        public int LookbackBars { get; set; }

        [Parameter("Zoom Scale Factor", DefaultValue = 1.0, MinValue = 0.1, MaxValue = 5.0)]
        public double ZoomScaleFactor { get; set; }

        private Color upColor;
        private Color downColor;
        private Color borderColor;
        private Dictionary<double, VolumeData> volumeProfile;

        protected override void Initialize()
        {
            upColor = Color.FromArgb(120, 0, 180, 180);
            downColor = Color.FromArgb(120, 220, 38, 115);
            borderColor = Color.FromArgb(180, 50, 50, 50);
            volumeProfile = new Dictionary<double, VolumeData>();
        }

        public override void Calculate(int index)
        {
            // Only process if we're at the last visible bar
            if (index != Chart.LastVisibleBarIndex)
                return;

            // Clear previous drawings on each calculation
            Chart.Objects.Where(obj => obj.Name.StartsWith("VP_")).ToList().ForEach(obj => Chart.RemoveObject(obj.Name));

            int startIndex = Math.Max(0, index - LookbackBars);

            // Calculate the time span between bars to determine zoom level
            TimeSpan barTimeSpan = index > 0
                ? MarketSeries.OpenTime[index] - MarketSeries.OpenTime[index - 1]
                : TimeSpan.FromMinutes(1);

            // Adjust the offset based on chart timeframe and zoom
            int baseOffset = (int)(180 * ZoomScaleFactor); // Base offset in seconds
            double timeframeAdjustment = barTimeSpan.TotalSeconds / 60.0; // Adjustment based on timeframe
            int adjustedOffset = (int)(baseOffset * timeframeAdjustment);

            double priceHigh = double.MinValue;
            double priceLow = double.MaxValue;

            for (int i = startIndex; i <= index; i++)
            {
                if (MarketSeries.High[i] > priceHigh)
                    priceHigh = MarketSeries.High[i];
                if (MarketSeries.Low[i] < priceLow)
                    priceLow = MarketSeries.Low[i];
            }

            double priceRange = priceHigh - priceLow;
            double rowHeight = Math.Max(Symbol.TickSize, Math.Round(priceRange / RowSize / Symbol.TickSize) * Symbol.TickSize);

            volumeProfile.Clear();

            // Calculate volume profile
            for (int i = startIndex; i <= index; i++)
            {
                double price = Math.Round(MarketSeries.Close[i] / rowHeight) * rowHeight;
                bool isUpBar = MarketSeries.Close[i] >= MarketSeries.Open[i];
                double volume = MarketSeries.TickVolume[i];

                if (!volumeProfile.ContainsKey(price))
                    volumeProfile[price] = new VolumeData();

                if (isUpBar)
                    volumeProfile[price].UpVolume += volume;
                else
                    volumeProfile[price].DownVolume += volume;
            }

            double maxVolume = volumeProfile.Values.Max(v => v.UpVolume + v.DownVolume);
            DateTime lastBarTime = MarketSeries.OpenTime[Chart.LastVisibleBarIndex];

            // Draw volume profile with zoom-adjusted positioning
            foreach (var kvp in volumeProfile.OrderByDescending(x => x.Key))
            {
                double price = kvp.Key;
                var data = kvp.Value;

                double totalVolRatio = (data.UpVolume + data.DownVolume) / maxVolume;
                double upRatio = data.UpVolume / (data.UpVolume + data.DownVolume);
                double downRatio = 1 - upRatio;

                double totalWidth = 10 * totalVolRatio;
                double upWidth = totalWidth * upRatio;
                double downWidth = totalWidth * downRatio;

                DateTime endTime = lastBarTime.AddMinutes(20);

                // Draw up volume
                if (data.UpVolume > 0)
                {
                    var rect = Chart.DrawRectangle(
                        $"VP_Up_{price}",
                        endTime.AddMinutes(-upWidth),
                        price,
                        endTime,
                        price + rowHeight,
                        upColor);
                    rect.IsFilled = true;

                    var border = Chart.DrawRectangle(
                        $"VP_Up_Border_{price}",
                        endTime.AddMinutes(-upWidth),
                        price,
                        endTime,
                        price + rowHeight,
                        borderColor);
                    border.IsFilled = false;
                    border.Thickness = 1;
                }

                // Draw down volume
                if (data.DownVolume > 0)
                {
                    var rect = Chart.DrawRectangle(
                        $"VP_Down_{price}",
                        endTime.AddMinutes(-(upWidth + downWidth)),
                        price,
                        endTime.AddMinutes(-upWidth),
                        price + rowHeight,
                        downColor);
                    rect.IsFilled = true;

                    var border = Chart.DrawRectangle(
                        $"VP_Down_Border_{price}",
                        endTime.AddMinutes(-(upWidth + downWidth)),
                        price,
                        endTime.AddMinutes(-upWidth),
                        price + rowHeight,
                        borderColor);
                    border.IsFilled = false;
                    border.Thickness = 1;
                }
            }
        }

        private class VolumeData
        {
            public double UpVolume { get; set; }
            public double DownVolume { get; set; }
        }
    }
}

@EDG777