Low Level CBT blocking trades that main code block does not

I need an accurate day-by-day trade count of the number of actual entries, exits, and open positions for that day. This needs to be actual trades, not trade signals which are sometimes skipped because of insufficient funds. So I created a study in the CBT to write to "C:temp\TradeCounts.csv". Enabling the parameter "DoTradeCounts" will enable the write to CSV. (Please make sure you have a temp folder in your C drive.)

I have reduced the code to an extremely simple RSI strategy for the forum. If you run this code with DoTradeCounts on, you will get less trades than if you run it with DoTradeCounts off. I have compared the two trade lists and it appears that AB is blocking some entries when I try to enter via the low level CBT that are not blocked with main-block signals. I do not know why because I am passing the exact sig.PosSize and sig.Price from the main block back into the low level signal. Why would it block these trades in the CBT, but not in the main?


_SECTION_BEGIN("Price");
SetChartOptions(0,chartShowArrows|chartShowDates);
_N(Title = StrFormat("{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}}", O, H, L, C, SelectedValue( ROC( C, 1 ) ) ));
Plot( C, "Close", ParamColor("Color", colorDefault ), styleNoTitle | ParamStyle("Style") | GetPriceStyle() ); 
_SECTION_END();


DoTradeCounts = Param("DoTradeCounts",0,0,1,1);	

spyParams = ParamStr("------  MM PARAMS  -----","");
InitialCap = Param("InitialCapital",100000,100000,10000000,1000);
PositionSizeType = Param("PositionSizeType 0=Dollars, 1=Percent",1,0,1,1);
PositionSizeInput = Param("PositionSizeInput",3,1,1000000,1);

///// Indicator
indicatorParams = ParamStr("------  ENTRY/EXIT PARAMS  -----","");
RSIlength = Param("RSIlength",14,1,100,1);	
RSIentryThresh = Param("RSIentryThresh",20,1,100,1);	
RSIexitThresh = Param("RSIexitThresh",80,1,100,1);	


myRSI = RSI(RSIlength);
bRSIentry = Ref(myRSI,-1) < RSIentryThresh;
bRSIexit = Ref(myRSI,-1) > RSIexitThresh;


Buy = 0;
BuyPrice = 0;
Sell = 0;
SellPrice = 0;
Short = 0;
Cover = 0;


dt=DateTime();
bi = BarIndex();
dn = DateNum();
tn = TimeNum();
SelectedDN = SelectedValue(dn);
SelectedTN = SelectedValue(tn);

SetOption("MaxOpenPositions", 9999);
//SetOption("AllowPositionShrinking", True);
SetOption("InitialEquity", InitialCap);
SetOption("AllowSameBarExit",true);
SetOption("ActivateStopsImmediately",true);
SetBacktestMode( backtestRegular );
	
switch( PositionSizeType )
{
	case 0:
		SetPositionSize( PositionSizeInput, spsValue ); 
		break;
        
	case 1:
		SetPositionSize( PositionSizeInput, spsPercentOfEquity ); 
		break;
}


Buy = bRSIentry AND Close < Ref(Close, -1) AND Ref(Close,-1) < Ref(Close,-2) AND Ref(Close,-2) < Ref(Close,-3); 
BuyPrice = Open;

Sell = bRSIexit; 
SellPrice = Open;

buy = ExRem( buy, sell );
sell = ExRem( sell, buy ); 

    
PlotShapes(IIf(Buy, shapeUpArrow, shapeNone),colorBlue,  0, BuyPrice, Offset=-15); 
PlotShapes(IIf(sell, shapeDownArrow, shapeNone),colorYellow, 0, SellPrice, Offset=-15);


