# Using Custom Backtest to find Min/Max MAE/MFE per Loosing/Winning Trades

I am trying to use a Custom backtest procedure and output Min/Max MAE/MFE per loosing and winning trades. My idea is to get the nature of loosing trades and determine stop points.
The strategy is intraday based. So my estimate was that I would get the above values during the day since it is running on 1min data.

Here is my backtest proc:

``````SetCustomBacktestProc("");
if (Status("action") == actionPortfolio) {
bo = GetBacktesterObject();
bo.Backtest();
winningMAE[0] = 0;
winningMFE[0] = 0;
loosingMAE[0] = 0;
loosingMFE[0] = 0;
wi = 0;
li = 0;
wi++;
}
else {
li++;
}
}
--wi;
--li;
winningMAE = Sort(winningMAE, first=0, last=wi);
winningMFE = Sort(winningMFE, first=0, last=wi);
loosingMAE = Sort(loosingMAE, first=0, last=li);
loosingMFE = Sort(loosingMFE, first=0, last=li);
}
``````

I have the following output. And it can be seen that most values are 0 which is not as expected:

I would also like a Mean function but there is only for MA, is there a way to get array means without using custom looping proc?

If you want to store to a custom array then rather use Matrices as they are independent from 1-dim array of length BarCount.

``````m = MA( Close, 20 );
Buy = Cross( Close, m );
Sell = Cross( m, Close );
Short = Cover = 0;

/// Storing separate winning/losing MAE/MFE of closed trades to 2-dim array
/// And getting separate winning/losing min/max MAE/MFE of closed trades
/// by fxshrat@gmail.com
SetCustomBacktestProc("");
if (Status("action") == actionPortfolio) {
bo = GetBacktesterObject();
bo.Backtest();

wi = li = 0;
wi++;
else
li++;
}

mat1 = Matrix(Max(1,wi), 2);// winning
mat2 = Matrix(Max(1,li), 2);// losing

wi = li = 0;
wi++;
} else {
li++;
}
}

sort1 = MxSort(mat1, 1);
sort2 = MxSort(mat2, 1);
//_TRACE(MxToString(sort1));
//_TRACE(MxToString(sort2));

last_wi = MxGetSize(sort1, 0)-1;
last_li = MxGetSize(sort2, 0)-1;

// Winning ones
Min_MAE_wi = sort1[0][0];
Max_MAE_wi = sort1[last_wi][0];
Min_MFE_wi = sort1[0][1];
Max_MFE_wi = sort1[last_wi][1];

// Losing ones
Min_MAE_li = sort2[0][0];
Max_MAE_li = sort2[last_li][0];
Min_MFE_li = sort2[0][1];
Max_MFE_li = sort2[last_li][1];

// Winning ones
// Losing ones
}
``````

If you do not want to store results to array then you do not need Matrix usage at all to get min/max MAE/MFE. You would just need Min/Max functions.

``````/// @link https://forum.amibroker.com/t/using-custom-backtest-to-find-min-max-mae-mfe-per-loosing-winning-trades/15376/2
/// Getting separate winning/losing min/max MAE/MFE of closed trades
/// by fxshrat@gmail.com
SetCustomBacktestProc("");
if (Status("action") == actionPortfolio) {

bo = GetBacktesterObject();
bo.Backtest();

wi = li = 0;
Min_MAE_wi = Min_MFE_wi = Min_MAE_li = Min_MFE_li = 1e9;
Max_MAE_wi = Max_MFE_wi = Max_MAE_li = Max_MFE_li = -1e9;
wi++;
} else {
li++;
}
}

// Winning ones
// Losing ones
}
``````

To get mean of MAE/MFE just sum up within loop and then divide by wi/li.

4 Likes

Hey fkshrat, thanks again for your solution.
I also want to understand what I am doing wrong.
So when we create any array in AFL, it automatically becomes BarCount - 1 length?
And if yes, then here I was using wi & li index variables to access the first(0) and last(wi/li) elements. So I am failing to understand what I was doing wrong in my solution?

I hope to improve my understanding. Regards.

Before anything else, you seriously need to read (yes READ) this: Understanding how AFL works.

