Values not updating on time frame change for custom indicator with graphics module

I have a custom indicator that uses some values like the standard deviation of close and other calculated values.

I have a version of this that loads in a new window which doesn’t use graphics module and outputs fine. When you change time frames (1D/5m/etc) for that one, you get expected results, where values like standard deviation and others update as expected.

However on a more complicated version of it that uses the graphics module and outputs the values on the last bar, it does not update when you change time frames. So if you go from the daily bar to a 5 minute bar, the values will still reflect those of the daily bar, where the less complicated one will have the correct values corresponding to the current time frame.

Currently the only way to get it to update after changing times seems to be to re-save the indicator code or remove and re-add the indicator through the UI.

Uploading examples of daily and hourly time frame with both indicators shown. Source code below.

Daily chart, both indicators (one in new area, one as overlay) match:

If you change to the hourly, the values above and their plots still correspond to the daily bar, even though the chart has changed to hourly:

  • Is there something I am missing to force it to update when the time frame is changed?

Here is the source of the overlay/graphics module version, which has the issue:

const predef = require("./tools/predef");
const meta = require("./tools/meta");
const MovingHigh = require("./tools/MovingHigh");
const MovingLow = require("./tools/MovingLow");
const MMA = require("./tools/MMA");
const StdDev = require("./tools/StdDev");
const trueRange = require("./tools/trueRange");
const g = require("./tools/graphics");
const du = g.du;
const op = g.op;
const px = g.px;

const barScapeLines = function (openValue, description, value, color, textSize, decimals) {
    
    const startPosition = 40;
    const length = 20;
    const padding = 10;
    
    return {
        tag: 'Container',
        key: description+'Container',
        children: [
            {
                 tag: 'LineSegments',
                 key: 'lines',
                 lines: [
                     {
                         tag: 'Line',
                         a: {
                             x: op(du(0), '+', px(startPosition)),
                             y: du(openValue+value),
                         },
                         b: {
                             x: op(du(0), '+', px(startPosition+length)),
                             y: du(openValue+value)
                         },
                         infiniteStart: false,
                         infiniteEnd: false,
                         
                     },
                     {
                         tag: 'Line',
                         a: {
                             x: op(du(0), '+', px(startPosition)),
                             y: du(openValue-value),
                         },
                         b: {
                             x: op(du(0), '+', px(startPosition+length)),
                             y: du(openValue-value)
                         },
                         infiniteStart: false,
                         infiniteEnd: false,
                         
                     }
                 ],
                 lineStyle: {
                     lineWidth: 2,
                     color: color
                 }
            },
            {
                tag: "Text",
                key: description+"H",
                text: `${description} ${value.toFixed(decimals)}`,
                point: {
                    x: op(du(0), '+', px(startPosition+length+padding)),
                    y: op(du(openValue+value), '+', px(0))
                },
                style: { fontSize: textSize, fontWeight: "bold", fill: color},
                textAlignment: "rightMiddle"
            },
            {
                tag: "Text",
                key: description+"L",
                text: `${description} ${value.toFixed(decimals)}`,
                point: {
                    x: op(du(0), '+', px(startPosition+length+padding)),
                    y: op(du(openValue-value), '+', px(0))
                },
                style: { fontSize: textSize, fontWeight: "bold", fill: color},
                textAlignment: "rightMiddle"
            },
        ]
    }

};

class BarScape {
    init() {
        this.pullbackAverage = MMA(this.props.barScapePeriod);
        this.pullbackHigh = MovingHigh(this.props.barScapePeriod);
        this.bodyAverage = MMA(this.props.barScapePeriod);
        this.bodyHigh = MovingHigh(this.props.barScapePeriod);
        this.stdDev = StdDev(this.props.barScapePeriod);
    }

    map(d, i, history) {
        
        const open = d.open();
        const close = d.close();
        const high = d.high();
        const low = d.low();
        
        const up = close > open ? true : false;
        const down = close < open ? true : false;
        const top = close > open ? close : open;
        const bottom = close > open ? open : close;
        const body = top-bottom;
        const pullback = up ? bottom-low : high-top;

        const averageBody = this.bodyAverage(body);
        const highBody = this.bodyHigh(body);
        const averagePullback = this.pullbackAverage(pullback);
        const highPullback = this.pullbackHigh(pullback);
        const standardDev = this.stdDev(close);

        // display settings
        const decimals = this.props.decimals;
        const textSize = this.props.textSize;
        const highPullbackColor = this.props.highPullbackColor;
        const avgPullbackColor = this.props.avgPullbackColor;
        const highBodyColor = this.props.highBodyColor;
        const avgBodyColor = this.props.avgBodyColor;
        const stdDevColor = this.props.stdDevColor;

        return {
            
          graphics: d.isLast() && {
                items: [
                    barScapeLines (open, "OPEN", 0, "#BBBBBB", textSize, decimals),
                    barScapeLines (open, "STDEV", standardDev, stdDevColor, textSize, decimals),
                    barScapeLines (open, "PB_AVG", averagePullback, avgPullbackColor, textSize, decimals),
                    barScapeLines (open, "PB_HIGH", highPullback, highPullbackColor, textSize, decimals),
                    barScapeLines (open, "B_AVG", averageBody, avgBodyColor, textSize, decimals),
                    barScapeLines (open, "B_HIGH", highBody, highBodyColor, textSize, decimals),
                ]
            }
        
        };

        
    }

