User mistake, was: Display error in builtin WMA (Weighted Moving Average) calculation

Hi

I was recently amazed about the output of the WMA. The "error" can be reproduced.

If you take a recent IPO stock and add a MA indicator with a length of 250 days you'll probably know the line can't be drawn. Strangely, this behavior does not apply to the WMA.

This code will plot the whole line:

Plot(wma(Close,250),"",colorBrightGreen,styleLine | styleThick);

I went a little further and tried to make the WMA code by myself. I think it is correct because I tested it on another charting platform and received the same output.

function WMA2(x, y) {
	norm = 0;
	res = 0;
	for(i=0; i<y; i++) {
		weight = (y-i)*y;
		norm=norm+weight;
		res=res+Ref(x,-i)*weight;
	}
	return (res/norm);
}

Plot(WMA2(Close,250),"",colorBlack,styleLine | styleThick);

You will now see that with the manual calculation the line is no longer drawn.

Would be great if this could be considered in the next release.

There is no error. In fact it is totally by design. WMA in AmiBroker is variable period function http://www.amibroker.com/guide/a_varperiods.html

So WMA takes advantage of being variable period AND its construction (putting maximum weight into most recent quote) and starts outputting results immediately from first quote.

The same way AMA can start outputting values from second bar.

3 Likes

Tomasz,

There is no need to use loops when you have already provided an efficient inbuilt function. However, out-of curiosity and for learning purposes, wondering how would you code that. Would it be possible to share the logic?

Reason for asking is because, the header of the topic "Display error in builtin WMA (Weighted Moving Average) calculation" kind-of baffled me and made me see for myself. The best I could comprehend and write is this:

function fCustomWMA( Price, Period ) {
	 //Based on https://www.investopedia.com/terms/l/linearlyweightedmovingaverage.asp
	 Numerator = 0;
	 Denominator = 0;

	 for( i = 0; i < Period; i++ ) {
		 Numerator = Numerator + ( Ref( Price, -i ) * ( Period - i ) );
		 Denominator = Denominator + ( Period - i );
	 }

	 return Numerator / Denominator;
}

_SECTION_BEGIN( "WMA" );
	 arr = ParamField( "Select Array", 3 );
	 perWMA = Param( "WMA Period", 20, 1, 1440, 1 );
	 
	 InBuiltWMA = WMA( arr, perWMA );
	 CustomWMA = fCustomWMA( arr, perWMA );
_SECTION_END();

_SECTION_BEGIN( "Plot or Explore" );
	 if( Status( "Action" ) == actionIndicator ) {
		 Plot( C, "Price", colorDefault, styleBar | styleThick );
		 Plot( InBuiltWMA, "InBuiltWMA", colorLightOrange );
		 Plot( CustomWMA, "CustomWMA", ColorRGB( 70, 160, 255 ) );
	 }
	 
	 if( Status( "Action" ) == actionExplore ) {
		 Filter = 1;
		 AddColumn( InBuiltWMA, "InBuiltWMA", 1.2 );
		 AddColumn( CustomWMA, "CustomWMA", 1.2 );		 
		 
		 CheckCond = CustomWMA != InBuiltWMA;
		 TextSelector = IIf( CheckCond, 0, 1 );
		 TextList = "Not-Matching\n ";
		 fgcolor = IIf( CheckCond, colorWhite, colorDefault );
		 bkcolor = IIf( CheckCond, colorRed, colorDefault );
		 AddMultiTextColumn( TextSelector, TextList, "Check", 1.2, fgcolor, bkcolor, 88 );
		 
		 AddColumn( abs( CustomWMA - InBuiltWMA ), "Marginal Difference", 1.8 );
	 }
_SECTION_END();

Going by this hint, the above for-loop calculates accordingly yet fails to achieve precision in comparison to the in-built one.

Obviously for this reason as mentioned by you, although negligible, there remains a Marginal Difference between the Custom and the InBuilt one.

Thank you!

Hi Cougar

I got near to the same result as the inbuilt code. Sorry for stealing your snippet :wink:

Maybe you have another idea because I also wonder how to rebuild it for better understanding.

function fCustomWMA( Price, Period ) {
	 //Based on https://www.investopedia.com/terms/l/linearlyweightedmovingaverage.asp
	 Numerator = 0;
	 Denominator = 0;

	 for( i = 0; i < Period; i++ ) {
		 Numerator = Numerator + ( Ref( Price, -i ) * ( Period - i ) );
		 Denominator = Denominator + ( Period - i );
	 }

	 return Numerator / Denominator;
}

