MTF Custom Indicator

Created at 24 Apr 2025, 23:17
KA

kabbajjad

Joined 30.06.2022

MTF Custom Indicator
24 Apr 2025, 23:17


Hello everyone thank you for your help.

I have spent a few hours debugging this but I still can't find a fix.

I have developed a custom Indicator and I want to create another Inidicator that will be a panel that will show if for each timeframe the last close is above or below the “centerline” made by the initial custom indicator.

I have tried two options : 
1) Added the indicator as a reference + Indicators.GetIndicator<SwingPoints>(barsH4, Period, true, true, true, 10, 10, Color.White, Color.White). All the output values from the indicator are NaN.
2) Rewriting the full code into this new indicator Panel but it works only on the current timeframe the indicator is set on. I am guessing it is due to Calculate(index) that only calculates based on the current timeframe candles.

Codes provided below : 
INITIAL SWINGPOINT INDICATOR 
using System;

using System.Collections.Generic;

using System.Diagnostics.Metrics;

using cAlgo.API;

using cAlgo.API.Indicators;

using cAlgo.API.Internals;


 

namespace cAlgo

{

    [Indicator(AccessRights = AccessRights.None, IsOverlay = true)]

    public class SwingPoints : Indicator

    {


 

        [Parameter("Swing Point Period", DefaultValue = 5, MinValue = 1, MaxValue = 50)]

        public int Period { get; set; }


 

        [Parameter("Show Swing Points", DefaultValue = true)]

        public bool ShowPivot { get; set; }


 

        [Parameter("Show Center Line", DefaultValue = true)]

        public bool ShowCenterLine { get; set; }


 

        [Parameter("Show Support/Resistance", DefaultValue = true)]

        public bool ShowSR { get; set; }


 

        [Parameter("H pips above pivot", DefaultValue = 10)]

        public int Hpips { get; set; }


 

        [Parameter("L pips below pivot", DefaultValue = 10)]

        public int Lpips { get; set; }


 

        [Parameter("Res Colors", DefaultValue = "Yellow")]

        public Color ResColors { get; set; }


 

        [Parameter("Sup Colors", DefaultValue = "Blue")]

        public Color SupColors { get; set; }


 

        [Output("PivotHigh")]

        public IndicatorDataSeries PivotHighSeries { get; set; }


 

        [Output("PivotLow")]

        public IndicatorDataSeries PivotLowSeries { get; set; }


 

        [Output("Center Line")]

        public IndicatorDataSeries CenterLine { get; set; }


 

        private double lastPivotHigh = double.NaN;

        private double lastPivotLow = double.NaN;

        private string activeResLine = null;

        private string activeSupLine = null;

        private DateTime lastResTime;

        private DateTime lastSupTime;

        private double lastResPrice;

        private double lastSupPrice;



 

        protected override void Initialize()

        {

            CenterLine = CreateDataSeries();

            PivotHighSeries = CreateDataSeries();

            PivotLowSeries = CreateDataSeries();


 

        }


 

        public override void Calculate(int index)

        {

            if (index < Period * 2)

                return;


 

            int pivotIndex = index - Period;


 

            bool isHigh = true;

            for (int i = 1; i <= Period; i++)

            {

                if (Bars.HighPrices[pivotIndex] <= Bars.HighPrices[pivotIndex - i] ||

                    Bars.HighPrices[pivotIndex] <= Bars.HighPrices[pivotIndex + i])

                {

                    isHigh = false;

                    break;

                }

            }



 

            if (ShowCenterLine)

            {


 

                PivotHighSeries[index] = isHigh

                ? Bars.HighPrices[pivotIndex]

                : double.NaN;



 

                double lastPivotPoint = double.IsNaN(PivotHighSeries[index]) ? double.NaN : PivotHighSeries[index];



 

                double prevCL = CenterLine[index - 1];


 

                double currCL = double.IsNaN(prevCL)

                    ? lastPivotPoint

                    : (double.IsNaN(lastPivotPoint)

                        ? prevCL

                        : (prevCL * 2 + lastPivotPoint) / 3);



 

                CenterLine[index] = currCL;


 

                // 2. Only draw valid segments

                if (!double.IsNaN(prevCL) && !double.IsNaN(currCL))

                {

                    var clr = Bars.ClosePrices[index] > currCL

                        ? SupColors : ResColors;



 

                    Chart.DrawTrendLine(

                        "ctrCL" + index,

                        Bars.OpenTimes[index - 1], prevCL,

                        Bars.OpenTimes[index], currCL,

                        clr, 2, LineStyle.Solid

                    );

                }

            }

        }

    }

}



PANEL INDICATOR : 
using System;