    filter(d,i) {
        return i > this.props.barScapePeriod;
    }
}


module.exports = {
    name: "BarScape",
    title: "BarScape",
    description: "BarScape",
    calculator: BarScape,
    params: {
        barScapePeriod: predef.paramSpecs.period(20),
        decimals: predef.paramSpecs.number(7),
        textSize: predef.paramSpecs.number(11),
        stdDevColor: predef.paramSpecs.color("#fc03df"),
        avgBodyColor: predef.paramSpecs.color("gray"),
        highBodyColor: predef.paramSpecs.color("green"),
        avgPullbackColor: predef.paramSpecs.color("orange"),
        highPullbackColor: predef.paramSpecs.color("red")
    },
    inputType: meta.InputType.BARS,
    plots: {

    },
    plotter: [

    ],
    schemeStyles: {

    },
    tags: ["Brian"],

};

One update:

It seems the horizontal placement of this doesn’t really work right either. In the code editor view that I have set up using du(0) places it at the last bar more or less (with the padding), but if I use that code in the normal chart view it places it somewhere far off to the left on the chart. However, if I change that to pass i in from the map function, and then use du(i) instead, then the horizontal placement has strange behavior for any time frame but the first one pretty much. Changing from daily to 5m for example kicks it far off to the right off the screen.

I am evidently missing something here, or just doing it wrong.

Can you please try these two things - add global: true to the body of the Container object (it will still save old renders even with d.isLast(), global ensures a single instance), and also try changing the key 'lines' to something else - I was doing some debugging the other day and realized that native lines use the key 'lines'. Not sure if it will mess up graphics or not, but key should be unique for re-render purposes. 'barscape-lines' would suffice to test this theory.

Thank you for the ideas.

Here’s the updated code with those suggestions added. I used the description in that key as I had the other places. Still has the same behavior. And the horizontal placement does get wonky at times too, though I can’t really figure out what determines when/how the horizontal placement issues happen. It isn’t as consistent as I thought, even in testing this I got it off to the left some or off to the right some.

const predef = require("./tools/predef");
const meta = require("./tools/meta");
const MovingHigh = require("./tools/MovingHigh");
const MovingLow = require("./tools/MovingLow");
const MMA = require("./tools/MMA");
const StdDev = require("./tools/StdDev");
const trueRange = require("./tools/trueRange");
const g = require("./tools/graphics");
const du = g.du;
const op = g.op;
const px = g.px;

const barScapeLines = function (openValue, description, value, color, textSize, decimals) {
    
    const startPosition = 40;
    const length = 20;
    const padding = 10;
    
    return {
        tag: 'Container',
        key: description+'Container',
        global: true,
        children: [
            {
                 tag: 'LineSegments',
                 key: description+'-barscape-lines',
                 lines: [
                     {
                         tag: 'Line',
                         a: {
                             x: op(du(0), '+', px(startPosition)),
                             y: du(openValue+value),
                         },
                         b: {
                             x: op(du(0), '+', px(startPosition+length)),
                             y: du(openValue+value)
                         },
                         infiniteStart: false,
                         infiniteEnd: false,
                         
                     },
                     {
                         tag: 'Line',
                         a: {
                             x: op(du(0), '+', px(startPosition)),
                             y: du(openValue-value),
                         },
                         b: {
                             x: op(du(0), '+', px(startPosition+length)),
                             y: du(openValue-value)
                         },
                         infiniteStart: false,
                         infiniteEnd: false,
                         
                     }
                 ],
                 lineStyle: {
                     lineWidth: 2,
                     color: color
                 }
            },
            {
                tag: "Text",
                key: description+"H",
                text: `${description} ${value.toFixed(decimals)}`,
                point: {
                    x: op(du(0), '+', px(startPosition+length+padding)),
                    y: op(du(openValue+value), '+', px(0))
                },
                style: { fontSize: textSize, fontWeight: "bold", fill: color},
                textAlignment: "rightMiddle"
            },
            {
                tag: "Text",
                key: description+"L",
                text: `${description} ${value.toFixed(decimals)}`,
                point: {
                    x: op(du(0), '+', px(startPosition+length+padding)),
                    y: op(du(openValue-value), '+', px(0))
                },
                style: { fontSize: textSize, fontWeight: "bold", fill: color},
                textAlignment: "rightMiddle"
            },
        ]
    }

};

