Mixing Mid-Level CBT with custom trade metrics

Hello,

I add several custom trade metrics to some of my trading systems.
The cbt code I use is similar to the one included in kb (https://www.amibroker.com/kb/2014/11/20/how-to-show-indicator-values-in-backtest-trade-list/ ).

In one of those systems I decided to add custom position size calculation, so I choose the mid-level cbt code snippet.

Backtesting results are ok, but one of the custom trade metrics I use is calculated using trade exit price.
When I use the mid-level approach, the trade.exitprice property of the currently open positions is zero.
I believe this happens since PostProcess() method actually closes the open positions at the end of the backtest process, but the custom metrics code is executed before PostProcess() method.

Am I doing something wrong or is there a more appropriate way to do this?

Below are simplified versions of the code (the code doesn't actually calculate any meaningful trade metric, but it just to ilustrate this issue)

High Level CBT (custom trade metric only)

// your trading system here
Buy = Cross( MACD(), Signal() );
Sell = Cross( Signal(), MACD() );
Short=Cover=0;

SetPositionSize(1,spsShares);

SetCustomBacktestProc( "" );

if ( Status( "action" ) == actionPortfolio )
{
    bo = GetBacktesterObject();
    // run default backtest procedure without generating the trade list
    bo.Backtest( True );

    // iterate through closed trades
    for ( trade = bo.GetFirstTrade( ); trade; trade = bo.GetNextTrade( ) )
    {
        // display custom metric
        
        trade.AddCustomMetric( "Exit Price", trade.ExitPrice );
    }

    // iterate through open positions
    for ( trade = bo.GetFirstOpenPos( ); trade; trade = bo.GetNextOpenPos( ) )
    {
        // display custom metric
        
        trade.AddCustomMetric( "Exit Price", trade.ExitPrice  );
    }

    // generate trade list
    bo.ListTrades( );
}

Mid Level CBT with custom trade metrics


// your trading system here
Buy = Cross( MACD(), Signal() );
Sell = Cross( Signal(), MACD() );
Short=Cover=0;

SetPositionSize(1,spsShares);

SetCustomBacktestProc("");

if (Status("action") == actionPortfolio) 
{
    bo = GetBacktesterObject();	//  Get backtester object
    bo.PreProcess();	//  Do pre-processing (always required)

    for (i = 0; i < BarCount; i++)	//  Loop through all bars
    {
        for (sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) )
        {	
             sig.PosSize=sig.PosSize;
        }	
        
        bo.ProcessTradeSignals( i );	//  Process trades at bar (always required)
    }	  
      
     // iterate through closed trades
    for ( trade = bo.GetFirstTrade( ); trade; trade = bo.GetNextTrade( ) )
    {
        // display custom metric
        
        trade.AddCustomMetric( "Exit Price", trade.ExitPrice );
    }

    // iterate through open positions
    for ( trade = bo.GetFirstOpenPos( ); trade; trade = bo.GetNextOpenPos( ) )
    {
       // display custom metric
        
        trade.AddCustomMetric( "Exit Price", trade.ExitPrice  );
    }
    
    bo.PostProcess();	//  Do post-processing (always required)
}


high%20level mid%20level

2 Likes

Positions that are still open at the end of the backtest are never closed, even by PostProcess(). Therefore, they will not have an exit price. The value shown in the "Ex. Price" column is actually just the price from the last bar of the backtest. If you really need to calculate your custom metric for open trades, you could use Foreign() to retrieve the Close price for those trades or you could save the prices in a static variable in your phase 1 code and retrieve it in the CBT.

3 Likes

Thanks @mradtke.

Yes, static variables should be the more straighforward solution to this.

I just wanted to check if I was missing something, since in High Level CBT "mode" the trade.ExitPrice property is available (it's not zero).