doTrace = 1;
SetCustomBacktestProc( "" );


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

	if (DoTradeCounts)
	{
		fhname = "C:\\temp\\TradeCounts.csv";
		fh = fopen(fhname, "w");
	}
	
	for ( i = 0; i < BarCount; i++ )
	{
		dn_tn = NumToStr(dn[i], 1.0, False, False) + "_" + NumToStr(tn[i], 1.0, False, False);
		dn_tn_i = "DT: " + dn_tn + " i: " + NumToStr(i,1.0, True, False) + " ||||| " ;
			
		if (DoTradeCounts)
		{
			if ( i == 0 )
			{
				CntInitial = 0;
				CntReThresh = 0;
				ScoreCount[i] = 0;
				
				EntriesCnt = 0;
				ExitsCnt = 0;
				OpenPosCnt = 0;

				fputs("Date" + "," + 
						"EntriesCnt" + "," +  
						"ExitsCnt" + "," +  
						"# OpenPos" + "\n", fh);
			}
			
			if ( i >= 0 )
			{
				Dtf= DateTimeFormat("%Y/%m/%d", dt[i]);

				EntriesCnt = 0;
				ExitsCnt = 0;
				for (sig = bo.GetFirstSignal(i); sig; sig = bo.GetNextSignal(i))
				{   
					symbol = sig.Symbol;
	
					if (sig.IsEntry() )  
					{
						
						CashOld = bo.Cash();
						
						//mark main block size and price
						myPosSize = sig.PosSize;
						myPrice = sig.Price;
						
						//null the main block trade
						sig.PosSize = 0;
						sig.Price = 0;
						
						//now enter low level
						bo.EnterTrade( i, symbol, 1, myPrice, myPosSize);	
						
						//Detect if a trade was actually taken
						if (round(bo.Cash()) < round(CashOld)) 
						{
							EntriesCnt += 1;
							if (doTrace) { _TRACE(dn_tn_i + " *** symbol: " + symbol + " CashOld: " + NumToStr(CashOld, 1.2) + " bo.Cash: " + NumToStr(bo.Cash(), 1.2) + "  ENTRY TAKEN "    ); }
						}
						else
						{
							if (doTrace) { _TRACE(dn_tn_i + " *** symbol: " + symbol + " CashOld: " + NumToStr(CashOld, 1.2) + " bo.Cash: " + NumToStr(bo.Cash(), 1.2) + "  ENTRY SKIPPED "    ); }
						}
						
					}
					if (sig.IsExit() )
					{
						CashOld = bo.Cash();
						
						//mark main block price
						myPrice = sig.Price;
						
						//null the main block trade
						sig.PosSize = 0;
						sig.Price = 0;
						
						//now exit low level
						bo.ExitTrade(i, symbol, myPrice, 1);
										
						//Detect if a trade was actually exited
						if (round(bo.Cash()) > round(CashOld)) 
						{
							ExitsCnt += 1;
							if (doTrace) { _TRACE(dn_tn_i + " *** symbol: " + symbol + " CashOld: " + NumToStr(CashOld, 1.2) + " bo.Cash: " + NumToStr(bo.Cash(), 1.2) + "  EXIT TAKEN "    ); }
						}
						else
						{
							if (doTrace) { _TRACE(dn_tn_i + " *** symbol: " + symbol + " CashOld: " + NumToStr(CashOld, 1.2) + " bo.Cash: " + NumToStr(bo.Cash(), 1.2) + "  EXIT SKIPPED "    ); }
						}
					}
				}
			
				OpenPosCnt = 0;
				for ( openpos = bo.GetFirstOpenPos(); openpos; openpos = bo.GetNextOpenPos() )
				{
					OpenPosCnt += 1;	
				}
				
				fputs(Dtf + "," + 
					NumToStr(EntriesCnt[i], 1.0, False, False) + "," +  
					NumToStr(ExitsCnt[i], 1.0, False, False) + "," +  
					NumToStr(OpenPosCnt[i], 1.0, False, False) + "\n", fh);
			}

		}	
		bo.ProcessTradeSignals( i );
	}
	
	if (DoTradeCounts)
	{
		fclose(fh);  	
	}	
			
    bo.PostProcess();  

}






I posted that late last night and I was tired. Here is a cleaner version of the code.


_SECTION_BEGIN("Price");
SetChartOptions(0,chartShowArrows|chartShowDates);
_N(Title = StrFormat("{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}}", O, H, L, C, SelectedValue( ROC( C, 1 ) ) ));
Plot( C, "Close", ParamColor("Color", colorDefault ), styleNoTitle | ParamStyle("Style") | GetPriceStyle() ); 
_SECTION_END();


DoTradeCounts = Param("DoTradeCounts",0,0,1,1);	

