Report charts type "dispersion"?

Hi, I'd really appreciate any help because this is way beyond me. I'm trying to learn CBT, the High level, and I want to create custom metrics and report charts.

I've managed to create some really cool things, but when I try to create "scatter" charts (sorry if that's not the right term), I'm stuck. Basically, I'm trying to create a report chart that looks like this:

It would be a report chart where the X-axis shows the trade number and the Y-axis shows the result. I'm trying to make two charts, one for winning trades and one for losing trades. This is what I'm putting in the custom procedure path:

SetCustomBacktestProc("");
if( Status("action") == actionPortfolio ) 
{ 
    bo = GetBacktesterObject(); 
    bo.Backtest();  
    stat = bo.GetPerformanceStats(0); 
	
	prof = 0;
	
    for (trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade()) 
    {                   
        prof = trade.GetProfit(); 
    }                    
    
    StaticVarSet("prof", prof);
}

And this is what I'm putting in the report chart:

Profit    = StaticVarGet("prof"); 

Title = EncodeColor(colorBlack) + " Profit: " + NumToStr( Profit, 1.0, True);

Plot(Profit, "Profit",colorRed, styleLine);

This is what I've gotten so far:

As you can see, I'm very, very far from where I want to be, but I'm unable to progress any further.

I've read everything I could find about CBT, but my level is very low.

It is beyond your current skill level. First you need to understand absolute basics such as that what is a scalar value and that when you are overwriting variable the old value is gone. So your “profit” is just one scalar value of profit or very last trade. You can’t have chart from one number.

Thanks, Dr. Tomasz.
I know it's far beyond my skill level, but I'm really trying.

Look, in another AFL file I use as a backtester, I've put this:

SetCustomBacktestProc("");
if( Status("action") == actionPortfolio ) //garantizar que el procedimiento solo se ejecute durante la segunda fase
{ 
    bo = GetBacktesterObject(); //antes de poder hacer cualquier otra cosa, se necesita una copia del objeto Backtester:
        
    bo.Backtest();//Ejecutar el backtest por defecto
    
    stat = bo.GetPerformanceStats(0); // Obtener el objeto de estadísticas para todas las operaciones
    
 	//Nº días sin alcanzar nuevos máximos durante backtest
	eq = bo.EquityArray();
	DrawdownDuration = HighestBars(eq); //el número de barras desde el último máximo de equidad
}

StaticVarSet("DrawdownDuration", DrawdownDuration);

Then, in the report chart, I put this:

DrawdownDuration    = StaticVarGet("DrawdownDuration"); 
Plot(DrawdownDuration, "DrawdownDuration",colorRed, styleLine);

And I can see the DrawdownDuration variable with a value that isn't overwritten. I can see its evolution.

I understand you don't have time to waste on newbies, but please tell me one thing: is it possible to create a report chart (not a window chart) like the one I'm trying to create?

If so, I'll keep trying until I succeed.

I admit there are some very basic things I still don't quite understand. Everything is working against me: the English language, which I have to keep translating, the programming, and the AFL language... but I won't give up!

In you first attempt, as Tomasz said, prof contains only one value, in the "for"
you are overwritting it.

In the second example HighestBars return an array

Thank you very much awilson.
I understand that when looping through all the trades, the last value is taken.
Let's see if I can figure out how to get the value of each trade as if it were an array, so I can then plot it.

"Report chart" is nothing else but chart. You should just develop your code as NORMAL CHART, if it works as Normal Chart, it will also work as report chart, because report charts are exactly as regular charts, just run in the context of ~~~EQUITY symbol (so selected symbol is ~~~EQUITY) and output is redirected to image file (PNG), not screen. There is nothing else to it.

Develop your code as REGULAR CHART. Just select ~~~EQUITY as current ticker. Once you have working chart, you can use it as "report chart" without any changes. There is nothing "special" about report chart.

And definitely custom backtest code does NOT BELONG to chart.

Look at existing code in the "Report charts" folder. You can just insert them as any other chart. They just work. You just need to select ~~~EQUITY as current symbol.

I'm making progress!

