Dynamic Variables to solve Highest and Lowest Moving average in a group of averages

I have been trying to help another user by coding a method to identify the highest and lowest moving average out of a large number of moving averages.

I was successful with multiple nested IIF’s as done by @portfoliobuilder in the thread from a couple of weeks ago,

but Tomasz’s dynamic variable solution in that same thread is obviously better. I’ve tried a few variations but can’t seem to get it. Can anyone point to some other examples I can review and learn from?

1 Like

@quantboy I don't know of other examples and I assume you are not looking for a simple condition where multiple moving averages are in a certain order, like this

http://www.amibroker.com/kb/2015/02/10/checking-relationship-between-multiple-moving-averages/

As I like to preface most offered solutions, I am no expert but … I had recently attempted something similar to what you are asking and made a few tweaks for it. It looks like it’s working but double check before using it.

Aron P. had actually introduced me to dynamic variables a couple of years ago, but I had forgotten all about them until that recent forum thread. Here I used Tomasz’s code as a starting point and I love the @helixtrader idea of the Multitextcolumn for the Exploration output. So if this works credit to @Aron, Tomasz and Alan for their teachings (if it doesn't work I accept the blame for being a bad student :smile:).

As I look at the Explore, it looks a bit cluttered but I was uncertain about which output you were most interested in. Obviously you can remove any or all columns that are not of interest.

// compare and find highest and lowest moving average

MA1 = MA( C, 10 );
MA2 = MA( C, 20 );
MA3 = MA( C, 40 );
MA4 = MA( C, 60 );

topMA = 0;
bottomMA = 1e9;

for( i = 1; i <= 4; i++ )
{
    indicator = VarGet( "MA" + i );
    topMA = IIf( topMa > indicator, topMa, indicator );
    bottomMA = IIf( bottomMA < indicator, bottomMA, indicator );
}

TopValue = topMA;
BottomValue = bottomMA;

/////////////////////////////////////////////////////////////////////
// the rest is just for the Explore output to appear a certain way //
/////////////////////////////////////////////////////////////////////
HighestMA = IIf( topMA == MA1, 0,
                 IIf( topMA == MA2, 1,
                      IIf( topMA == MA3, 2,
                           IIf( topMA == MA4, 3, Null ) ) ) );

LowestMA = IIf( bottomMA == MA1, 0,
                IIf( bottomMA == MA2, 1,
                     IIf( bottomMA == MA3, 2,
                          IIf( bottomMA == MA4, 3, Null ) ) ) );

// Explore the calculations //
Filter = 1;
AddColumn( Close, "Close" );
AddColumn( MA1, "MovingAve 1", 1.2, colorDefault, IIf( MA1 == topMA, colorLime, IIf( MA1 == bottomMA, colorRose, colorDefault ) ) );
AddColumn( MA2, "MovingAve 2", 1.2, colorDefault, IIf( MA2 == topMA, colorLime, IIf( MA2 == bottomMA, colorRose, colorDefault ) ) );
AddColumn( MA3, "MovingAve 3", 1.2, colorDefault, IIf( MA3 == topMA, colorLime, IIf( MA3 == bottomMA, colorRose, colorDefault ) ) );
AddColumn( MA4, "MovingAve 4", 1.2, colorDefault, IIf( MA4 == topMA, colorLime, IIf( MA4 == bottomMA, colorRose, colorDefault ) ) );
AddColumn( TopValue, "TopValue" );
AddColumn( BottomValue, "BottomValue" );
AddMultiTextColumn( HighestMA, "MovingAve 1\nMovingAve 2\nMovingAve 3\nMovingAve 4", "Highest MA", 1.2, colorDefault, colorLime );
AddMultiTextColumn( LowestMA, "MovingAve 1\nMovingAve 2\nMovingAve 3\nMovingAve 4", "Lowest MA", 1.2, colorDefault, colorRose );

image

3 Likes

Another examples of using Varset/Varget? Just use forum search http://forum.amibroker.com/search?q=varget

With dynamic variables you can simplify repetitive code, for example.

As for highest and lowest MA out of a bunch of MAs just use the most obvious ones and usual suspects... Min() and Max() functions.

function VarGetMax( varname, num ) {
    local n, maxall;
    maxall = -1e9;
    for ( n = 1; n <= num; n++ )
        maxall = Max( maxall, VarGet( varname + n ) );
    return maxall;
}

function VarGetMin( varname, num ) {
    local n, minall;
    minall = 1e9;
    for ( n = 1; n <= num; n++ )
        minall = Min( minall, VarGet( varname + n ) );
    return minall;
}

Use case:

//Example:

/*
var1 = ROC( C, 10 );
var2 = ROC( C, 20 );
var3 = ROC( C, 30 );
var4 = ROC( C, 40 );
var5 = ROC( C, 50 );
*/

// simpler than upper ones
maxn = 5;

for ( n = 1; n <= maxn; n++ ) {
  VarSet( "var" + n, ROC( C, n*10 ) );
  printf( "%g ROC(C,%g) variable: %g\n", n, n*10, VarGet( "var" + n ) );
}

getmax = VarGetMax( "var", maxn );
printf( "\nMax. of %g Variables: %g", maxn, getmax );

getmin = VarGetMin( "var", maxn );
printf( "\nMin. of %g Variables: %g", maxn, getmin );

11


And please, the idea of Multitextcolumn does not not come from that H. "vendor" but comes from other sources first and foremost from AB documentation as well as from earlier forum posts. And quite frankly that a vendor didn't know about dynamic variables is quite "shocking" to me (that's why my comment in that thread being misunderstood) and just proves to me being the opposite of a professional programmer. Instead rather using infos by others to push some (fake?) service. Proof? Just let me give a task to solve and then let's compare results by a neutral (really professional) person with actually existing expertise.