using System.Collections.Generic;

using System.Text;

using cAlgo.API;


 

namespace cAlgo

{

    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]

    public class MultiTimeframeSwingPanel : Indicator

    {

        [Parameter("Swing Point Period", DefaultValue = 5, MinValue = 1, MaxValue = 50)]

        public int Period { get; set; }


 

        private Dictionary<TimeFrame, double> CenterLine;


 

        private (string Label, TimeFrame Tf)[] panelItems = new[]

        {

            ("M1",  TimeFrame.Minute),

            ("M5",  TimeFrame.Minute5),

            ("M15", TimeFrame.Minute15),

            ("M30", TimeFrame.Minute30),

            ("H1",  TimeFrame.Hour),

            ("H4",  TimeFrame.Hour4),

            ("D1",  TimeFrame.Daily),

            ("W1",  TimeFrame.Weekly)

        };


 

        private SwingPoints swingM1, swingM5, swingM15, swingM30, swingH1, swingH4, swingD1, swingW1;


 

        protected override void Initialize()

        {

            // Initialize all center‑lines to NaN

            CenterLine = new Dictionary<TimeFrame, double>();

            foreach (var (_, tf) in panelItems)

                CenterLine[tf] = double.NaN;


 

        }


 

        public override void Calculate(int index)

        {

            var sb = new StringBuilder();


 

            foreach (var (label, tf) in panelItems)

            {

                var tfBars = MarketData.GetBars(tf);

                int count = tfBars.ClosePrices.Count;


 

                // Need enough bars to compute a pivot at all

                if (count <= Period * 2)

                {

                    Print("Not enough bars to compute a pivot at all");

                    Print("Count: {0}, Period: {1}", count, Period);

                    sb.AppendLine($"{label}: –");

                    continue;

                }


 

                // pivotIndex for the last completed bar

                int pivotIndex = count - 1 - Period;


 

                // 1) Detect pivot high exactly like your SwingPoints

                bool isHigh = true;

                for (int i = 1; i <= Period; i++)

                {

                    if (tfBars.HighPrices[pivotIndex] <= tfBars.HighPrices[pivotIndex - i] ||

                        tfBars.HighPrices[pivotIndex] <= tfBars.HighPrices[pivotIndex + i])

                    {

                        isHigh = false;

                        break;

                    }

                }


 

                // 2) Update that TF’s center‑line

                double lastPivotPoint = isHigh

                    ? tfBars.HighPrices[pivotIndex]

                    : double.NaN;


 

                double prevCL = CenterLine[tf];

                double currCL = double.IsNaN(prevCL)

                    ? lastPivotPoint

                    : (double.IsNaN(lastPivotPoint)

                        ? prevCL

                        : (prevCL * 2 + lastPivotPoint) / 3);


 

                CenterLine[tf] = currCL;


 

                // If we still don’t have a valid center‑line, show “–”

                if (double.IsNaN(currCL))

                {

                    Print("No valid center line for {0}", label);

                    sb.AppendLine($"{label}: –");

                    continue;

                }


 

                // 3) Compare the last **closed** bar’s close to currCL

                double lastClose = tfBars.ClosePrices.Last(1);

                bool bullish = lastClose > currCL;

                string arrow = bullish ? "↑" : "↓";


 

                sb.AppendLine($"{label}: {arrow}");

            }


 

            // Draw (or update) the single block of text

            Chart.DrawStaticText(

                "mtfPanel",              // unique ID

                sb.ToString(),           // multi-line text

                VerticalAlignment.Top,   // from chart top

                HorizontalAlignment.Left,// from chart left

                Color.White             // text color

                                        // style

            );

        }

    }

}



BASICALLY ASIDE From the current timeframe which gives the correct arrow; all the other ones give “-” value because currCL, prevCL, lastPivotPoint are all equal to NaN. Also sorry for the messy code it's just a messy tradingview indicator converted to ctrader for a client.

Thank you for your reply.
Best regards,
Jad

 


@kabbajjad
Replies

firemyst
29 Apr 2025, 02:06

To start, you do need to get the Bars data for every other time frame you want that isn't the chart's timeframe:

Bars _marketSeries = MarketData.GetBars(SourceTimeFrame, Symbol.Name);

 

Then, in your calculate method, you need to get the latest information from that/those timeframe(s):

int altIndex = index;
if (Bars.TimeFrame != _marketSeries.TimeFrame)
{
   altIndex = _marketSeries.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);
}

 

Then get the values you want using that altIndex Some examples with the moving average and standard deviation:

 

double maResultIndex = _movingAverage.Result[altIndex];
double sd = StandardDeviation * _standardDeviation.Result[altIndex];

 


@firemyst