class BarScape {
    init() {
        this.pullbackAverage = MMA(this.props.barScapePeriod);
        this.pullbackHigh = MovingHigh(this.props.barScapePeriod);
        this.bodyAverage = MMA(this.props.barScapePeriod);
        this.bodyHigh = MovingHigh(this.props.barScapePeriod);
        this.stdDev = StdDev(this.props.barScapePeriod);
    }

    map(d, i, history) {
        
        const open = d.open();
        const close = d.close();
        const high = d.high();
        const low = d.low();
        
        const up = close > open ? true : false;
        const down = close < open ? true : false;
        const top = close > open ? close : open;
        const bottom = close > open ? open : close;
        const body = top-bottom;
        const pullback = up ? bottom-low : high-top;

        const averageBody = this.bodyAverage(body);
        const highBody = this.bodyHigh(body);
        const averagePullback = this.pullbackAverage(pullback);
        const highPullback = this.pullbackHigh(pullback);
        const standardDev = this.stdDev(close);

        // display settings
        const decimals = this.props.decimals;
        const textSize = this.props.textSize;
        const highPullbackColor = this.props.highPullbackColor;
        const avgPullbackColor = this.props.avgPullbackColor;
        const highBodyColor = this.props.highBodyColor;
        const avgBodyColor = this.props.avgBodyColor;
        const stdDevColor = this.props.stdDevColor;

        return {
            
          graphics: d.isLast() && {
                items: [
                    //barScapeLines (open, "OPEN", 0, "#BBBBBB", textSize, decimals),
                    barScapeLines (open, "STDEV", standardDev, stdDevColor, textSize, decimals),
                    barScapeLines (open, "PB_AVG", averagePullback, avgPullbackColor, textSize, decimals),
                    barScapeLines (open, "PB_HIGH", highPullback, highPullbackColor, textSize, decimals),
                    barScapeLines (open, "B_AVG", averageBody, avgBodyColor, textSize, decimals),
                    barScapeLines (open, "B_HIGH", highBody, highBodyColor, textSize, decimals),
                ]
            }
        
        };

        
    }

    filter(d,i) {
        return i > this.props.barScapePeriod;
    }
}


module.exports = {
    name: "BarScape",
    title: "BarScape",
    description: "BarScape",
    calculator: BarScape,
    params: {
        barScapePeriod: predef.paramSpecs.period(20),
        decimals: predef.paramSpecs.number(7),
        textSize: predef.paramSpecs.number(11),
        stdDevColor: predef.paramSpecs.color("#fc03df"),
        avgBodyColor: predef.paramSpecs.color("gray"),
        highBodyColor: predef.paramSpecs.color("green"),
        avgPullbackColor: predef.paramSpecs.color("orange"),
        highPullbackColor: predef.paramSpecs.color("red")
    },
    inputType: meta.InputType.BARS,
    plots: {

    },
    plotter: [

    ],
    schemeStyles: {

    },
    tags: ["Brian"],

};

What I’ve done for now to work around the issue is make a version with just lines (multiline plot) so it’s like a channel and one with dots. Either case it is enough to have the same info laid out, though not as ideal as having the numbers and lines marked. If you (or anyone) happen to have ideas on how to make the other way work let me know. I think I have issues with understanding the horizontal layout too, and so that other path for now is just not going to work well. A table layout (similar to tradingview’s tables) would also work, and I have a version of that, but it has the horizontal placement issue on that one (not vertical, since in that case it doesn’t try to plot specific places vertically), and also suffers from the “not updating” thing, where the numbers don’t update with the time frame.

This isn’t the kind of solution you’re looking for, but just a heads up, I’ve found a quick way to “refresh” an indicator: add a second chart tab to the pane displaying your chart. When you want to refresh the indicator, switch to that other chart and then switch back to your original chart.

Thanks for that info. For now I’ve been using tradingview in the cases where I use this most, and hope to find out how to make the tradovate indicator behave better in time. The downside there is that there is no auto-break-even or trailing stops (not to mention no preset brackets), but, there are trade-offs everywhere I think. No perfect solutions.

Just wanted to update, in case someone else runs into something like this, that I got it working. It updates when it should, the location doesn’t go wonky, all works as it should now. Here are the pieces that seemed to matter:

  • building the whole thing from functions didn’t seem to work, it immediately started behaving better when I moved it all into non-dynamically generated pieces (a big collection of js objects)
  • dynamic values for text that were decided at the point of printing were also a culprit. in other words, the text: key of the children objects had to not have any logic contained there for printing or not printing or formatting the value, etc. I think the same is true for the other object keys (including the key: one)
  • the global:true part was also a thing, but, it had to be done it seemed in a pretty specific structure for it to work. if I varied from the example I found in the “Volume Delta Grid” code, it started behaving badly again.
  • Not doing pixel/domain unit mix-and-matches on the horizontal alignment solved those issues. Instead, it just stays in domain units (bars), and it is consistent as long as the other pieces are in place (non-dynamic, etc)