I've already managed to create an AFL file that I'm adding to the CBT path, and through the log (first time I've used it!) I can see that it's created for each trade, along with its result.

SetCustomBacktestProc(""); 

if( Status("action") == actionPortfolio ) 
{ 
    bo = GetBacktesterObject(); 
    bo.Backtest(); 
    
    StaticVarRemove( "ResultTrade*" ); 

    NumTrade = 0; 

    for (trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade()) 
    {                    
        NumTrade++; 
        resultado = trade.GetProfit(); 
     
        StaticVarSet( "ResultTrade" + NumTrade, resultado ); 

        _TRACE( "CONTADOR: " + NumTrade + " | BENEFICIO: " + resultado );
    }                        
    
    StaticVarSet( "TotalTradesContados", NumTrade );
    
    _TRACE( "--- Total de trades guardados: " + NumTrade + " ---" ); 
}

My next step is to create the Report Chart so I can see the results. I've managed to see something with this AFL:

// 1. Recuperamos el total
total = StaticVarGet( "TotalTradesContados" );

// 2. Creamos un array del tamaño de las barras del gráfico
// Usamos Null para que las barras donde no hay trades no pinten nada
resArray = Null;

if( total > 0 )
{
    for( i = 1; i <= total AND i < BarCount; i++ )
    {
        // Recuperamos el valor de la memoria
        res = StaticVarGet( "ResultTrade" + i );
        
        // IMPORTANTE: Metemos el trade 1 en la barra 1, el 2 en la barra 2...
        // Así el eje X del gráfico se convierte en el "Número de Trade"
        resArray[ i ] = res;
    }
}

// 3. Dibujamos el gráfico en el panel
Title = "EJE X: NUMERO DE TRADE | EJE Y: RESULTADO $";

// Dibujamos el histograma
Plot( resArray, "Resultado", IIf( resArray > 0, colorGreen, colorRed ), styleHistogram | styleThick | styleOwnScale );

// Dibujamos una línea en cero para separar ganancias de pérdidas
Plot( 0, "", colorLightGrey, styleLine );

Far from what I'm aiming for, but it's something!

I am also trying to create an XY Scatter plot with this AFL (but without any results).

total = StaticVarGet( "TotalTradesContados" );

if( total > 0 )
{
    for( i = 1; i <= total; i++ )
    {
        res = StaticVarGet( "ResultTrade" + i );
        
        colorPunto = IIf( res > 0, colorGreen, colorRed );

        XYChartAddPoint( "MisResultados", "Trade #" + i, i, res, colorPunto );
    }

    XYChartSetAxis( "MisResultados", "Numero de Operacion", "Beneficio / Perdida ($)" );
}

Remember that my goal is to achieve a Report Chart like this:

Now I really deserve a little help, huh? I'm working really hard, reading all the related topics I can find, and of course... with my friend Gemini too.

Come on, please, give a newbie a hand!

Yonsi72 , re-read last comment of Tomasz.
You are taking the long path, re-ineventig the wheel


the code below is only to show what each field means in Equity

```

Plot( ee = Foreign( "~~~EQUITY", "C"), "Equity", colorBlue ); //colorAqua
Plot( lt = Foreign( "~~~EQUITY", "O"), "Long Trades", colorGreen);
Plot( st = Foreign( "~~~EQUITY", "H"), "Short Trades", colorRed);
Plot( Foreign( "~~~EQUITY", "I"), "Open Positions", colorOrange, styleOwnScale | styleHistogram);
Plot( st - lt, "Short - Long", colorLavender, styleArea | styleOwnScale ); //colorGold

```

Thanks, Awilson.

Honestly, I don't quite understand the relationship between the two static variables I'm creating with the CBT AFL (which contain the trade number and its result) and Equity.

I also don't quite understand if what I'm trying to do can be done in a Report Chart or only in a chart that comes from an Explorer.

Are you trying to tell me that with Equity I can achieve what I'm looking for? Without needing to calculate static variables?

I'm a bit lost, to be honest, but I'm still trying...

see this topic

Equity Curve - What do Open and High Array Values Represent? - AFL Programming - AmiBroker Community Forum

and

also,
Run a system to an equity symbol - AFL Programming - AmiBroker Community Forum

if required

AmiBroker Knowledge Base » How to create copy of portfolio equity?

Thanks nsm51, I've looked at the topics you suggested. They're all about Equity.

Please excuse my lack of knowledge, but I don't understand how to generate a chart from Equity that shows the number of trades on the X-axis and the result of each trade on the Y-axis.

I don't understand what you're trying to say; I'm trying to figure it out, but I can't. Sorry.

After you run a backtest the symbol ~~~EQUITY is updated/created with the results

Open field will have Long Trades Equty
High field will have Short Trades Equity

For each day(period), with this 2 data can't you achieve what you want?

Dear awilson,
my goal is to achieve a Report Chart like this:

The X-axis (horizontal) displays the trade number; it's not a daily bar, but a number (the one corresponding to each trade).

The Y-axis (vertical) shows the result of each trade. If it was positive, it will be above 0, and if it was negative, below 0.

I can't figure out how to do that with what you're telling me. I don't understand how to get the number for each trade and relate it to its result.

Ok I musunderstood you

Try something like this

// XY chart coding example
// This formula generates 2 X-Y scatter charts that display relationship between
// final trade profit and MFE and MAE

Buy = Cross( MACD(), Signal() );
Sell = Cross( Signal(), MACD() );
Short = False;
Cover = False;

Eq = Equity( 1 ); // single-security equity  this evaluates stops (if you use them) and removes extra signals

Entry = Buy OR Short;
EqAtEntry = ValueWhen( Entry, Eq );
Profit = 100 * ( Eq - EqAtEntry ) / EqAtEntry; // percent profit

// MAE and MFE below use CLOSING equity, MAE and MFE in the report use high/low price
MAE = 100 * ( LowestSince( Entry, Eq ) - EqAtEntry ) / EqAtEntry; // percent MAE
MFE = 100 * ( HighestSince( Entry, Eq ) - EqAtEntry ) / EqAtEntry; // percent MAE

bi = BarIndex();

Len = bi - ValueWhen( Entry, bi );

EntryPrice = ValueWhen( Entry, BuyPrice );
// if you prefer MAE/MFE using high/low price
// uncomment the lines below (long only Version)
MAE = 100 * ( LowestSince( Entry, Low ) - EntryPrice ) / EntryPrice ; // percent MAE using low
MFE = 100 * ( HighestSince( Entry, High ) - EntryPrice ) / EntryPrice ; // percent MAE using high

Exit = Sell OR Cover;

Filter = Exit;
AddColumn( Eq, "Equity" );
AddColumn( Profit, "Profit" );
AddColumn( MAE, "MAE" );
AddColumn( MFE, "MFE" );
AddColumn( Len, "trade length" );

SetCustomBacktestProc( "" );

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

    StaticVarRemove( "ResultTradeMatrix*" );
    rows = 5000; // maximum trades expected
    ResultTrade = Matrix( rows, 1 );

    NumTrade = 0;

    for( trade = bo.GetFirstTrade(); trade AND NumTrade < rows; trade = bo.GetNextTrade() )
    {
        resultado = trade.GetProfit();
        ResultTrade[NumTrade][0] = resultado;
        _TRACE( "CONTADOR: " + NumTrade + " | BENEFICIO: " + resultado );
        NumTrade++;
    }

    StaticVarSet( "ResultTradeMatrix", ResultTrade );
    StaticVarSet( "TotalTradesContados", NumTrade );

    _TRACE( "--- Total de trades guardados: " + NumTrade + " ---" );
}
else
    if( Status( "action" ) == actionExplore )
    {
        ResultTrade = StaticVarGet( "ResultTradeMatrix" );

        if( ResultTrade )
        {
			rows = MxGetSize( ResultTrade, 0 ); 
			
			for( NumTrade = 0; NumTrade < rows; NumTrade++ )
			{
				// item text consists of two parts
				// first part (before t) is a item name displayed immediatelly on XY chart
				// second part (after t) is a tooltip hint text that is displayed in the tooltip
				// when you hover on given item
				// here we will only use hint text
				HintText = "trade " + NumTrade;
				Profit = ResultTrade[NumTrade][0];

				XYChartAddPoint( "Trade vs Profit", HintText, NumTrade, Profit, Colorred);
			}

			XYChartSetAxis( "Trade vs Profit", "[NumTrade]", "[Profit]" );

        }
        else
        {
            Error( "Could not find Trade Matrix", True );
        }
    }

