How to plot a study as a normalized index that has 0-100% values (e.g. like MoneyFlowIndex)?

Glefdar

Active member
Hi all,

I can't figure out how this could be done: I want to create an index plot that does not include negative values, and works on a scale of 0% to 100% like MoneyFlowIndex. I want the plot to be for the study value (but I'd also like to try doing this for the rate of change of the study value).

The rate of change is normally calculated this way:

Code:
(price / price[length] - 1) * 100

The standard code that I found to normalize a plot is this:

Code:
script normalizePlot {
    input data = close;
    input newRngMin =  -1;
    input newRngMax = 1;
    def hhData = HighestAll( data );
    def llData = LowestAll( data );
    plot nr = ((( newRngMax - newRngMin ) * ( data - llData )) / ( hhData - llData )) + newRngMin;
}

I am plotting normalized FreedomofMovement rate of change like so:

Code:
declare lower;

def length = 6;

#Rate of change formula: (price / price[length] - 1) * 100

#index example formula: input movingAvgLength = 1; plot MoneyFlowIndex = Average(moneyflow(high, close, low, volume, length), movingAvgLength);

script normalizePlot {
    input data = close;
    input newRngMin =  -1;
    input newRngMax = 1;
    def hhData = HighestAll( data );
    def llData = LowestAll( data );
    plot nr = ((( newRngMax - newRngMin ) * ( data - llData )) / ( hhData - llData )) + newRngMin;
}

#FreedomofMovement

def fomlength = 20;
def numDev = 2.0;
def allowNegativeValues = yes;

def mov = AbsValue(close / close[1] - 1);
def minMov = Lowest(mov, fomlength);
def maxMov = Highest(mov, fomlength);
def nMov = 1 + (mov - minMov) / (maxMov - minMov) * 9;
def vol = (volume - Average(volume, fomlength)) / StDev(volume, fomlength);
def minVol = Lowest(vol, fomlength);
def maxVol = Highest(vol, fomlength);
def nVol = 1 + (vol - minVol) / (maxVol - minVol) * 9;
def vByM = nVol / nMov;

def rawFoM = (vByM - Average(vByM, fomlength)) / StDev(vByM, fomlength);
def FoM = if allowNegativeValues then rawFoM else Max(0, rawFoM);
def StDevLevel = numDev;

def FOMNormalized = normalizePlot(FoM);

plot FOMROC = normalizePlot((FOMNormalized/FOMNormalized[length]-1)*100);

FOMROC.SetDefaultColor(Color.Yellow);

What happens when plotting though is that I'm getting an absolute value in some range of oscillations, like -.326 to .145 for example. So even with the normalization script being used, (1) negative values still occur and (2) the scaling will make the plot unusably shrunken in view if also plotting something else normalized that will be in a higher or lower range (such as in a range of -.9 to -.8 or .7 to .9, etc.).

I see that in the case where two studies don't have a scaling problem fitting in a single lower section that uses a y-axis from 0 to 100%, both use average or movingaverage as the function for generating the study value (such as the case for MoneyFlowIndex and SchaffTrendCycle):

vc6KDhi.jpg


Code:
plot MoneyFlowIndex = Average(moneyflow(high, close, low, volume, length), movingAvgLength);

Code:
plot STC = MovingAverage(averageType, fastK2, DPeriod);

Is there some generic way to convert the FreedomofMovement plot, or any other such plot that can contain negative values normally, into this kind of 0-100% index format? Maybe this is a dumb question, please excuse that, as I don't understand the nuts and bolts of how these calculations work, I just know the format that I want the FreedomofMovement study (and/or a plot for FreedomofMovement rate of change) to use -- i.e. the same format as MoneyFlowIndex. Hoping someone can educate me about whether this is actually possible or has an idea of how it could be done.
 

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

I was first directed here almost two months ago, since the I have tried many attempts are normalizing histogram studies to fit into the same script with RMI and have failed at every attempt. Making a post is always my last resort/hope. I tried to utilize the range code above with 0-100 to normalize a squeeze to plot at the 50 mark because the RMI that I coded as you will see has lines at 10, 20, 30, 70, 80, and 90. In attempting to keep the horizontal lines I assumed best case scenario was to normal the squeeze since the RMI has those constants.

I am beginning to wonder if it is even possible to combine a histogram that plots negative data to become normalized with RMI 0-100 range.

This is the squeeze code, I was attempting to normalize and one of the issues becomes that the cloud for the channel that is in the background is also dependent on the variables that create the histogram.

Any thoughts, is it even possible?
My only other thought is to change it into a percent and multiply that by 50 and then add the positive side to 50 and subtract the negative side from 50.

