CBT code for MaxLOSSPerDay and MaxLossPerTrade

I am trying to implement the logic
a) If unrealised loss in any open-position exceeds maxLossOpenPos then exit that position
b) If total loss of all closed trades added to unrealised loss of all open positions exceeds MaxLossPerDay then exit all open positions and ignore any new signal for that day (i.e. no more trading that day)

My understanding of CBT is quite limited and I came up with below code but it is not working.
Please help me. Also if you could point me to a thread which is of similar topic then it will be useful to me. Thankyou


_SECTION_BEGIN("Price");
SetChartOptions(0,chartShowArrows|chartShowDates|chartWrapTitle);
_N(Title = StrFormat("{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}} ", O, H, L, C, SelectedValue( ROC( C, 1 ))) );
PlotOHLC( O, H, L, C, "Traded", colorRed, styleBar , Null, Null, 0, 1, 1); 
_SECTION_END();

Buy = Cross( MACD(), Signal() );
Sell = Cross( Signal(), MACD() );
Short=Sell;
Cover=Buy;
// trade on next bar open
SetTradeDelays( 1, 1, 1, 1 );
BuyPrice = SellPrice = CoverPrice=ShortPrice=Open; 

// trade size: 25% of current portfolio equity
SetPositionSize( 25, spsPercentOfEquity );
if( Status( "action" ) == actionPortfolio )
{

    bo = GetBacktesterObject();
    bo.PreProcess();

    equityArray = bo.EquityArray;
    MaxLossPerDay = equityArray * 5 / 100;
    MaxLossPerOpenPosition = equityArray * 2 / 100;
    dt = DateTime();

    for( i = 0; i < BarCount; i++ )
    {

        if( i > 0 )
        {
            previousBarDate = DateTimeConvert( 0, dt[i - 1] );
            thisBarDate = DateTimeConvert( 0, dt[i] );

            if( previousBarDate != thisBarDate ) //new day
                MaxLossPerDayHit= totalLossForTheDay = totalRealisedLossForTheDay = totalUnRealisedLossForTheDay = 0;
            
        }     

        for( closedTrade = bo.GetFirstTrade(); closedTrade; closedTrade = bo.GetNextTrade() )
        {
            if( closedTrade == bo.GetFirstTrade() ) //if this is the first trade in list
                totalRealisedLossForTheDay = closedTrade.GetProfit();
            else
                if( DateTimeConvert( 0, closedTrade.EntryDateTime ) != datePreviousTrade ) // if first trade of the day
                    totalRealisedLossForTheDay = 0;
                else
                    totalRealisedLossForTheDay = totalRealisedLossForTheDay + closedTrade.GetProfit();	//realised profit/loss

            datePreviousTrade = DateTimeConvert( 0, closedTrade.EntryDateTime );
        }


        for( openpos  = bo.GetFirstOpenPos();  openpos ;  openpos  = bo.GetNextOpenPos() )
        {
            thisOpenPositionLoss = openpos.GetProfit();

            if( thisOpenPositionLoss < ( -1 * MaxLossPerOpenPosition[i] ) )
            {
                bo.ExitTrade( i, openpos.Symbol, openpos.GetPrice( i, "C" ), 2 );
                _TRACE( "MaxLoss Per OpenPosition Hit for instrument " + openpos.Symbol + " on " + dt );
            }

            else
                if( totalLossForTheDay < ( -1 * MaxLossPerDay[i] ) )
                {
                    bo.ExitTrade( i, openpos.Symbol, openpos.GetPrice( i, "C" ), 2 );
                    _TRACE( "MaxLoss Per Day Hit for instrument " + openpos.Symbol + " on " + dt );
                }

            if( openpos == bo.GetFirstOpenPos() )
                totalUnRealisedLossForTheDay = openpos.GetProfit();
            else
                if( DateTimeConvert( 0, openpos.EntryDateTime ) != datePreviousOpenPos )
                    totalUnRealisedLossForTheDay = 0;
                else
                    totalUnRealisedLossForTheDay = totalUnRealisedLossForTheDay + openpos.GetProfit();	//Unrealised profit/loss

            datePreviousOpenPos = DateTimeConvert( 0, openpos.EntryDateTime );
        }

        totalLossForTheDay = totalRealisedLossForTheDay + totalUnRealisedLossForTheDay;

        for( sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) )
        {
            if( totalLossForTheDay < ( -1 * MaxLossPerDay[i] ) )
            {               
                    sig.PosSize = 0; //ignore the signal
                    _TRACE( "MAX LOSS for Day Hit. No Nore Trading For The Day. Signal for instrument " + sig.Symbol + " IGNORED on " + sig.EntryDateTime );
            }  
            

			if( sig.IsEntry()) // Process long entries
			bo.EnterTrade( i, sig.Symbol, True, sig.Price, sig.PosSize );
			else
			{
			if( sig.IsExit()) // Process long exits
			bo.ExitTrade( i, sig.Symbol, sig.Price );
			}
        }
        bo.HandleStops(i);

        //bo.ProcessTradeSignals( i );
    }

    bo.PostProcess();

}