mmParams = ParamStr("------  MM PARAMS  -----","");
InitialCap = Param("InitialCapital",100000,100000,10000000,1000);
PositionSizeType = Param("PositionSizeType 0=Dollars, 1=Percent",1,0,1,1);
PositionSizeInput = Param("PositionSizeInput",3,1,1000000,1);

///// Indicator
indicatorParams = ParamStr("------  ENTRY/EXIT PARAMS  -----","");
RSIlength = Param("RSIlength",14,1,100,1);	
RSIentryThresh = Param("RSIentryThresh",20,1,100,1);	
RSIexitThresh = Param("RSIexitThresh",80,1,100,1);	


myRSI = RSI(RSIlength);
bRSIentry = Ref(myRSI,-1) < RSIentryThresh;
bRSIexit = Ref(myRSI,-1) > RSIexitThresh;


Buy = 0;
BuyPrice = 0;
Sell = 0;
SellPrice = 0;
Short = 0;
Cover = 0;


dt=DateTime();
bi = BarIndex();
dn = DateNum();
tn = TimeNum();
SelectedDN = SelectedValue(dn);
SelectedTN = SelectedValue(tn);

SetOption("MaxOpenPositions", 9999);
//SetOption("AllowPositionShrinking", True);
SetOption("InitialEquity", InitialCap);
SetOption("AllowSameBarExit",true);
SetOption("ActivateStopsImmediately",true);
SetBacktestMode( backtestRegular );
	
switch( PositionSizeType )
{
	case 0:
		SetPositionSize( PositionSizeInput, spsValue ); 
		break;
        
	case 1:
		SetPositionSize( PositionSizeInput, spsPercentOfEquity ); 
		break;
}


Buy = bRSIentry AND Close < Ref(Close, -1) AND Ref(Close,-1) < Ref(Close,-2) AND Ref(Close,-2) < Ref(Close,-3); 
BuyPrice = Open;

Sell = bRSIexit; 
SellPrice = Open;

buy = ExRem( buy, sell );
sell = ExRem( sell, buy ); 

    
PlotShapes(IIf(Buy, shapeUpArrow, shapeNone),colorBlue,  0, BuyPrice, Offset=-15); 
PlotShapes(IIf(sell, shapeDownArrow, shapeNone),colorYellow, 0, SellPrice, Offset=-15);