On the other hand @aron is a good one (IMO) as he can solve problems on his own and you can see an actual own style of doing things.

9 Likes

@fxshrat @portfoliobuilder thank you both!

@fxshrat in your functions you declare the local variables,

local n, maxall;
    
// and later 

local n, minall;

Is this just good programming practice or is it necessary to declare the local variables? I assume if they first appear within a function they automatically will be recognized as being local as opposed to global variables. But I am a novice code writer so uncertain.

IMHO, in general, It is a good programming practice (many programming languages have specific keywords to define the “scope” of the variables) but in the case of AmiBroker, AFAIK, it is not strictly needed. See these pages:

Global and local keywords

User-definable functions, procedures. Local/global scope

Moreover, please note the possibility to use the

SetOption("RequireDeclarations", True );

From the above page:

You can however force AFL engine to require all variables to be declared using local or global keywords on formula-by-formula basis by placing SetOption(“RequireDeclarations”, True ); at the top of the formula.

3 Likes

Yes if variables apprear first in the function definition they are automatically treated as local, but @fxshrat uses good programming practice of explicit local declaration so regardless if your code is using same identifiers for globals or not it will work correctly in call cases.

1 Like

Check this example (local declaration being commented)

i = 4; 
cnt = 3;

"The value of i before function call :" + WriteVal( i ); 
"The value of cnt before function call :" + WriteVal( cnt ); 

function func()
{
    // local i, cnt;
    cnt = 0;
    for( i = 0; i < 10; i++ )
        cnt++;
    return cnt; 
}

"\nThe result of func() = " + WriteVal( func() ); 

i++;
cnt++;

"\ni: " + i;
"cnt: " + cnt;

Result
10


Now local declaration being uncommented

i = 4; 
cnt = 3;

"The value of i before function call :" + WriteVal( i ); 
"The value of cnt before function call :" + WriteVal( cnt ); 

function func()
{
    local i, cnt;
    cnt = 0;
    for( i = 0; i < 10; i++ )
        cnt++;
    return cnt; 
}

"\nThe result of func() = " + WriteVal( func() ); 

i++;
cnt++;

"\ni: " + i;
"cnt: " + cnt;

Result:
11