My closest attempt "simply" adding 50 to the variables to plot it at the 50 (centerline of the RMI) and make it so it plots only > and < 50, code below:
Code:
# Trend and Momentum Oscillator from TadingView converted by mbarcala
#Original Link: https://www.tradingview.com/script/8bJ87hfP-Romi-Trend-and-Momentum-Oscillator/
#study(title = "Romi Trend and Momentum Oscillator", shorttitle="ROMI")

input s1length = 20;
input slength = 6;
input SigLength = 3;

#Histogram Bull variables
def s1 = (ExpAverage(close, slength) - ExpAverage(close, s1length))+50;
def Signal1 = ExpAverage(s1, SigLength)+50;

def MMin = Min(s1, Signal1);
def Con1 = s1 >= 50;
def Con2 = s1 > s1[1];
def Con3 = s1 < s1[1];

#histogram bear variables
def s2 = (ExpAverage(close, slength) - ExpAverage(close, s1length))-50;
def Signal2 = ExpAverage(s2, SigLength)-50;

def Mmax = Max(s2, Signal2);
def Con4 = s2 < 50;
def Con5 = s2 > s2[1];
def Con6 = s2 < s2[1];

plot BullRise = if Con1 and Con2 then MMin else 0;
BullRise.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
BullRise.SetDefaultColor(Color.CYAN);
BullRise.SetLineWeight(2);
plot BullFall = if Con1 and Con3 then MMin else 0;
BullFall.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
BullFall.SetDefaultColor(Color.BLUE);
BullFall.SetLineWeight(2);
plot BearFall = if Con4 and Con6 then Mmax else 0;
BearFall.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
BearFall.SetDefaultColor(color.plum);
BearFall.SetLineWeight(2);
plot BearRise = if Con4 and Con5 then Mmax else 0;
BearRise.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
BearRise.SetDefaultColor(color.magenta);
BearRise.SetLineWeight(2);

def UZ = Average(s1, 34) + (1.3185 * StDev(s2, 34));
#UZ.SetPaintingStrategy(PaintingStrategy.LINE);
#UZ.SetLineWeight(1);
#UZ.SetDefaultColor(Color.YELLOW);
def LZ = Average(s1, 34) - (1.3185 * StDev(s2, 34));
#LZ.SetPaintingStrategy(PaintingStrategy.LINE);
#LZ.SetLineWeight(1);
#LZ.SetDefaultColor(Color.YELLOW);
AddCloud(UZ,LZ,Color.GRAY,Color.GRAY);

#Relative Momentum Index with a Simple Moving Average component. Cloud after 80/20.
declare lower;
declare zerobase;

input length = 18;
input momentum = 9;
input rmiMALength = 2; #hint rsiMALength: RSI Moving Average Length
input rmiAverageType = AverageType.EXPONENTIAL;

assert(momentum > 0, "'momentum' must be positive: " + momentum);

def emaInc = ExpAverage(Max(close - close[momentum], 0), length);
def emaDec = ExpAverage(Max(close[momentum] - close, 0), length);
def RMI = if emaDec == 0 then 0 else 100 - 100 / (1 + emaInc / emaDec);
plot OverBought90 = 90;
plot OverBought80 = 80;
plot OverBought70 = 70;
plot OverSold30 = 30;
plot OverSold20 = 20;
plot OverSold10 = 10;

# def the RMI Moving Average
def rmiMA = MovingAverage(rmiAverageType, RMI, rmiMALength);

AddCloud(if RMI>=80 then RMI else double.nan, OverBought80, Color.RED, Color.RED);
AddCloud(if RMI<=20 then RMI else double.nan, OverSold20, Color.GREEN, Color.GREEN);

OverBought90.SetDefaultColor(CreateColor(153,0,0));
OverSold10.SetDefaultColor(CreateColor(51,102,0));
OverBought80.SetDefaultColor(CreateColor(255,0,0));
OverSold20.SetDefaultColor(CreateColor(51,204,0));
OverBought70.SetDefaultColor(CreateColor(255,102,0));
OverSold30.SetDefaultColor(CreateColor(255,255,0));

#Paint RMI relative to MA crossover
plot RMIxrmiMA = RMI;
RMIxrmiMA.SetPaintingStrategy(PaintingStrategy.LINE);
RMIxrmiMA.AssignValueColor(if RMI > rmiMA then Color.CYAN else Color.MAGENTA);
RMIxrmiMA.SetLineWeight(3);


Here is the original code for the Histogram alone and the RMI code is the same as in the above script.