doTrace = 1;
SetCustomBacktestProc( "" );


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

	if (DoTradeCounts)
	{
		fhname = "C:\\temp\\TradeCounts.csv";
		fh = fopen(fhname, "w");
	}
	
	for ( i = 0; i < BarCount; i++ )
	{
		dn_tn = NumToStr(dn[i], 1.0, False, False) + "_" + NumToStr(tn[i], 1.0, False, False);
		dn_tn_i = "DT: " + dn_tn + " i: " + NumToStr(i,1.0, True, False) + " ||||| " ;
			
		if (DoTradeCounts)
		{
			if ( i == 0 )
			{
				EntriesCnt = 0;
				ExitsCnt = 0;
				OpenPosCnt = 0;

				fputs("Date" + "," + 
						"EntriesCnt" + "," +  
						"ExitsCnt" + "," +  
						"# OpenPos" + "\n", fh);
			}
			
			if ( i >= 0 )
			{
				Dtf= DateTimeFormat("%Y/%m/%d", dt[i]);

				EntriesCnt = 0;
				ExitsCnt = 0;
				for (sig = bo.GetFirstSignal(i); sig; sig = bo.GetNextSignal(i))
				{   
					symbol = sig.Symbol;
	
					if (sig.IsEntry() )  
					{
						
						CashOld = bo.Cash();
						
						//mark main block size and price
						myPosSize = sig.PosSize;
						myPrice = sig.Price;
						
						//null the main block trade
						sig.PosSize = 0;
						sig.Price = 0;
						
						//now enter low level
						bo.EnterTrade( i, symbol, 1, myPrice, myPosSize);	
						
						//Detect if a trade was actually taken
						if (round(bo.Cash()) < round(CashOld)) 
						{
							EntriesCnt += 1;
							if (doTrace) { _TRACE(dn_tn_i + " *** symbol: " + symbol + " CashOld: " + NumToStr(CashOld, 1.2) + " bo.Cash: " + NumToStr(bo.Cash(), 1.2) + "  ENTRY TAKEN "    ); }
						}
						else
						{
							if (doTrace) { _TRACE(dn_tn_i + " *** symbol: " + symbol + " CashOld: " + NumToStr(CashOld, 1.2) + " bo.Cash: " + NumToStr(bo.Cash(), 1.2) + "  ENTRY SKIPPED "    ); }
						}
						
					}
					if (sig.IsExit() )
					{
						CashOld = bo.Cash();
						
						//mark main block price
						myPrice = sig.Price;
						
						//null the main block trade
						sig.PosSize = 0;
						sig.Price = 0;
						
						//now exit low level
						bo.ExitTrade(i, symbol, myPrice, 1);
										
						//Detect if a trade was actually exited
						if (round(bo.Cash()) > round(CashOld)) 
						{
							ExitsCnt += 1;
							if (doTrace) { _TRACE(dn_tn_i + " *** symbol: " + symbol + " CashOld: " + NumToStr(CashOld, 1.2) + " bo.Cash: " + NumToStr(bo.Cash(), 1.2) + "  EXIT TAKEN "    ); }
						}
						else
						{
							if (doTrace) { _TRACE(dn_tn_i + " *** symbol: " + symbol + " CashOld: " + NumToStr(CashOld, 1.2) + " bo.Cash: " + NumToStr(bo.Cash(), 1.2) + "  EXIT SKIPPED "    ); }
						}
					}
				}
			
				OpenPosCnt = 0;
				for ( openpos = bo.GetFirstOpenPos(); openpos; openpos = bo.GetNextOpenPos() )
				{
					OpenPosCnt += 1;	
				}
				
				fputs(Dtf + "," + 
					NumToStr(EntriesCnt, 1.0, False, False) + "," +  
					NumToStr(ExitsCnt, 1.0, False, False) + "," +  
					NumToStr(OpenPosCnt, 1.0, False, False) + "\n", fh);
			}

		}	
		bo.ProcessTradeSignals( i );
	}
	
	if (DoTradeCounts)
	{
		fclose(fh);  	
	}	
			
    bo.PostProcess();  

}






You are doing a number of things in your code that I don't understand, such as this:

//mark main block size and price
myPosSize = sig.PosSize;
myPrice = sig.Price;
						
//null the main block trade
sig.PosSize = 0;
sig.Price = 0;

We'll set that aside for the moment. I assume by "main block" you actually mean your Phase 1 AFL. The Phase 1 AFL is going to generate the same set of signals regardless of how they are processed in Phase 2, whether that's the built-in backtester or a high, mid, or low level CBT. Therefore, if you're getting a different result when using bo.ProcessTrades() (mid-level CBT) than when you use bo.EnterTrade() and bo.ExitTrade() (low-level CBT) then the obvious answer is that you're doing something different when you process the trade signals yourself.

The way to track this down is to first generate trade lists using the two methods, and then use Excel or another tool to find the discrepancies. When I did this, I quickly discovered an instance where the mid-level CBT was taking a trade that the low-level CBT was not. Another good clue was that another symbol had exited on that same day.

Your low-level code is processing all signals in the order that AmiBroker gives them to you. From your TRACE statements, you can easily see that on any given day you're processing all the entries first and then the exits. However, if you run the mid-level CBT with the Report mode set to Detailed Log, you will see that AB is processing exits before entries when the signals are for different symbols. For example, look specifically at the date where the trade discrepancy occurs, and you will see that by doing the exit of Symbol A first, it frees up cash for the entry of Symbol B.

I'm confident that now that the problem has been identified, you have the coding skills to create a solution.

6 Likes

Matt, I believe you are correct. The answer is because I am processing all entries, before exits, and it should be the other way around. I will revise the code and see if this fixes it.

Concerning the first thing you pointed out. How do I null a Phase I entry signal? So that it does not double trade.... only takes the low level entry.....

I thought setting sig.PosSize = 0 would do that. What is the proper way?

If you are doing the entries and exits yourself using low-level methods, then you should not be asking AB to process the signals as well. In other words, only call bo.ProcessTradeSignals() in the "else" clause (which doesn't yet exist) of the if(doTradeCounts) statement.

Keep in mind that you may need to look for exits after entries as well, if you really want to allow same bar exits.

1 Like