1 Like

Updated Code. I am getting endless For loop detected error when i backtest this.

//If unrealised loss in any openposition exceeds maxLossOpenPos then exit that position
//If total loss of all closed trades added to unrealised loss of all open positions exceeds MaxLossPerDay then exit all open positions and ignore any new signal for that day (i.e. no more trading that day)

_SECTION_BEGIN( "Price" );
SetChartOptions( 0, chartShowArrows | chartShowDates | chartWrapTitle );
_N( Title = StrFormat( "{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}} ", O, H, L, C, SelectedValue( ROC( C, 1 ) ) ) );
PlotOHLC( O, H, L, C, "Traded", colorRed, styleBar , Null, Null, 0, 1, 1 );
_SECTION_END();

Buy = Cross( MACD(), Signal() );
Sell = Cross( Signal(), MACD() );
Short = Sell;
Cover = Buy;
// trade on next bar open
SetTradeDelays( 1, 1, 1, 1 );
BuyPrice = SellPrice = CoverPrice = ShortPrice = Open;

// trade size: 25% of current portfolio equity
SetPositionSize( 25, spsPercentOfEquity );
SetCustomBacktestProc( "" );

if( Status( "action" ) == actionPortfolio )
{

    bo = GetBacktesterObject();
    bo.PreProcess();
    MaxLossPerDayHit = totalLossForTheDay = totalRealisedLossForTheDay = totalUnRealisedLossForTheDay = datePreviousTrade = datePreviousOpenPos = 0;
    equityArray = bo.EquityArray;
    MaxLossPerDay = equityArray * 0.5 / 100;
    MaxLossPerOpenPosition = equityArray * 0.1 / 100;
    dt = DateTime();
    previousBarDate = Ref( DateTimeConvert( 0, dt ), -1 );
    thisBarDate = DateTimeConvert( 0, dt );

    for( i = 0; i < BarCount; i++ )
    {

        if( i > 0 )
        {
            //previousBarDate = DateTimeConvert( 0, dt );
            //thisBarDate = DateTimeConvert( 0, dt);

            if( previousBarDate[i] != thisBarDate[i] ) //new day
                MaxLossPerDayHit = totalLossForTheDay = totalRealisedLossForTheDay = totalUnRealisedLossForTheDay = 0;

        }

        for( closedTrade = bo.GetFirstTrade(); closedTrade; closedTrade = bo.GetNextTrade() )
        {
            if( closedTrade == bo.GetFirstTrade() ) //if this is the first trade in list
                totalRealisedLossForTheDay = closedTrade.GetProfit();
            else
                if( DateTimeConvert( 0, closedTrade.EntryDateTime ) != datePreviousTrade ) // if first trade of the day
                    totalRealisedLossForTheDay = 0;
                else
                    totalRealisedLossForTheDay = totalRealisedLossForTheDay + closedTrade.GetProfit();	//realised profit/loss

            if( !( IsNull( closedTrade.EntryDateTime ) ) )
                datePreviousTrade = DateTimeConvert( 0, closedTrade.EntryDateTime );
        }


        for( openpos  = bo.GetFirstOpenPos();  openpos ;  openpos  = bo.GetNextOpenPos() )
        {
            thisOpenPositionLoss = openpos.GetProfit();

            if( thisOpenPositionLoss < ( -1 * MaxLossPerOpenPosition[i] ) )
            {
                bo.ExitTrade( i, openpos.Symbol, openpos.GetPrice( i, "C" ), 2 );
                _TRACE( "MaxLoss Per OpenPosition Hit for instrument " + openpos.Symbol + " on " + dt );
            }

            else
                if( totalLossForTheDay < ( -1 * MaxLossPerDay[i] ) )
                {
                    bo.ExitTrade( i, openpos.Symbol, openpos.GetPrice( i, "C" ), 2 );
                    _TRACE( "MaxLoss Per Day Hit for instrument " + openpos.Symbol + " on " + dt );
                }

            if( openpos == bo.GetFirstOpenPos() )
                totalUnRealisedLossForTheDay = openpos.GetProfit();
            else
                if( DateTimeConvert( 0, openpos.EntryDateTime ) != datePreviousOpenPos )
                    totalUnRealisedLossForTheDay = 0;
                else
                    totalUnRealisedLossForTheDay = totalUnRealisedLossForTheDay + openpos.GetProfit();	//Unrealised profit/loss

            if( !( IsNull( openpos.EntryDateTime ) ) )
                datePreviousOpenPos = DateTimeConvert( 0, openpos.EntryDateTime );
        }

        totalLossForTheDay = totalRealisedLossForTheDay + totalUnRealisedLossForTheDay;

        for( sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) )
        {
            if( totalLossForTheDay < ( -1 * MaxLossPerDay[i] ) )
            {
                sig.PosSize = 0; //ignore the signal
                _TRACE( "MAX LOSS for Day Hit. No Nore Trading For The Day. Signal for instrument " + sig.Symbol + " IGNORED on " + sig.EntryDateTime );
            }


            if( sig.IsEntry() ) // Process long entries
                bo.EnterTrade( i, sig.Symbol, True, sig.Price, sig.PosSize );
            else
            {
                if( sig.IsExit() ) // Process long exits
                    bo.ExitTrade( i, sig.Symbol, sig.Price );
            }
        }

        bo.HandleStops( i );

        //bo.ProcessTradeSignals( i );
    }

    bo.PostProcess();

}




error1
amierror11

error2
amierror12

error3
amierror13

Errors 1 and 2 look like the ones that typically occur when you run a backtest that generates no trades. You can control how many loop iterations are considered "endless" in the main application Preferences.

You should probably add more _TRACE statements so that you can see exactly what your code is doing, because at a quick glance it does not appear correct. It's also horribly inefficient to go through the entire closed trade list on each bar of the backtest. Why not just store the equity at the start of each new day and compare it to the equity at the start or end of each bar during the day?

I looked up 2 threads where fxshrat had gven solutions for cbt. Below is my code (STILL NOT WORKING FULLY)

  1. It seems to work for maxLossPerTrade
  2. but still not working for maxLossPerDay
//If unrealised loss in any openposition exceeds maxLossOpenPos then exit that position
//If total loss of all closed trades added to unrealised loss of all open positions exceeds MaxLossPerDay then exit all open positions and ignore any new signal for that day (i.e. no more trading that day)
_TRACE( "!CLEAR!" );
_SECTION_BEGIN( "Price" );
SetChartOptions( 0, chartShowArrows | chartShowDates | chartWrapTitle );
_N( Title = StrFormat( "{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}} ", O, H, L, C, SelectedValue( ROC( C, 1 ) ) ) );
PlotOHLC( O, H, L, C, "Traded", colorRed, styleBar , Null, Null, 0, 1, 1 );
_SECTION_END();

Buy = Cross( MACD(), Signal() );
Sell = Cross( Signal(), MACD() );
Short = Sell;
Cover = Buy;
// trade on next bar open
SetTradeDelays( 1, 1, 1, 1 );
BuyPrice = SellPrice = CoverPrice = ShortPrice = Open;

// trade size: 25% of current portfolio equity
SetPositionSize( 25, spsPercentOfEquity );
SetCustomBacktestProc( "" );
dt = DateTime();
thisBarDate = DateTimeConvert( 0, dt );

if( Status( "action" ) == actionPortfolio )
{

    bo = GetBacktesterObject();
    bo.PreProcess();
    MaxLossPerDayHit = totalLossForTheDay = totalRealisedLossForTheDay = totalUnRealisedLossForTheDay = datePreviousTrade = datePreviousOpenPos = 0;
    last_dt = LastValue( ValueWhen( Status( "lastbarinrange" ), DateTime() ) );
    cash_array = Null;

    for( i = 0; i < BarCount; i++ )
    {
        bo.ProcessTradeSignals( i );
        CapitalAtStartOfDay = ValueWhen( thisBarDate != Ref( thisBarDate, -1 ), bo.equityArray );
        cash_array[ i ] = bo.cash;
        EquityArray = bo.EquityArray;
        MaxLossPerDay = CapitalAtStartOfDay * 0.7 / 100; //700
		MaxLossPerOpenPosition = CapitalAtStartOfDay * 0.2 / 100; //500
		MaxLossPerDayHit[i]=(CapitalAtStartOfDay[i] - EquityArray[i]) > MaxLossPerDay[i];
        //_TRACE("EquityArray="+EquityArray[i]);
        if( i > 0 )
            if( thisBarDate[i - 1] != thisBarDate[i] ) //new day
                _TRACE( "New Day " + thisBarDate[i] + ", CapitalAtStartOfDay=" + CapitalAtStartOfDay[i] );

        for( openpos  = bo.GetFirstOpenPos();  openpos ;  openpos  = bo.GetNextOpenPos() )
        {        
			MaxLossPerOpenPositionHit[i]=openpos.getProfit() < -1 * MaxLossPerOpenPosition[i];     
			          
            if( MaxLossPerDayHit[i] )
                bo.ExitTrade( i, openpos.Symbol, openpos.GetPrice( i, "C" ), 6 );
                
            else if( MaxLossPerOpenPositionHit[i] )
                bo.ExitTrade( i, openpos.Symbol, openpos.GetPrice( i, "C" ), 2 );
    
        }
        
        for( sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) )
        {       
        
            if(  !(MaxLossPerDayHit[i] ))
            {
                sig.PosSize = 0; //ignore the signal
                sig.Price=0;
                _TRACE( "MAX LOSS for Day Hit. No Nore Trading For The Day. Signal for instrument " + sig.Symbol + " IGNORED on " + dt[i] );
            }


			if( sig.IsEntry() ) // Process long entries
			bo.EnterTrade( i, sig.Symbol, True, sig.Price, sig.PosSize );
			else
			{			
				if( sig.IsExit() ) // Process long exits
				bo.ExitTrade( i, sig.Symbol, sig.Price );
			}		
		
            
        }
        
		bo.HandleStops( i );
		
		bo.UpdateStats(i, 1); 
		bo.UpdateStats(i, 2);

    }

    for( closedTrade = bo.GetFirstTrade(); closedTrade; closedTrade = bo.GetNextTrade() )
    {
        equityAtEntry = Lookup( bo.EquityArray, closedTrade.EntryDateTime );
        closedTrade.AddCustomMetric( "CapitalAtStartOfDay", Lookup( CapitalAtStartOfDay, closedTrade.EntryDateTime ) );
        closedTrade.AddCustomMetric( "Portfolio Value", Lookup( bo.EquityArray, closedTrade.EntryDateTime ) );
        closedTrade.AddCustomMetric( "Portfolio Value @exit", Lookup( bo.EquityArray, closedTrade.ExitDateTime ) );
        closedTrade.AddCustomMetric( "Available Cash", Lookup( cash_array, closedTrade.EntryDateTime ) );
        //_TRACE("equityAtEntry="+equityAtEntry);
    }


    for( openpos  = bo.GetFirstOpenPos();  openpos ;  openpos  = bo.GetNextOpenPos() )
    {
        equityAtEntry = Lookup( bo.EquityArray, openpos.EntryDateTime );

        openpos.AddCustomMetric( "Portfolio Value", equityAtEntry );
        openpos.AddCustomMetric( "Portfolio Value @exit", Lookup( bo.EquityArray, last_dt ) );
        openpos.AddCustomMetric( "Available Cash", Lookup( cash_array, openpos.EntryDateTime ) );


    }

    bo.PostProcess();
}