function fCustomWMA2( Price, Period ) {
	 Numerator = 0;
	 Denominator = 0;
	 Period_final = 0;
	if (BarCount < Period) {
		Period_final = BarCount;
	}
	else {
		Period_final = Period;
	}
	
	 for( i = 0; i < Period_final; i++ ) {
		 Numerator = Numerator + ( Ref( Price, -i ) * ( Period_final - i ) );
		 Denominator = Denominator + ( Period_final - i );
	 }

	 return Numerator / Denominator;
}

_SECTION_BEGIN( "WMA" );
	 arr = ParamField( "Select Array", 3 );
	 perWMA = Param( "WMA Period", 20, 1, 1440, 1 );
	 
	 InBuiltWMA = WMA( arr, perWMA );
	 CustomWMA = fCustomWMA( arr, perWMA );
	 CustomWMA2 = fCustomWMA2( arr, perWMA );
_SECTION_END();

_SECTION_BEGIN( "Plot or Explore" );
	 if( Status( "Action" ) == actionIndicator ) {
		 Plot( C, "Price", colorDefault, styleBar | styleThick );
		 Plot( InBuiltWMA, "InBuiltWMA", colorLightOrange );
		 Plot( CustomWMA, "CustomWMA", ColorRGB( 70, 160, 255 ) );
	 }
	 
	 if( Status( "Action" ) == actionExplore ) {
		 Filter = 1;
		 AddColumn( InBuiltWMA, "InBuiltWMA", 1.2 );
		 AddColumn( CustomWMA, "CustomWMA", 1.2 );
		 AddColumn( CustomWMA2, "CustomWMA 2", 1.2 );			 
		 
		 CheckCond = CustomWMA != InBuiltWMA;
		 TextSelector = IIf( CheckCond, 0, 1 );
		 TextList = "Not-Matching\n ";
		 fgcolor = IIf( CheckCond, colorWhite, colorDefault );
		 bkcolor = IIf( CheckCond, colorRed, colorDefault );
		 AddMultiTextColumn( TextSelector, TextList, "Check", 1.2, fgcolor, bkcolor, 88 );
		 
		 CheckCond2 = CustomWMA2 != InBuiltWMA;
		 TextSelector2 = IIf( CheckCond, 0, 1 );
		 fgcolor = IIf( CheckCond2, colorWhite, colorDefault );
		 bkcolor = IIf( CheckCond2, colorRed, colorDefault );
		 AddMultiTextColumn( TextSelector2, TextList, "Check 2", 1.2, fgcolor, bkcolor, 88 );
		 
		 
		 AddColumn( abs( CustomWMA - InBuiltWMA ), "Marginal Difference", 1.8 );
		 AddColumn( abs( CustomWMA2 - InBuiltWMA ), "Marginal Difference 2", 1.8 );
	 }
_SECTION_END();

@paddy,

Using custom loops and comparing its results with in-built functions will always bear some difference. At best we can try to reduce the margin of error. As you can see for yourself, in this case of WMA, the difference is not that impacting.

You can give further try to what Tomasz said here. It would be a great learning if an alternative method with more precision can be developed using custom loops.

Having that said, it would be anyways futile since the inbuilt functions already exists. Such deeds for learning inner workings of indicators - yes helpful! Practical usage - don't see any need! Because the inbuilt ones are highly efficient and accurate any given day.

Tomasz has meticulously taken care of "Everything" while building those functions which exists for decades now. Please refrain from using such subject line in the future, it renders a shock-wave! :slight_smile:

In many places internally AmIBroker uses double precision for intermediate values. This produces more precise result in cases like summation of hundreds or thousands of values. You would probably get better accurracy in your formula if you multiplied by weight once before summation instead of first multiplying, then summing up and then dividing.

2 Likes

@Cougar it is quite evident that the formula itself isn't a secret recipe but more due to the rounding errors as pointed out by Tomasz and higher intermediate computational precision.

When the period is small, say 2-3, you probably have 60-70% results that match but when compared with the same results to a period of 100, it doesn't even look like 5% match.

In this case the mismatch happens only after 10-3 to 10-5 th decimal place, almost unnoticeable. I can very well live with that kind of mismatch! It's an imperfect world, anyways...

Moreover, unless you code from scratch (taking care of single/double precision for the floats) there is no way to match the inbuilt functions values using custom loops. So, no point attempting it, simply give it a go to WMA()! That's all....:slightly_smiling_face:

Thank you for the tip! Since you said probably....

The numerical properties of algorithms is a separate science. Generally speaking precision is lost when magnitude of numbers added differs a lot. In this case with 200-bar WMA recent bar is multiplied by 200 and oldest bar is multiplied by 1. This alone could cost you 2 significant places unless you use double precision for intermediate values (accumulator).

4 Likes