Code:
# Trend and Momentum Oscillator from TadingView converted by mbarcala
#Original Link: https://www.tradingview.com/script/8bJ87hfP-Romi-Trend-and-Momentum-Oscillator/
#study(title = "Romi Trend and Momentum Oscillator", shorttitle="ROMI")

input s1length = 20;
input slength = 6;
input SigLength = 3;

def s2 = ExpAverage(close, slength) - ExpAverage(close, s1length);
def Signal = ExpAverage(s2, SigLength);

def Mmax = Max(s2, Signal);
def MMin = Min(s2, Signal);
def Con1 = s2 >= 0;
def Con2 = s2 < 0;
def Con3 = s2 > s2[1];
def Con4 = s2 < s2[1];

plot BullRise = if Con1 and Con3 then MMin else 0;
BullRise.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
BullRise.SetDefaultColor(Color.CYAN);
BullRise.SetLineWeight(2);
plot BullFall = if Con1 and Con4 then MMin else 0;
BullFall.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
BullFall.SetDefaultColor(Color.BLUE);
BullFall.SetLineWeight(2);
plot BearFall = if Con2 and Con4 then Mmax else 0;
BearFall.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
BearFall.SetDefaultColor(color.plum);
BearFall.SetLineWeight(2);
plot BearRise = if Con2 and Con3 then Mmax else 0;
BearRise.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
BearRise.SetDefaultColor(color.magenta);
BearRise.SetLineWeight(2);

def UZ = Average(s2, 34) + (1.3185 * StDev(s2, 34));
#UZ.SetPaintingStrategy(PaintingStrategy.LINE);
#UZ.SetLineWeight(1);
#UZ.SetDefaultColor(Color.YELLOW);
def LZ = Average(s2, 34) - (1.3185 * StDev(s2, 34));
#LZ.SetPaintingStrategy(PaintingStrategy.LINE);
#LZ.SetLineWeight(1);
#LZ.SetDefaultColor(Color.YELLOW);
AddCloud(UZ,LZ,Color.GRAY,Color.GRAY);
 
Last edited:
Hi @lmk99,

It just so happens that I am working on a conversion myself and saw your post...Let me see if I can walk through an example and hopefully help you out...

First, the normalizePlot script...In order to get the desired range you're seeking, you need to change two values:

Code:
declare lower;
declare zerobase;

script normalizePlot {
    input data = close;
    input newRngMin =  -1;
    input newRngMax = 1;
    def hhData = HighestAll( data );
    def llData = LowestAll( data );
    plot nr = ((( newRngMax - newRngMin ) * ( data - llData )) / ( hhData - llData )) + newRngMin;
}

So, newRngMin should be 0 and newRngMax should be 100...

Second, the upper portion of the FreedomOfMovement indicator code needs to be separated from the bottom portion and put in a script:

Code:
script FoMScr {
input length = 60;
input allowNegativeValues = no;

def mov = AbsValue(close / close[1] - 1);
def minMov = Lowest(mov, length);
def maxMov = Highest(mov, length);
def nMov = 1 + (mov - minMov) / (maxMov - minMov) * 9;
def vol = (volume - Average(volume, length)) / StDev(volume, length);
def minVol = Lowest(vol, length);
def maxVol = Highest(vol, length);
def nVol = 1 + (vol - minVol) / (maxVol - minVol) * 9;
def vByM = nVol / nMov;

def rawFoM = (vByM - Average(vByM, length)) / StDev(vByM, length);
plot FoM = if allowNegativeValues then rawFoM else Max(0, rawFoM);
}

Third, the bottom (formatting) portion of the FoM code needs to be set up as shown below...For the purposes of this demonstration I chose "FoMScrX" for the variable name, but you can choose anything you want...

Code:
plot FoMScrX = normalizePlot(FoMScr(), 0, 100);
FoMScrX.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
FoMScrX.SetLineWeight(3);
FoMScrX.DefineColor("Above", GetColor(0));
FoMScrX.DefineColor("Below", GetColor(2));
FoMScrX.AssignValueColor(if FoMScrX >= numDev then FoMScrX.Color("Above") else FoMScrX.Color("Below"));

Fourth, there was code included in the FoM source that wasn't directly related to FoM:

Code:
input numDev = 20;
plot StDevLevel = numDev;
StDevLevel.SetDefaultColor(GetColor(7));
StDevLevel.SetStyle(Curve.SHORT_DASH);

This code should go right above the formatting portion of the FoM code...

Finally, if you put it all together, it should hopefully work and look like this:

Code:
declare lower;
declare zerobase;

script normalizePlot {
    input data = close;
    input newRngMin =  0;
    input newRngMax = 100;
    def hhData = HighestAll( data );
    def llData = LowestAll( data );
    plot nr = ((( newRngMax - newRngMin ) * ( data - llData )) / ( hhData - llData )) + newRngMin;
}

script FoMScr {
    input length = 60;
    input allowNegativeValues = no;

    def mov = AbsValue(close / close[1] - 1);
    def minMov = Lowest(mov, length);
    def maxMov = Highest(mov, length);
    def nMov = 1 + (mov - minMov) / (maxMov - minMov) * 9;
    def vol = (volume - Average(volume, length)) / StDev(volume, length);
    def minVol = Lowest(vol, length);
    def maxVol = Highest(vol, length);
    def nVol = 1 + (vol - minVol) / (maxVol - minVol) * 9;
    def vByM = nVol / nMov;

    def rawFoM = (vByM - Average(vByM, length)) / StDev(vByM, length);
    plot FoM = if allowNegativeValues then rawFoM else Max(0, rawFoM);
}

input numDev = 20;
plot StDevLevel = numDev;
StDevLevel.SetDefaultColor(GetColor(7));
StDevLevel.SetStyle(Curve.SHORT_DASH);

plot FoMScrX = normalizePlot(FoMScr(), 0, 100);
FoMScrX.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
FoMScrX.SetLineWeight(3);
FoMScrX.DefineColor("Above", GetColor(0));
FoMScrX.DefineColor("Below", GetColor(2));
FoMScrX.AssignValueColor(if FoMScrX >= numDev then FoMScrX.Color("Above") else FoMScrX.Color("Below"));

I hope this helps...

Good Luck and Good Trading :)
I am wondering if you could help to normalize Plot script for DMI and TTMSqueeze or PRO? I cannot make it work on the same 0
 
I was first directed here almost two months ago, since the I have tried many attempts are normalizing histogram studies to fit into the same script with RMI and have failed at every attempt. Making a post is always my last resort/hope. I tried to utilize the range code above with 0-100 to normalize a squeeze to plot at the 50 mark because the RMI that I coded as you will see has lines at 10, 20, 30, 70, 80, and 90. In attempting to keep the horizontal lines I assumed best case scenario was to normal the squeeze since the RMI has those constants.

I am beginning to wonder if it is even possible to combine a histogram that plots negative data to become normalized with RMI 0-100 range.

I am not sure if I understand what you need, but will share this just in case it helps:

Code:
script Scale
    {
    input c = close;
    def Min = LowestAll(close*-1);
    def Max = HighestAll(close);
    def hh = HighestAll(c);
    def ll = LowestAll(c);
    plot Range = (((Max - Min) * (c - ll)) /  (hh - ll)) + Min;
}

I used this subscript to scale multiple studies to the range of the price chart, with negative values and positive values to keep the histogram studies functioning normally. If you tried re-scaling both the RMI and the histogram studies with this subscript then maybe it would solve your problem? At least the shape of each plot will be preserved, which may be good enough for your signals to still be usable normally.

For the RMI lines at 10, 20, etc., I'm thinking it might be possible to make them display where you want them by generating them as a percentage of the re-scaled price range. So for example instead of "def rmi10 = 10" you could have:

def Min = LowestAll(close*-1);
def Max = HighestAll(close);

def rmi10 = (Min*.10)*2;

I think that this logically makes sense but I haven't thought it through that much: rmi10 is normally a line drawn at 10% of the study section area's vertical height. In this case, since the re-scaled plots have their heights doubled by making a negative and positive range, I think that the lowest value which is negative would be multiplied by .10 first. E.g. if the high price of the equity on the chart was $10, the range of the chart area would be -10 to +10. The value at 10% of the height of the study area would not be -9 (which is a 10% increase from -10), but I think it would instead be -8, which you'd get by taking (Min*.10) and multiplying it by 2.

Likewise above the midline which in the original RMI study is 50 but in the re-scaled study is 0, you'd replace "Min" with "Max" as follows:

def rmi70 = (Max*.70)/2

I think you'd divide by two instead of multiply by two: for example, if the max value was 10, and the min value was -10, the 70% point would be just close to halfway above 0, e.g. (10*.7)/2 = 4.5.

My logic about how to make the equivalent RMI lines might be wrong - I'm not very good at mathematical thinking - but I think you could do something along similar lines to create equivalent values that are re-scaled to the study area.
 

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
585 Online
Create Post

Similar threads

Similar threads

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.

How do I get started?

We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.

If you are new, or just looking for guidance, here are some helpful links to get you started.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top