DO NOT jump to advanced concepts without understanding basics. Three days back you had trouble understanding this! Today you're discussing on "Custom Backtest".

What fxshrat explained, you would've easily understood if your basics were clear? Please check whether "Help Vampire" syndrome is getting heavy on you!

I see that but nowhere it mentions that what will be the initial size of the array when one is declared. I am talking about custom arrays. Since it is not mentioned, I assume that custom array length will be the same as the size of data I insert.

Then fxshrat wrote that: "Use matrix to avoid BarCount -1 length limitations".
So even if all custom arrays are automatically made to BarCount - 1 (after filling the remaining data points with Null) I still cannot figure out why my code went wrong since I am ONLY relying on the maximum data inserted index to access value.

I also appreciate such great solutions given pre-made to me but I would like to know the internals myself.

once again... in AFL all 1-dim arrays have length BarCount.

The article of "Understanding how AFL works" mentions it already:

All array indices in AFL are zero-based, i.e. counting bars starting from bar 0 (oldest). The latest (newest) bar has an index of (BarCount-1). The 10-element array will have BarCount = 10 and indices going from 0 to 9 as shown below

In your case also e.g. WinningMAE is not of length `wi` but is of length `BarCount` as well as all of your other arrays of your first post.

Then... you iterate trade list (from first closed trade to last closed trade) but not from `0` to `Barcount-1`.

`wi/li` may be length `BarCount` but it is highly unlikely depending on number of trades and on if statement being true.

So LastValue() function will access last element of array of length `Barcount`. Lastvalue does access element at bar-index `BarCount-1` but not at indexes `wi/li`.

Also again.... you do not need to create any array to get the output you are after.

``````m = MA( Close, 20 );
Buy = Cross( Close, m );
Sell = Cross( m, Close );
Short = Cover = 0;

/// Getting separate winning/losing min/max MAE/MFE of closed trades
/// And getting separate mean of winning/losing MAE/MFE
/// by fxshrat@gmail.com
SetCustomBacktestProc("");
if (Status("action") == actionPortfolio) {

bo = GetBacktesterObject();
bo.Backtest();

wi = li = 0;
csMAE_wi = csMFE_wi = csMAE_li = csMFE_li = 0;
Min_MAE_wi = Min_MFE_wi = Min_MAE_li = Min_MFE_li = 1e9;
Max_MAE_wi = Max_MFE_wi = Max_MAE_li = Max_MFE_li = -1e9;
Min_MAE_wi = Min(Min_MAE_wi, MAE);
Min_MFE_wi = Min(Min_MFE_wi, MFE);
Max_MAE_wi = Max(Max_MAE_wi, MAE);
Max_MFE_wi = Max(Max_MFE_wi, MFE);
wi++;
} else {
Min_MAE_li = Min(Min_MAE_li, MAE);
Min_MFE_li = Min(Min_MFE_li, MFE);
Max_MAE_li = Max(Max_MAE_li, MAE);
Max_MFE_li = Max(Max_MFE_li, MFE);
csMAE_li += MAE;
csMFE_li += MFE;
li++;
}
}

// Winning ones
// Losing ones
}
``````

1 Like

I used the storing version and added mean calculations. Just my little enhancement:

``````function calcMean(array, total) {
s = 0;
for (i = 0; i <= total; i++)
s += array[i];
return s/total;
};

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

wi = li = 0;
wi++;
else
li++;
}

mat1 = Matrix(Max(1,wi), 2);// winning
mat2 = Matrix(Max(1,li), 2);// losing

wi = li = 0;
wi++;
} else {
li++;
}
}

sort1 = MxSort(mat1, 1);
sort2 = MxSort(mat2, 1);
//_TRACE(MxToString(sort1));
//_TRACE(MxToString(sort2));

last_wi = MxGetSize(sort1, 0)-1;
last_li = MxGetSize(sort2, 0)-1;

// Winning ones
Min_MAE_wi = sort1[0][0];
Max_MAE_wi = sort1[last_wi][0];
Min_MFE_wi = sort1[0][1];
Max_MFE_wi = sort1[last_wi][1];