Today, when writing some sample code (in version 6.27.1 - 64 bits) using the above option I saw that the reserved variables (like “Filter”, “PositionScore”, etc.) need to be redeclared as global.

Is this expected?

Without a declaration AmiBroker reports an error of “Undeclared variable”.

On the other hand declaring them as local is not reported as a syntax error, clicking the “Verify syntax” button, but for instance, when declaring “Filter” as local, the resulting exploration report is empty (I mean also if I set it to True - i.e. Filter = 1;).

Probably a specific warning for this user misuse of reserved variables in these cases will be useful.

If you turned ON option to REQUIRE declaration, why are you surprised that it REQUIRES declaration?

Declaring “filter” as local variable is PERFECTLY OK. Local variables are … local - they don’t interfere with globals and if you assign value to LOCAL variable, why do you think it should be treated as non local. It is local. You declared it so.

@Tomasz

thanks. I completely agree with your explanation.

Indeed, I was a bit surprised since I (wrongly) supposed reserved variables to be similar to reserved words in the sense that I expected the syntax checker to warn me that declaring them as local could lead to some unexpected result and errors in subsequent actions.

For instance, declaring PositionScore as local is not flagged as an error by the syntax checker, but running a backtest it gives an error, that IMHO is not immediately easy to understand (since the "PositionScore variable is actually declared, probably by mistake, as local).

Try this snippet:

version( 6.27 );
SetOption("RequireDeclarations", True );
SetOption("InitialEquity", 50000 );
SetOption( "MaxOpenPositions", 10);
SetPositionSize( 100 / 10, spsPercentOfEquity );
SetBacktestMode( backtestRotational );
SetOption("AllowPositionShrinking", true);
SetOption("WorstRankHeld", 3);
SetTradeDelays( 1, 1, 1, 1 );

procedure RotatePortfolio(calculatedScore) {
	local PositionScore;
	
	PositionScore = 1000 + calculatedScore;
	// ..some other code
}

local calculatedScore; 
 
calculatedScore = ROC(L, 20);;
// ..... some othe code
RotatePortFolio(calculatedScore);

Running a backtest I see the following error message:

immagine

While all this is not a big issue at all, I still think that a friendly warning by the syntax checker that the user is "hiding" reserved variables declaring them as locals, is something to consider for future versions.

Users really seriously underestimate the amount of thought I have put into AmiBroker.
I have really thought of everything. You just did not discover it yet.

Syntax error is when something is syntactically incorrect. But the code you wrote is syntactically CORRECT. There is no syntax error. Declaring local variables of any name is NOT an error.
You are getting an error later from backtester and only ROTATIONAL one because backtester in ROTATIONAL mode requries GLOBAL PositionScore variable. Local variables do NOT exist outside functions. That is why it complains that it does not have PositionScore. Because it does NOT exist.

Keep in mind that the very same code in AmiBroker can be run in Indicator, Commentary, interpretation, scan, exploration, backtest and optimization. So AFL formula editor at the time when you edit or check the code does NOT know HOW and WHERE you will run your code. It can not assume that you would run backtest. The same code can be used for different things.

4 Likes

@Tomasz thanks for making it clear.

Maybe, for this specific situation, you should only slightly modify the error message as:

Error 703 - Rotational trading requires a global PositionScore variable

While I cannot comment for the others users, personally I assure you that I’m impressed by the power and vastity of AmiBroker functionality (including the super-efficient use multi-threading); even more if I compare what it does vs. its price.

I also agree with you that “I have not discovered it yet.”
… And it will probably take years to do it! As I see it, AmibBroker is a huge functional framework, that under the hood does a lot of hidden work when we invoke it actions (scan explore, backtest, etc.), and understanding how to correctly and efficiently interact with it will take a good amount of time and practice.

I like to learn new skills looking at the docs, to short snippets, to full-blown code examples, and then I enjoy to test what I studied writing my own code (with a good amount of mistakes along the way).
For this reason, I’m here; I would like to learn from you and from other more experienced end-users that kindly share with us their knowledge.

Thanks again for taking the time to give us such accurate and detailed answers.

6 Likes