First do a backtest and then a explorer

Thank you so much, awilson.

With your help, I'm really getting closer to what I'm looking for. In fact, with your latest code, I can see that by creating an Explorer, I can get an XY chart with Trades and their Profit.

My remaining question is whether it's possible to create these XY charts without creating an Explorer...

To create XY charts as AMi charts you will need to use low-level graphics

look for Using low-level graphics functions and RRG discussions will also give you some ideas

https://forum.amibroker.com/search?q=rrg

Thanks again, awilson, finally, some direction.

I've practiced a bit with Gfx to create tables in my own style like this:

So let's see if I can create an XY graph with it now.

I've achieved something very close to what I want:

But I am unable to change the X axis (i.e., what instead of indicating Date/Time, indicates the Trade Number).

This is the AFL that I put in the CBT path:

//Activación del motor personalizado
SetCustomBacktestProc( "" );
if( Status( "action" ) == actionPortfolio )
{
    //El objeto Backtester
    bo = GetBacktesterObject();
    bo.Backtest( 1 );

	// Preparación de la estructura de datos
    StaticVarRemove( "ResultTradeMatrix*" );
    rows = 5000; // Define un límite máximo de operaciones que esperas capturar
    ResultTrade = Matrix( rows, 1 ); //Crea una estructura de datos tipo matriz de 5000 filas por 1 columna. Es mucho más eficiente que un array común para manejar grandes volúmenes de datos
    NumTrade = 0; //Un contador simple que empieza en cero. Nos servirá para saber en qué fila de la matriz guardar cada trade
	
	/* El bucle de extracción (Iteración)
	trade = bo.GetFirstTrade(): Toma la primera operación de la lista.
	trade AND NumTrade < rows: Mientras exista un trade y no hayamos superado las 5000 filas...
	trade = bo.GetNextTrade(): Al terminar una vuelta, pasa a la siguiente operación.
	*/
	    for( trade = bo.GetFirstTrade(); trade AND NumTrade < rows; trade = bo.GetNextTrade() ) //Este bloque recorre cada trade que se cerró durante el backtest:
    {
        resultado = trade.GetProfit(); //Extrae el beneficio neto (en dinero) de esa operación específica
        
        ResultTrade[NumTrade][0] = resultado; //Guarda ese beneficio en la matriz, en la posición que indique el contador.
        
        NumTrade++; //Suma 1 al contador para que el siguiente trade se guarde en la siguiente fila
    }
	
	//Almacenamiento Global y Cierre
    StaticVarSet( "ResultTradeMatrix", ResultTrade ); //Convierte la matriz local en una Variable Estática
    StaticVarSet( "TotalTradesContados", NumTrade ); //Guarda el número total de trades que realmente se encontraron
   
    bo.ListTrades(); // Ahora que terminé de extraer los datos, dibuja la tabla de trades normal en el reporte
    
}