There are probably multiple things still wrong with your code, but one reason your Max Loss Per Day logic isn't working is because you call bo.ProcessTradeSignals( i ); at the top of your for loop. Therefore, the calls to bo.EnterTrade and bo.ExitTrade at the bottom of your for loop aren't doing anything.

For retrieving the starting equity of the day, I would have done something like this:
Before the for loop:

dn = DateNum();

At the top of the for loop before doing anything else:

if (bar == 0)
   isNewDay = true;
else
   isNewDay = dn[bar] != dn[bar-1];

if (isNewDay)
{
   // Note that dailyStartingCapital is a scalar, not an array
   dailyStartingCapital = bo.Equity;
}

It seems there are several other places where you're currently using arrays but don't need to, but that mostly comes down to personal preference as long as you use them correctly.

But if i don't call bo.ProcessTradeSignals( i ); then bo.Equity gives wrong values.

Correction: before calling bo.ProcessTradeSignals, bo.Equity gives you correct values that you don't yet understand. If you use bo.Equity at the top of the loop, before ever calling bo.UpdateStats, then you should get the current equity based on the opening (I believe, but you should confirm) bar price for all open trades.

You could also change the logic that I gave you so that you save bo.Equity at the end of the for loop (after calling bo.UpdateStats(i,2) ), when the next bar will be the start of a new day. In other words, instead of saving today's opening equity, you would save yesterday's closing equity and use that in your comparisons.

I see that you've added a few _TRACE statements, but until you fully understand how the CBT is working you should add lots and LOTS of _TRACE statements so that you can have better visibility into exactly what AmiBroker is doing in the background while you're running your CBT. For example, look at individual trade P/L before and after you've called bo.UpdateStats(i,1) and bo.UpdateStats(i,2). What prices are being used to calculate profit?

2 Likes

The below code is working. I have tested it for both PerDay and PerTrade.
It is somewhat astonishing that such a basic thing is not available in the forum or knowledge Base. This stuff is basic and essential for any serious day trading system. The information about his is scattered across several threads and not in one thread as it should be. Tomasz, if you deem fit, please make this into a KB article. I would be happy to help you write the article if you need me.
Below is the working code.

//If unrealised loss in any openposition exceeds maxLossOpenPos then exit that position
//If total loss of all closed trades added to unrealised loss of all open positions exceeds MaxLossPerDayLimit then exit all open positions and ignore any new signal for that day (i.e. no more trading that day).
//I derive PerDay and perTrade loss limits from CapitalAtStartOfDay. You may use your own thing here.

//Trading system code begins here. Replace with your own system
_TRACE( "!CLEAR!" );
_SECTION_BEGIN( "Price" );
SetChartOptions( 0, chartShowArrows | chartShowDates | chartWrapTitle );
_N( Title = StrFormat( "{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}} ", O, H, L, C, SelectedValue( ROC( C, 1 ) ) ) );
PlotOHLC( O, H, L, C, "Traded", colorRed, styleBar , Null, Null, 0, 1, 1 );
_SECTION_END();

Buy = Cross( MACD(), Signal() ) && (TimeNum()<150000);
Sell = Cross( Signal(), MACD() ) ||(TimeNum()==151500);
Buy =ExRem(Buy, Sell);
Sell=ExRem(Sell, Buy);
Short = Sell && (TimeNum()<150000);
Cover = Buy ||(TimeNum()==151500);

Short =ExRem(Short, Cover);
Cover=ExRem(Cover, Short);

// trade on next bar open
SetTradeDelays( 1, 1, 1, 1 );
BuyPrice = SellPrice = CoverPrice = ShortPrice = Open;

// trade size: 25% of current portfolio equity
SetPositionSize( 25, spsPercentOfEquity );
//Trading system code ends here

//CBT code begins here
SetCustomBacktestProc( "" );
dt = DateTime();
thisBarDate = DateTimeConvert( 0, dt );

if( Status( "action" ) == actionPortfolio )
{

    bo = GetBacktesterObject();
    bo.PreProcess();
    MaxLossPerDayLimitHit = 0;

    for( i = 0; i < BarCount; i++ )
    {
        if( i == 0 )
            isNewDay = true;
        else
            isNewDay = thisBarDate[i] != thisBarDate[i - 1];

        if( isNewDay )
        {
            // Note that CapitalAtStartOfDay is a scalar, not an array
            CapitalAtStartOfDay = bo.Equity;
        }

		formatedDateTime=DateTimeFormat("%d-%m-%Y %H:%M:%S", dt[i]  );

        MaxLossPerDayLimit = CapitalAtStartOfDay * 1.0 / 100;
        MaxLossPerOpenPositionLimit = CapitalAtStartOfDay * 0.2 / 100;
        eq = bo.Equity;
        LossForTheDay=CapitalAtStartOfDay - eq;
        MaxLossPerDayLimitHit = LossForTheDay > MaxLossPerDayLimit;

        //_TRACE( "MaxLossPerDayLimitHit=" + MaxLossPerDayLimitHit + ", CapitalAtStartOfDay=" + CapitalAtStartOfDay + ", eq=" + eq + ", MaxLossPerDayLimit=" + MaxLossPerDayLimit +", MaxLossPerOpenPositionLimit="+MaxLossPerOpenPositionLimit+", formatedDateTime="+formatedDateTime);


        if( isNewDay ) {//new day
            _TRACE( "New Day " + formatedDateTime + ", CapitalAtStartOfDay=" + CapitalAtStartOfDay );
            }

        for( sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) )
        {
            if( MaxLossPerDayLimitHit )
            {
                if( sig.IsEntry() || sig.IsScale())
                {
                    sig.Price = -1;
                    //_TRACE( "MaxLossPerDayLimit Hit loss for the day exceeded "+MaxLossPerDayLimit+". No Nore Trading For The Day. Signal for instrument " + sig.Symbol + " IGNORED on " + formatedDateTime );
                }
            }
        }

        for( openpos  = bo.GetFirstOpenPos();  openpos ;  openpos  = bo.GetNextOpenPos() )
        {
            MaxLossPerOpenPositionLimitHit = openpos.getProfit() < -1 * MaxLossPerOpenPositionLimit;
            if (openpos.IsLong)
            priceAtWhichOpnPosLossLimitHit = -1 * (MaxLossPerOpenPositionLimit - openpos.EntryPrice * openpos.Shares)/openpos.Shares;
            else
            priceAtWhichOpnPosLossLimitHit =  ( openpos.EntryPrice * openpos.Shares + MaxLossPerOpenPositionLimit )/openpos.Shares;
            
            
            //priceAtWhichLimitHit * openpos.Shares= MaxLossPerOpenPositionLimit + openpos.EntryPrice * openpos.Shares;

            if( MaxLossPerDayLimitHit )
            {
                bo.ExitTrade( i, openpos.Symbol, openpos.GetPrice( i, "C" ), 6 );
                _TRACE( "MaxLossPerDayLimit Hit total portfolio loss of "+LossForTheDay+" exceeded limit set at "+MaxLossPerDayLimit+" for the day. Exiting Trade " + openpos.Symbol + " on " + formatedDateTime);
            }

            else
                if( MaxLossPerOpenPositionLimitHit )
                {
                    _TRACE( "MaxLossPerOpenPositionLimit Hit for " + openpos.Symbol + " Unrealised loss is " + (-1*openpos.getProfit())+", priceAtWhichOpnPosLossLimitHit="+priceAtWhichOpnPosLossLimitHit+", MaxLossPerOpenPositionLimit = "+MaxLossPerOpenPositionLimit+", on "+formatedDateTime);
                    bo.ExitTrade( i, openpos.Symbol,  priceAtWhichOpnPosLossLimitHit, 2 );

				}
        }

        bo.ProcessTradeSignals( i );
    }

    bo.PostProcess();
}

This topic was automatically closed 100 days after the last reply. New replies are no longer allowed.