# Make custom metric in backtest display results up to 4 decimal places

Please take a look at the manual (provided link in @Tomasz's post).

There is AddCustomMetric method for backtester object (having six function arguments)

So

``````bo = GetBacktesterObject(); // Retrieve the interface to portfolio backtester
bo.Backtest( 1 );

//...

bo.AddCustomMetric( Title, Value, LongOnlyValue, ShortOnlyValue, DecPlaces, Combine);

//....
``````

And there is AddCustomMetric method for trade object (having three function arguments)

So that's the one you need for trade list loop.

``````trade.AddCustomMetric( "VolumeGain", Lookup( symbolVol, trade.EntryDateTime ), DecPlaces = 4 );
``````
2 Likes

Thank you @fxshrat and @Tomasz

I did read the docs after Tomasz pointed out the function but jumped to the wrong conclusion on seeing the first AddCustomMetric() which had 6 arguments. I thought the error message was wrong. Well, at least this mistake will serve as a reminder to future users to take note. Will be more careful next time.

Thank you for taking the time to answer.

Hi.
Is it possible to define different decimals based on tickers?

``````DecPlaces = IIf( StrFind( Name(), "JPY" ), 3, 5)
``````

This always gives 5 decimals. even for JPY pairs.

``````/// INCOMPLETE SNIPPET!!

{
DecPlaces = IIf(StrFind(trade.Symbol, "JPY" ), 3, 5);
}

/// INCOMPLETE SNIPPET!!

``````

For AddCustomMetric methof of backtester object you would have to do it differently by iterating traded symbol list.

1 Like

I found the below code online in the Guide and have added it my code. I will add the "Expectancy" calc to the Summary Report but I would like it to display 4 or 6 decimals. I added DecPlaces=4 in the snippet and when run expectancy number is still produced to two decimals and the number "4" is to the right, under the Long Trades column.

I must be missing something obvious.

Thanks,

Mike

``````
SetCustomBacktestProc("");

/* Now custom-backtest procedure follows */

if( Status("action") == actionPortfolio )
{
bo = GetBacktesterObject();

bo.Backtest(); // run default backtest procedure

SumProfitPer100Inv = 0;

// iterate through closed trades first
{
// here we sum up profit per \$100 invested
}

// iterate through eventually still open positions
{
}

bo.AddCustomMetric( "Expectancy (per \$100 inv.)", expectancy2 , DecPlaces=4);

}

``````

You need to look at the documentation for the backtester object's version of AddCustomMetric, not the trade object's version of the AddCustomMetric function. See below.

bool AddCustomMetric( string Title*, variant* Value*, [optional] variant* LongOnlyValue*, [optional] variant* ShortOnlyValue , [optional] variant DecPlaces = 2, [optional] variant CombineMethod = 2 )

This method adds custom metric to the backtest report, backtest "summary" and optimization result list. Title is a name of the metric to be displayed in the report, Value is the value of the metric, optional arguments LongOnlyValue, ShortOnlyValue allow to provide values for additional long/short-only columns in the backtest report. DecPlaces argument controls how many decimal places should be used to display the value. The last CombineMethod argument defines how custom metrics are combined for walk-forward out-of-sample summary report

Wiithout adding "DecPlaces=4", the code produces the correct result on bottom of the bactest report. However, if I add the optional field, "DecPlaces=4", it still displays the calc to 2 decimal places and displays a second number, 4.0. I do not understand your comment to look at the documentation.

Matt..I appreciate your help. I have reviewed the doc you posted but I am using it to display the 4 decimals in the Backtest Report, not a trade report, and it still only displays two decimals. What has me confused is that the Exepctancy is being calculated and listed on the Backtest report but only to 2 decimals. It seems like it would be a simple to add the add optional field DecPlaces=4 but I cannot get it.

Thanks,

Mike

Please re-read following two posts of this very same thread carefully and slowly (you have all the time in the world so don't rush).

They already explain everything you need to know to correct your single line of code.

``````bo.AddCustomMetric( "Expectancy (per \$100 inv.)", expectancy2 , Null, Null, DecPlaces=4);
``````

Matt & FXSHRAT,

Thank you both for your help & patience! I was reading posts but taking Optional as Optional to include. My mistake.

Thank you,

Mike

Optional is optional, but you can NOT SKIP arguments if LATER along the line is the argument that you want to include. How on earth program would guess which argument you wanted to pass if you skipped randomly some of them in the middle. The order must be kept. Absolute position too. You can skip only AT THE END, not in the middle.

I've noticed in the past that some people seem to assume that they can use named arguments similar to the way it's done in Visual Basic and some other languages: https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/procedures/passing-arguments-by-position-and-by-name. Obviously this mechanism is not supported by AFL, but not everyone realizes that.

"Assume" . Assumptions are the root of all evil. Why would anyone assume that is beyond my imagination. If AFL is syntactically similar to any language it would be C/C++ or JavaScript. Neither of which has "named arguments".

AFL is NOT similar to Visual Basic in anything. There is absolutely NOTHING in AFL that would suggest similarity to MS Basic.

And no, don't ask for "named arguments". They are evil and ugly.

By the way, people writing code like this:

``````// WRONG - assignment inside is useless, does nothing except of using extra CPU cycles
bo.AddCustomMetric( "Expectancy (per \$100 inv.)", expectancy2 , Null, Null, DecPlaces=4);
``````

are making MISTAKE. There should BE NO ASSIGNMENT in function call arguments.

Should be this:

``````// CORRECT - DO NOT place useless assignments inside function calls
bo.AddCustomMetric( "Expectancy (per \$100 inv.)", expectancy2 , Null, Null, 4);
``````

People don't understand that the names in function PROTOTYPES are for DOCUMENTATION purposes only - so you know immediately from function signature that function accepts given input like that `MyFunction( text )` accepts text argument.

But this DOES NOT mean that you should type `text` variable like crazy when you call the function. You should just pass actual VALUE, not the name of argument and for heavens' sake don't make assignments:

``````MyFunction("actual stringto be passed"); // Correct.  Just the value, nothing else
``````

Do NOT do that:

``````// WRONG - assignment inside function call is USELESS does not DO anything purposeful and is just nonsense
MyFunction( text= "actual stringto be passed"); // DO NOT do that
``````
5 Likes

In the online Guide, there are three methods to calculate Expectancy. I have edited the first example with your help and it now produces the stat to 4 decimals. Thank you!
I just tried the second example, posted below, and it runs, flashes "Error 15 Endless loop detected" , I acknowledge the error and the script finishes, producing the correct 4 decimal results.

The error is generated by the first "for" loop.

The base code is the same in Example 1 and Example 2 so I am not sure why it would throw the error.

Thanks and I apologize for the coding mistakes. I carry over some coding form R, as Matt commented, and it creates some problems with AFL.
Thanks for all your help, support, and a great product!

Mike

``````SetCustomBacktestProc("");

/* Now custom-backtest procedure follows */

if( Status("action") == actionPortfolio )
{
bo = GetBacktesterObject();

bo.Backtest(); // run default backtest procedure

SumProfitPer100Inv = 0;

// iterate through closed trades first
{
// here we sum up profit per \$100 invested
}

// iterate through eventually still open positions
{
}

bo.AddCustomMetric( "Expectancy (per \$100 inv.)", expectancy2, Null, Null, 4 );

}

``````

You may disagree but I use a lot of named parameters in Amibroker afl code. They work as long as I use them in the right order in Amibroker. While they may be useless to the machine, they are useful to the human reader. Named parameters are more descriptive. Maybe there are other programmers who agree with me. I also use named parameters a lot in python.

I hope you have no intention to take away this Amibroker "feature". I like it, even if it is an accidental "feature" which you dislike.

``````bo.AddCustomMetric( "Expectancy (per \$100 inv.)", expectancy2 , Null, Null, /*DecPlaces*/ 4 );
``````
1 Like

To further clarify: these ARE NOT named arguments but are assignments.

This thing too confused me at first, as there is a lot of old code examples on the internet that use them without the proper purpose (as you wrote, they seem to be used primarily for self-documentation)
I have seen them employed in a "useful" way only in sporadic cases when these assigned variables are reused later in the code.

2 Likes

Assignments are not named arguments.

You kind of can.... by using (comma separated) string as function argument.
`"ArgName1,ArgName2,ArgName3,...,ArgNameN"`
And order may be different (as well as number of substrings), see example below

``````// Example 1
// https://forum.amibroker.com/t/make-custom-metric-in-backtest-display-results-up-to-4-decimal-places/14964/22
function fun_named_args(str_list) {
vg1 = VarGet("Arg1");
vg2 = VarGet("Arg2");
vg3 = VarGet("Arg3");
//
str_list = StrReplace(str_list," ", "");
str_list = ","+StrToLower(str_list)+",";
find1 = StrFind(str_list, ",arg1,") AND
typeof(vg1) == "number" AND
NOT IsNull(vg1);
find2 = StrFind(str_list, ",arg2,") AND
typeof(vg2) == "number" AND
NOT IsNull(vg2);
find3 = StrFind(str_list, ",arg3,") AND
typeof(vg3) == "number" AND
NOT IsNull(vg3);
//
if ( find1 )	funArg1 = vg1;
else			funArg1 = 1;// Default value
if ( find2 )	funArg2 = vg2;
else			funArg2 = 2;// Default value
if ( find3 )	funArg3 = vg3;
else			funArg3 = 3;// Default value
// just a sample result
result = 1*funArg1+2*funArg2+4*funArg3;
return result;
}

Arg1 = 5;
Arg2 = 6;
Arg3 = 7;

// "default order"
res1 = fun_named_args("Arg1,Arg2,Arg3");
printf("result1: %g\n", res1);

// different order
res2 = fun_named_args("Arg3,Arg1,Arg2");
printf("result2: %g\n", res2);

// just two "arguments" and
// using one default value for Arg3
res3 = fun_named_args(" Arg2 , Arg1");
printf("result3: %g\n", res3);

// using three defaults
res4 = fun_named_args("");
printf("result4: %g\n", res4);

//etc
``````

Or like this form

where list would be `"ArgName1:val1,ArgName2:val2,ArgName3:val3,...ArgNameN:valN"`

``````// Example 2
// https://forum.amibroker.com/t/make-custom-metric-in-backtest-display-results-up-to-4-decimal-places/14964/22
function fun_named_args2(str_list) {
str_list = StrReplace(str_list," ", "");
str_list = ","+StrToLower(str_list);
find1 = StrFind(str_list, ",arg1");
find2 = StrFind(str_list, ",arg2");
find3 = StrFind(str_list, ",arg3");
str_rep = StrReplace(str_list, ",arg1", ",a_arg1");
str_rep = StrReplace(str_rep, ",arg2", ",b_arg2");
str_rep = StrReplace(str_rep, ",arg3", ",c_arg3");
str_sort = StrSort(str_rep, 1);
//printf(str_sort);
//
if ( find1 ) {	funArg1 = StrExtract(str_sort,1);
funArg1 = StrToNum(StrExtract(funArg1,1,':'));
} else			funArg1 = 1;// Default value
if ( find2 ) {	funArg2 = StrExtract(str_sort,2);
funArg2 = StrToNum(StrExtract(funArg2,1,':'));
} else			funArg2 = 2;// Default value
if ( find3 ) {	funArg3 = StrExtract(str_sort,3);
funArg3 = StrToNum(StrExtract(funArg3,1,':'));
} else			funArg3 = 3;// Default value
// just a sample result
result = 1*funArg1+2*funArg2+4*funArg3;
return result;
}

// "default order"
res1 = fun_named_args2("Arg1:5,Arg2:6,Arg3:7");
printf("result1: %g\n", res1);

// different order
res2 = fun_named_args2("Arg3:7,Arg1:5,Arg2:6");
printf("result2: %g\n", res2);

// just two "arguments" and
// using one default value for Arg3
res3 = fun_named_args2(" Arg2 : 6 , Arg1 : 5");
printf("result3: %g\n", res3);

// using three defaults
res4 = fun_named_args2("");
printf("result4: %g\n", res4);

//etc
``````

This is not recommendation doing such things. Just showing that you kind of simulate named arguments.

2 Likes

@fxshrat, you can do magic with Amibroker. haha.

Let me re-iterate

First and foremost
Arguments in AFL should be supplied in order specified in the documentation.

Second
The notation `arg = 1` used in function prototypes in the docs is standard notation for default values of arguments (used in C++ language and taken from C++). This notation has absolutely nothing to do with "named arguments" known from say Python.

AFL syntax is similar to C/C++ / JavaScript. It has absolutely nothing common with Python or VisualBasic

Third
If you embed assignment in the function call parameter list - it remains just an assignment, nothing more. It does not become "named argument". It is not any special feature.

It works precisely as it would work in C/C++. It works because assignments have value as well so in C/C++ you can write:

``````x = y = 7; // it works because assignment ( y = 7 ) has "value of 7"
// at this point both x and y will be 7
``````

So if you call a function in C/C++

``````printf("%d", y = 7 );
``````

It will print 7. The very same thing is happening in AFL.

Fourth:
This "feature" is not really a feature but consequence of the fact that assignments have value in C/C++ and AFL. For this reason function calls like

``````printf("%d", y = 7 );
``````

work and will continue to work, so there is no worry that this "feature" will be removed.

You can use it to "self-document" your code of course, but the point is that the name of variable being assigned does not matter at all. I can call it anyway I like and it does not modify how function call works (UNLIKE in languages that have 'named parameters').

``````printf("%d", super_important_variable_but_it_does_not_matter = 7 );
``````

I personally don't like the idea of writing that way, because this assignment does nothing useful and is not very good for "self-documentation" as names may: a) be misleading if arguments are used in incorrect order; b) suggest that names actually mean something .

BTW: if you type the function argument names appear in a tooltip and are highlighted as you type so, there should be no problem in knowing which argument is which and what meaning it has.

7 Likes