And this is the AFL I use to create the chart in a New Blank Chart:

// 1. Recuperar los datos de la memoria estática
ResultTrade = StaticVarGet( "ResultTradeMatrix" );
NumTrades = StaticVarGet( "TotalTradesContados" );

// 2. Verificar si existen datos (si corriste el backtest antes)
if( !IsNull( ResultTrade ) AND NumTrades > 0 )
{
    // Creamos un array vacío para volcar los datos de la matriz
    ProfitArray = Null; 

    // 3. Pasar los datos de la Matriz al Array para graficar
    // El eje X será el índice 'i' (número de trade)
    for( i = 0; i < NumTrades; i++ )
    {
        ProfitArray[ i ] = ResultTrade[ i ][ 0 ];
    }

    // 4. Configuración visual del gráfico
    SetChartOptions( 0, chartShowDates | chartShowArrows );
    Title = "Resultado por Operación | Total Trades: " + NumTrades;

    // Dibujar el histograma: Verde para ganancias, Rojo para pérdidas
    Plot( ProfitArray, "Profit", IIf( ProfitArray > 0, colorGreen, colorRed ), styleDots | styleThick | styleNoLine);
    
    // Añadir una línea de cero para referencia
    Plot( 0, "", colorLightGrey, styleLine );
}
else
{
    // Mensaje de error si no se ha ejecutado el backtest
    Title = "No hay datos. Por favor, ejecuta el Backtest primero para llenar la matriz.";
}

Will it be possible to change the X-axis? :thinking: :thinking:

You need to build your own X-axis

I think that's beyond me now.... :sweat_smile: