Unexpected result using ExRem

Hi All,

I'm generating a number of custom report metrics in my code that is basically using the code below. Can someone please tell me if it is alright to combine the low, medium and high level custom bactest functions like this, and if so, is the order of the calls correct in the code below, as some of the values I'm getting in the report are blank,,,

SetCustomBacktestProc("");
if (Status("action") == actionPortfolio)
{
    bo = GetBacktesterObject();    //  Get backtester object

    bo.PreProcess();    //  Do pre-processing

    for (i = 0; i < BarCount; i++)    //  Loop through all bars
    {
        for (sig = bo.GetFirstSignal(i); sig; sig = bo.GetNextSignal(i))
        {
            if (sig.IsEntry() && sig.IsLong())    //  Process long entries
                bo.EnterTrade(i, sig.Symbol, True, sig.Price, sig.PosSize);
            else
            {
                if (sig.IsExit() && sig.IsLong())    //  Process long exits
                    bo.ExitTrade(i, sig.Symbol, sig.Price);
            }
            ......
        }

        bo.HandleStops(i);    //  Handle programmed stops at this bar

        for (trade = bo.GetFirstOpenPos(); trade; trade = bo.GetNextOpenPos())
        {
            if (trade.GetProfit() >= trade.GetEntryValue())  //  If time to scale-in
            {
                scaleSize = trade.GetEntryValue() / 2;    //  Scale-in the trade
                bo.ScaleTrade(i, trade.Symbol, True, trade.GetPrice(i, "C"), scaleSize);
            }
        }

        bo.UpdateStats(i, 1);    //  Update MAE/MFE stats for bar
        bo.UpdateStats(i, 2);    //  Update stats at bar's end
    }    //  End of for loop over bars

    bo.PostProcess();    //  Do post-processing



    bo.Backtest(True);            //  Run backtests with no trade listing

    stat = bo.GetPerformanceStats(0);    //  Get Stats object for all trades

    winAvgProfit = stat.GetValue("WinnersAvgProfit");

    loseAvgLoss = stat.GetValue("LosersAvgLoss");

    for (trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade())
    {                    //  Loop through all closed trades
        prof = trade.GetProfit();        //  Get trade profit in dollars

        relProf = 0;            //  This will be profit/avgProfit as %

        if (prof > 0)            //  If a winner (profit > 0)
            relProf = prof / winAvgProfit * 100;    //  Profit relative to average
        else                //  Else if a loser (profit <= 0)
            relProf = -prof / loseAvgLoss * 100;    //  Loss relative to average

        trade.AddCustomMetric("Rel Avg Profit%", relProf);    //  Add metric
    }                    //  End of for loop over all trades

    bo.ListTrades();            //  Generate list of trades


    myMetric = <calculation using stats>;    //  Calculate new metric

    bo.AddCustomMetric("MyMetric", myMetric);    //  Add metric to display

    for (trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade())
    {
        . . . .    //  Use Trade object here
    }
    for (trade = bo.GetFirstOpenPos(); trade; trade = bo.GetNextOpenPos())
    {
        . . . .    //  Use Trade object here
    }
    myMetric = <some result from Trade object calculations>;
    bo.AddCustomMetric("MyMetric", myMetric);    //  Add metric to display


    equityValue = bo.EquityArray();

    ..........
}

Just a rushed check.

SetCustomBacktestProc("");
if (Status("action") == actionPortfolio)
{
    bo = GetBacktesterObject();    //  Get backtester object

    bo.PreProcess();    //  Do pre-processing
    
    equityValue = bo.EquityArray();

    for (i = 0; i < BarCount; i++)    //  Loop through all bars
    {
        for (sig = bo.GetFirstSignal(i); sig; sig = bo.GetNextSignal(i))
        {
            if (sig.IsEntry() && sig.IsLong())    //  Process long entries
                bo.EnterTrade(i, sig.Symbol, True, sig.Price, sig.PosSize);
            else
            {
                if (sig.IsExit() && sig.IsLong())    //  Process long exits
                    bo.ExitTrade(i, sig.Symbol, sig.Price);
            }
            ......
        }

        bo.HandleStops(i);    //  Handle programmed stops at this bar

        for (trade = bo.GetFirstOpenPos(); trade; trade = bo.GetNextOpenPos())
        {
            if (trade.GetProfit() >= trade.GetEntryValue())  //  If time to scale-in
            {
                scaleSize = trade.GetEntryValue() / 2;    //  Scale-in the trade
                bo.ScaleTrade(i, trade.Symbol, True, trade.GetPrice(i, "C"), scaleSize);
            }
        }

        bo.UpdateStats(i, 1);    //  Update MAE/MFE stats for bar
        bo.UpdateStats(i, 2);    //  Update stats at bar's end
    }    //  End of for loop over bars

    stat = bo.GetPerformanceStats(0);    //  Get Stats object for all trades

    winAvgProfit = stat.GetValue("WinnersAvgProfit");

    loseAvgLoss = stat.GetValue("LosersAvgLoss");

    for (trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade())
    {                    //  Loop through all closed trades
        prof = trade.GetProfit();        //  Get trade profit in dollars

        relProf = 0;            //  This will be profit/avgProfit as %

        if (prof > 0)            //  If a winner (profit > 0)
            relProf = prof / winAvgProfit * 100;    //  Profit relative to average
        else                //  Else if a loser (profit <= 0)
            relProf = -prof / loseAvgLoss * 100;    //  Loss relative to average

        trade.AddCustomMetric("Rel Avg Profit%", relProf);    //  Add metric
    }                    //  End of for loop over all trades


    myMetric = <calculation using stats>;    //  Calculate new metric

    bo.AddCustomMetric("MyMetric", myMetric);    //  Add metric to display

    for (trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade())
    {
        . . . .    //  Use Trade object here
    }
    for (trade = bo.GetFirstOpenPos(); trade; trade = bo.GetNextOpenPos())
    {
        . . . .    //  Use Trade object here
    }
    myMetric = <some result from Trade object calculations>;
    bo.AddCustomMetric("MyMetric", myMetric);    //  Add metric to display    
    
    bo.PostProcess();    //  Do post-processing
}

No, that is not correct. As a general rule, you should use only ONE of the high/mid/low level set of CBT functions. You most certainly should not be calling boEnterTrade() yourself (low level) and then later calling bo.Backtest().

Thank you for your reply.

Sorry, I just realised that somehow I had lost some code (the 'PostProcess' and 'BackTest(True)') from the code I pasted...

SetCustomBacktestProc("");
if (Status("action") == actionPortfolio)
{
    bo = GetBacktesterObject();    //  Get backtester object

    bo.PreProcess();    //  Do pre-processing

    for (i = 0; i < BarCount; i++)    //  Loop through all bars
    {
        for (sig = bo.GetFirstSignal(i); sig; sig = bo.GetNextSignal(i))
        {
            ....
        }

        bo.HandleStops(i);    //  Handle programmed stops at this bar

        for (trade = bo.GetFirstOpenPos(); trade; trade = bo.GetNextOpenPos())
        {
            ....
        }

        bo.UpdateStats(i, 1);    //  Update MAE/MFE stats for bar
        bo.UpdateStats(i, 2);    //  Update stats at bar's end
    }

    bo.PostProcess();

    bo.Backtest(True);            //  Run backtests with no trade listing

    stat = bo.GetPerformanceStats(0);

    for (trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade())
    {
        ....
    }

    bo.ListTrades();


    for (trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade())
    {
        ....
    }

    for (trade = bo.GetFirstOpenPos(); trade; trade = bo.GetNextOpenPos())
    {
        ....
    }


    equityValue = bo.EquityArray();

    ....
}

So, I know you said as a general rule the high/mid/low level set of CBT functions shouldn't be combined, but if done right, can they be?

Your custom backtest code is not correct.

Sorry, can you elaborate?

You are mixing bo.PreProcess & bo.PostProcess together with bo.Backtest & bo.ListTrades.

But that is my question, is it possible to mix these calls in some manner in which it does work?

No you can't call both in the same formula.
bo.Backtest() is all-in-one call. It does everything. It is provided only to add custom metrics AFTER backtest.

You either use high-level backtest (bo.Backtest() call) or mid-level/low-level (then you use PreProcess/PostProcess but not calling bo.Backtest()).

This is so because PreProcess() and PostProcess need to be called once only and bo.Backtest() internally calls PreProcess and PostProcess.

Quote from manual: http://www.amibroker.com/guide/a_custombacktest.html

Various approaches for various applications

The porfolio backtester interface supports various approaches to customization of backtest process that suit different applications.

  • high-level approach (the easiest)
    using Backtest() method and it runs default backtest procedure (as in old versions) - allows simple implementation of custom metrics
  • mid-level approach
    using PreProcess()/ProcessTradeSignal()/PostProcess() methods - allows to modify signals, query open positions (good for advanced position sizing)
  • low-level approach (the most complex)
    using PreProcess()/EnterTrade()/ExitTrade()/ScaleTrade()/UpdateStats()/HandleStops()/PostProcess() methods - provides full control over entire backtest process for hard-code programmers only
5 Likes

Hi codejunkie,

Sorry, for some reason I didn't get an email when you posted this initial reply and so initially I didn't see it. I've now reworked my code, only calling the 'pre' and 'post' process methods without the bo.Backtest(True) and the bo.ListTrades() calls and all is working as needed now.

Thank you!