// Losing ones
Min_MAE_li = sort2[0][0];
Max_MAE_li = sort2[last_li][0];
Min_MFE_li = sort2[0][1];
Max_MFE_li = sort2[last_li][1];

// Winning ones
bo.AddCustomMetric("Mean Winning MAE", calcMean(MxGetBlock(sort1, 0, last_wi, 0, 0, True), last_wi));
bo.AddCustomMetric("Mean Winning MFE", calcMean(MxGetBlock(sort1, 0, last_wi, 1, 1, True), last_wi));
// Losing ones
bo.AddCustomMetric("Mean Loosing MAE", calcMean(MxGetBlock(sort2, 0, last_li, 0, 0, True), last_li));
bo.AddCustomMetric("Mean Loosing MFE", calcMean(MxGetBlock(sort2, 0, last_li, 1, 1, True), last_li));
}
``````

You do not need to apply looping at all to create mean of matrix vectors.
You just need Matrix multiplication using @ operator.
Sum of elements in column vectors of matrix X ->
Row vector V of ones (with column dimension == row dimension of matrix X) @ matrix X.
Then division by rownum X.

``````V @ X / RownumX
``````

Results in row vector (`1xn`) if X is `m x n` matrix and n > 1. Otherwise matrix of size `1x1`.

Also I would appreciate if you do not remove links and creator of working code since AFAICS it wasn't you who got it done properly but actually (as far as my memory serves me right) it has been my time spent for complete rewrite.

``````m = MA( Close, 20 );
Buy = Cross( Close, m );
Sell = Cross( m, Close );
Short = Cover = 0;

/// original code
/// fixed rewrite:
/// Storing separate winning losing MAE/MFE of closed trades to 2-dim array
/// And getting separate winning/losing min/max MAE/MFE of closed trades
/// And getting separate mean of winning/losing MAE/MFE
/// by fxshrat@gmail.com
SetCustomBacktestProc("");
if (Status("action") == actionPortfolio) {
bo = GetBacktesterObject();
bo.Backtest();

wi = li = 0;
else								li++;
}

mat1 = Matrix(Max(1,wi), 2);// winning
mat2 = Matrix(Max(1,li), 2);// losing

wi = li = 0;
mat1[wi][0] = MAE;
mat1[wi][1] = MFE;
wi++;
} else {
mat2[li][0] = MAE;
mat2[li][1] = MFE;
li++;
}
}

sort1 = MxSort(mat1, 1);
sort2 = MxSort(mat2, 1);
//_TRACE(MxToString(sort1));
//_TRACE(MxToString(sort2));

rownum1 = MxGetSize(sort1, 0);
rownum2 = MxGetSize(sort2, 0);
last_wi = rownum1-1;
last_li = rownum2-1;

// Winning ones
Min_MAE_wi = sort1[0][0];
Max_MAE_wi = sort1[last_wi][0];
Min_MFE_wi = sort1[0][1];
Max_MFE_wi = sort1[last_wi][1];
// Mean winning
cs_mat1 = Matrix(1, rownum1, 1) @ sort1;
mMAE_wi = cs_mat1[0][0]/rownum1;
mMFE_wi = cs_mat1[0][1]/rownum1;

// Losing ones
Min_MAE_li = sort2[0][0];
Max_MAE_li = sort2[last_li][0];
Min_MFE_li = sort2[0][1];
Max_MFE_li = sort2[last_li][1];
// Mean losing
cs_mat2 = Matrix(1, rownum2, 1) @ sort2;
mMAE_li = cs_mat2[0][0]/rownum2;
mMFE_li = cs_mat2[0][1]/rownum2;

// Winning ones
// Losing ones
}
``````
4 Likes

Hey, my removing link wasn't intentional. I pasted your code in a long AFL system of mine and I forgot to copy it back after making the modifications.
Thanks again for the suggestions.

1 Like

Ha ha ha.... Yeah, everyone can see the future....

Each and every question that you ask, to answer that, someone has to spend a considerable amount of time to code, test, experiment and post. So, mostly nothing is "prep-made".

If you do not respect other's time, don't expect that in reciprocal?

FYI, meaning of a term.

1 Like