Custom BackTest

Hello, I am trying to do a CBT where i first generate my position score array which is called "size" then i use it to scale in and scale out as given by the array in the CBT part, but when i backtest it is wrong and the required position and the current size executed are not correct. here is the whole code


_SECTION_BEGIN( "NAME" );
formn = ParamStr( "set name", "Remix Signal" );
SetFormulaName( formn );
_SECTION_END();

DBVOn = ParamToggle("DebugView","OFF|ON", 0);
	if( DBVOn ) _TRACE("First line");

	// same day Close
	SetTradeDelays( 0, 0, 0, 0 );  // 1 day delay
	BuyPrice = CoverPrice = SellPrice = ShortPrice = Close;  // trade the close


_SECTION_BEGIN("Rank Loop"); 
	a=b=d=buyr1=sellr1=ma20=rank=roc200i=rank2=rankcomb=Perf1=Sharp1=Sharp2=symlist=WatchLSz = NoS  = numberOfSymbols = Symctact=ROC1= HistoryTest = RocRatio = ROCTick1 = PSize2i= ROCTick2 = ROCTickN = PSize2i =0; 

// ---------------------------------------------------------------------------------------------- 
_SECTION_END();
	wlist = GetOption("FilterIncludeWatchlist"); 
 
if ( Status( "stocknum" ) == 0 )    
	{  	
	if (2 == 2 )  // Applies only to filter - needs open paren?
		{ 
			symlist = CategoryGetSymbols( categoryWatchlist, wlist ) ; 
			numberOfSymbols = StrCount( symlist, "," ) + 1;
			WatchLSz = NoS  = numberOfSymbols; 		 				 
			StaticVarRemove("*");  // delete ALL static variables 
		} 	
// --------------------------------watchlist Loop Pass 1 --------------------------------------------------
	for( i = 0; ( symbol= StrExtract( symlist, i ) ) != ""; i++ ) // Here i=1 symbol - looping through every symbol in watchlist of symbols
		{ 
		SetForeign(symbol);

		StaticVarAdd( "~SymbolCount", 1 );  // Should be same as WatchLSz  = "~SymbolCount" is new static variable 
		 
   		 Symctact = iif(c>0,1,0);  // Testing for activity C= CLOSE = closing price
		 StaticVarAdd( "~Symctact", Symctact );  // # active ticker on that bar = works
		 
		 HistoryTest = iif( ref(c,-100)>0,1,0);
		  if( DBVOn ) _TRACE("1 HistoryTest + sym " + HistoryTest + " " +  symbol);
		  
		 RocRatio = MA(c,3) / MA(c,100);
		 if( DBVOn ) _TRACE("1 RocRatio + sym " + RocRatio + " " +  symbol);
		 RocRatio12 = iif(RocRatio>1, 1,0);
		  if( DBVOn ) _TRACE("1 RocRatio12 + sym " + RocRatio12 + " " +  symbol);
		  
		 ROCTick1 = iif(RocRatio12==1 AND HistoryTest==1,1,0); //# of tickers active and passing filer test
		 
		 if( DBVOn ) _TRACE("1 ROCTick1 + sym " + ROCTick1 + " " +  symbol);
		 
		 StaticVarAdd( "~ROCFilTOT", ROCTick1 ); // sum tickers that get allocation now
 
		RestorePriceArrays(); 

		staticvarset("~RocRatio" +  symbol, RocRatio);  
		staticvarset("~RocRatio12" +  symbol, RocRatio12); //
		staticvarset("~ROCTick1" +  symbol, ROCTick1); // Add Stuff needs to be outside of loop to grab totals
		if( DBVOn ) _TRACE("1 ROCTick1 + sym o" + ROCTick1 + " " +  symbol);
		}	

// --------------------------------WL Loop Pass 2 -------------------------------------------------- 
	for( i = 0; ( symbol= StrExtract( symlist, i ) ) != ""; i++ ) // Here i=1 symbol - looping through every symbol in watchlist of symbols
		{ 
		SetForeign(symbol); 
		
		ROCTick2 = StaticVarGet ( "~ROCTick1" +  symbol ); // Individual ticker that passes
		if( DBVOn ) _TRACE("2 ROCTick2 + sym " + ROCTick2 + " " +  symbol);
		
		ROCtickN = StaticVarGet ( "~ROCFilTOT" );  // Total get # of  filtered ROC tickers for allocation works
		
		PSize2i = IIf(ROCTick2==1 AND ROCtickN>0, 100/ROCtickN,0);
		if( DBVOn ) _TRACE("2 PSize2i + sym " + PSize2i + " " +  symbol);
		
		StaticVarAdd( "~PSize", PSize2i );
		
		RestorePriceArrays(); 
		
		staticvarset("~PSize2i" +  symbol, PSize2i); 
		}
	 
}	

	 symbol = Name();
	//	RocRatio = MA(c,3) / MA(c,100);
	PSizetot = StaticVarGet( "~PSize" );  // total pos size allocation all tickers
	 RocRatioL1 = StaticVarGet ( "~RocRatio" +  symbol ); // 	
	 ROCTick3 = StaticVarGet ( "~ROCTick1" +  symbol  ); // Individual ticker that passes everything
	 ROCtickN2 = StaticVarGet ( "~ROCFilTOT" );  // get # of  filtered ROC tickers works
	 
	 PSize3 = StaticVarGet ( "~PSize2i"  +  symbol );
	 SymbolCount = StaticVarGet ( "~SymbolCount" ); // gross # of tickers
	 ActiveTicka = StaticVarGet ( "~Symctact" );  // get # active tickers
	
// ------------------  --------------------------			 

	RocRatiox = MA(c,3) / MA(c,100);
	HistoryTestx = iif( ref(c,-100)>0,1,0);
	
	Filter = 1;
	PSize3Prev = Ref(PSize3,-1);
	AddColumn( PSize3Prev , "PSize3Prev" , 1.2 );
	AddColumn( PSize3 , "PSize3" , 1.2 );


dt = DateTime();

//-------------------------------------------------------BACKTEST----------------------------------------------------------

Psizechg = PSize3 - PSize3Prev;  // Chg only 
PsizeScalein = IIf(Psizechg < 0,0,Psizechg);  // 
PsizeScaleout = IIf(Psizechg < 0,-Psizechg,0);
AddColumn(Psizechg,"Psizechg");
AddColumn(PsizeScalein,"PsizeScalein");
AddColumn(PsizeScaleout,"PsizeScaleout");

size =IIf(PsizeScalein,PsizeScalein,IIf(PsizeScaleout,PsizeScaleout,PSize3));  // added PSize3 end

//-------------------------------------------------------CBT----------------------------------------------------------

SetOption("UseCustomBacktestProc", True );
SetCustomBacktestProc("");
if ( Status( "action" ) == actionPortfolio ) 
{
    bo = GetBacktesterObject();
    bo.PreProcess();

    for ( bar = 0; bar < BarCount; bar++ )
    {
		entries = bo.GetSignalQty(bar, 1); // get entries
		exits   = bo.GetSignalQty(bar, 2);
        opens   = bo.GetOpenPosQty();
		openPositions = (entries+opens);
		_TRACEF("Day %g",bar);
		_TRACEF("Open Positions %g - Entry signals %g - Exit Signals %g",opens,entries,exits);
		
		for ( sig = bo.GetFirstSignal( bar ); sig; sig = bo.GetNextSignal( bar ) )
		{
			reqPosition = size[bar];

			eq = bo.Equity;
			if (sig.IsScale())
			{
				
				pos=bo.FindOpenPos(sig.Symbol);
				if(!IsNull(pos))
				{
					
				
					
					currentSize=pos.GetPositionValue()/bo.Equity();
					
					if(currentSize<reqPosition)
					{
						increaseSize=(reqPosition-currentSize);
						price = pos.GetPrice( bar, "C" );
						_TRACEF(pos.Symbol+"%g Entry signal scaling In position, Current Size %g , new Size %g",pos.Shares,currentSize,reqPosition/100*eq);
						bo.ScaleTrade( bar, pos.Symbol,1, price,increaseSize);
						
					}
					
					else
					{
						increaseSize=(reqPosition-currentSize);
						price = pos.GetPrice( bar, "C" );
						_TRACEF(pos.Symbol+"%g Entry signal scaling In position, Current Size %g , new Size %g",pos.Shares,currentSize,reqPosition/100*eq);
						bo.ScaleTrade( bar, pos.Symbol,0, price,increaseSize);
					}
					bo.RawTextOutput("Equity\t"+bo.Equity);
					line = StrFormat("%f ( %.1f%% )", reqPosition, currentSize);
					bo.RawTextOutput("Position value 2("+pos.Symbol+")\t"+line);
					bo.RawTextOutput("\t---------------------------------");
				
				}
			}
			
			
		}
		
		bo.ProcessTradeSignals( bar );
	}
    bo.PostProcess();
}	

//Buy = IIf(PsizeScalein>0 ,sigScaleIn, 0);
Buy = IIf(PsizeScalein>0 ,sigScaleIn, IIf(size>0,1,0));

Sell = IIf(psize3 == 0,1,IIf(PsizeScaleout>0,sigScaleOut,0));
AddColumn(Buy,"Buy");
//Sell = IIf(PsizeScaleout>0,sigScaleOut,0);
AddColumn(Sell,"Sell");
//SetPositionSize(5,spsPercentOfEquity);
PositionSize = -size;
AddColumn(PositionSize,"PositionSize");
AddColumn(size,"size");
1 Like

First I would suggest reading this post: How do I debug my formula?

Second, you need to understand that AmiBroker runs a backtest in two phases: Phase 1 is executed once for each symbol in your watchlist, and Phase 2 (the CBT) is run one time with the signals generated from all symbols. Because of that two phased approach, you cannot simply use a variable from Phase 1 (like your size variable) in Phase 2. Instead, the typical approach is to save a symbol-specific copy of the Phase 1 variable in a static variable, and then to retrieve that static variable from Phase 2.

Because you have provided a lot of code and not narrowed down your actual problem, I have not tried to verify all of your logic. I think that once you address the issues above, you will be able to more easily identify other problems.

2 Likes

@mradtke, good points; allow me to clarify. @mennatareeq and I have worked on this code together. While a bit complicated, I have carefully validated that on every bar for every ticker, the variable PSize3 (row 155) exactly captures the desired position size. It is set earlier (row 143) after several earlier steps. If you run this with say a 3 ticker watchlist, and then explore, you can see how this part of the code is working.

However, the buy/sell sigScaleIn + sigScaleOut are not executing the objective positionsizing accurately. We initially tried with just the buy/sell approach, and later added in the CBT section. I am not certain we are blending the two step properly, or if even the "blending" of the buy/sell with CBT is the best path.

We have both strived to work through examples/manual, but obviously not successfully.

Let me know if anything else we can clarify, and any/all help much appreciated.

Kind Regards,
Scott

PS - Sorry your row#s might be different
my 143 = staticvarset("~PSize2i" + symbol, PSize2i);
my 155 = PSize3 = StaticVarGet ( "~PSize2i" + symbol );

Positionsize%20Log

This might help illustrate the issue - the top is the explore output, which correctly shows the desired positionsize by day/ticker; bottom is the log.
From Jan 16 '17 TLT should have dropped from 50% of equity to 0% and SPY should be increasing from 50% to 100%. Even by Jan '18 SPY is still not at 100% of equity, assuming I am reading log correctly.

As a general rule, I advise that you only use a CBT if necessary. If you know all of your desired position sizes in Phase 1, and you've only resorted to a CBT to do the scaling, then it's probably the case that the CBT is not necessary.

Do you have a version of your code without a CBT where you attempted to do all the position sizing from Phase 1 using sigScaleIn and sigScaleOut? If so, please post that along with a description of the problems you encountered. Also, please confirm that you understand that when scaling (in either direction) the PositionSize array should reflect the incremental amount to scale, not the desired target position size. Also, I recommend using SetPositionSize() rather than setting the array directly.

2 Likes

Yes, the code explicitly sets the size conditionally based on if a change or not, but let me x2 check if that could be off somehow.
Will look to dig out the "pre-CBT" version.
thanks again

@mradtke , my first instinct was to say of course we know to set sigscalein to just the increment, but I am glad I double checked, your instinct was sound. I think the iif was not getting trigger properly here. I think this now is the solution:

Psizechg = PSize3 - PSize3Prev;  // Chg only 
PsizeScalein = IIf(Psizechg < 0,0,Psizechg);  // 
PsizeScaleout = IIf(Psizechg < 0,-Psizechg,0);
size =IIf(PsizeScalein>0,PsizeScalein,IIf(PsizeScaleout>0,PsizeScaleout,PSize3));

I say think, as a quick check it looks better, but aligning the desired positions with the log is a slow painful process.

Thank you @mradtke for sharing your good instinct :slight_smile:

1 Like

Getting closer much closer on most days, but not there yet.
Main problem - on days where something is sold, and other positions scalein, it seems it wants to buy before the equity cash is available from the sale. Hence, that day ends up with a very significant unintended cash position (basically the entire sale goes to cash rather than reallocated as per positionsize & sigscalein which should yield 100% allocation). image
Picture from CBT version - both have directionally similar challenge.

I can send more pictures if that helps - let me know.

I am enclosing the version without the CBT 1st below:

function Trace(tt, str) {
	_TRACE(DateTimeToStr(tt)+" "+str);
}

_SECTION_BEGIN( "NAME" );
formn = ParamStr( "set name", "Remix Signal" );
SetFormulaName( formn );
_SECTION_END();

DBVOn = ParamToggle("DebugView","OFF|ON", 0);
	if( DBVOn ) _TRACE("First line");
	
_SECTION_BEGIN("Basic Regular Setup");

 SetBacktestMode(backtestRegular);
	divlong = divshort = 1;
	SetOption("MinPosValue", 1 );
	SetOption("MinShares", .001 );
	MaxPositions = Param("max pos", 3,1,20,4); // Max Positions to hold, CW defaults to 10
	SetOption("MaxOpenLong", MaxPositions/DIVLONG );
	SetOption("MaxOpenShort", MaxPositions/DIVSHORT );
	HoldrankExt=param("HoldrankExt",0,0,4,1);	//extra positions beyound max positions to allow position to slip; default to 0
	SetOption("MaxOpenPositions", MaxPositions);
	SetOption("WorstRankHeld", MaxPositions +HoldrankExt);
	RoundLotSize = 0;

RModeN = Param("0=Trade,1=det,2=sum,3=no", 1,0,3,1);   // 0 - trade list; 1 - detailed log;2 - summary;3 - no output (custom only)
	SetOption("PortfolioReportMode", RModeN );
SetOption("SeparateLongShortRank", False );
SetOption("InitialEquity", 100000 );
//SetOption("AllowSameBarExit", False );
SetOption("ActivateStopsImmediately", False );
SetOption("AllowPositionShrinking", True );
SetOption("FuturesMode", False );
SetOption("InterestRate", 0 );

SetOption("MinPosValue", 1 );
SetOption("MinShares", .001 );
SetOption("PriceBoundChecking", True );
SetOption("ReverseSignalForcesExit", True ); 
SetOption("UsePrevBarEquityForPosSizing", FALSE );  // not sure?

SetOption("EveryBarNullCheck", False );
SetOption("HoldMinBars", 0 );
SetOption("EarlyExitFee", 0 );
SetOption("HoldMinDays", 0 );
SetOption("EarlyExitDays", 0 );
SetOption("DisableRuinStop", TRUE );
SetOption("GenerateReport", 1 );  //full report
SetOption("ExtraColumnsLocation", 1 ); // working

SetOption( "CommissionMode", 3 ); /* 3 set commissions $ per share; 1=% of trade */
comamt  =Param("com amt/shr", .01,.0,.5,.01);  // 
SetOption( "CommissionAmount", comamt ); /* per share  + SLIPPAGE */

MArlvl = param("marg %", 100, 50, 100.1, 10);
SetOption ("AccountMargin", MArLvl);   // 

_SECTION_END();	
	
	// same day Close
	SetTradeDelays( 0, 0, 0, 0 );  // 1 day delay
	BuyPrice = CoverPrice = SellPrice = ShortPrice = Close;  // trade the close


_SECTION_BEGIN("Rank Loop"); 
	a=b=d=buyr1=sellr1=ma20=rank=roc200i=rank2=rankcomb=Perf1=Sharp1=Sharp2=symlist=WatchLSz = NoS  = numberOfSymbols = Symctact=ROC1= HistoryTest = RocRatio = ROCTick1 = PSize2i= ROCTick2 = ROCTickN = PSize2i =0; 

// ---------------------------------------------------------------------------------------------- 
_SECTION_END();
	wlist = GetOption("FilterIncludeWatchlist"); 
 
if ( Status( "stocknum" ) == 0 )    
	{  	
	if (2 == 2 )  // Applies only to filter - needs open paren?
		{ 
			symlist = CategoryGetSymbols( categoryWatchlist, wlist ) ; 
			numberOfSymbols = StrCount( symlist, "," ) + 1;
			WatchLSz = NoS  = numberOfSymbols; 		 				 
			StaticVarRemove("*");  // delete ALL static variables 
		} 	
// --------------------------------watchlist Loop Pass 1 --------------------------------------------------
	for( i = 0; ( symbol= StrExtract( symlist, i ) ) != ""; i++ ) // Here i=1 symbol - looping through every symbol in watchlist of symbols
		{ 
		SetForeign(symbol);

		StaticVarAdd( "~SymbolCount", 1 );  // Should be same as WatchLSz  = "~SymbolCount" is new static variable 
		 
   		 Symctact = iif(c>0,1,0);  // Testing for activity C= CLOSE = closing price
		 StaticVarAdd( "~Symctact", Symctact );  // # active ticker on that bar = works
		 
		 HistoryTest = iif( ref(c,-100)>0,1,0);
		  if( DBVOn ) _TRACE("1 HistoryTest + sym " + HistoryTest + " " +  symbol);
		  
		 RocRatio = MA(c,3) / MA(c,100);
		 if( DBVOn ) _TRACE("1 RocRatio + sym " + RocRatio + " " +  symbol);
		 RocRatio12 = iif(RocRatio>1, 1,0);
		  if( DBVOn ) _TRACE("1 RocRatio12 + sym " + RocRatio12 + " " +  symbol);
		  
		 ROCTick1 = iif(RocRatio12==1 AND HistoryTest==1,1,0); //# of tickers active and passing filer test
		 
		 if( DBVOn ) _TRACE("1 ROCTick1 + sym " + ROCTick1 + " " +  symbol);
		 
		 StaticVarAdd( "~ROCFilTOT", ROCTick1 ); // sum tickers that get allocation now
 
		RestorePriceArrays(); 

		staticvarset("~RocRatio" +  symbol, RocRatio);  
		staticvarset("~RocRatio12" +  symbol, RocRatio12); //
		staticvarset("~ROCTick1" +  symbol, ROCTick1); // Add Stuff needs to be outside of loop to grab totals
		if( DBVOn ) _TRACE("1 ROCTick1 + sym o" + ROCTick1 + " " +  symbol);
		}	

// --------------------------------WL Loop Pass 2 -------------------------------------------------- 
	for( i = 0; ( symbol= StrExtract( symlist, i ) ) != ""; i++ ) // Here i=1 symbol - looping through every symbol in watchlist of symbols
		{ 
		SetForeign(symbol); 
		
		ROCTick2 = StaticVarGet ( "~ROCTick1" +  symbol ); // Individual ticker that passes
		if( DBVOn ) _TRACE("2 ROCTick2 + sym " + ROCTick2 + " " +  symbol);
		
		ROCtickN = StaticVarGet ( "~ROCFilTOT" );  // Total get # of  filtered ROC tickers for allocation works
		
		PSize2i = IIf(ROCTick2==1 AND ROCtickN>0, 100/ROCtickN,0);
		if( DBVOn ) _TRACE("2 PSize2i + sym " + PSize2i + " " +  symbol);
		
		StaticVarAdd( "~PSize", PSize2i );
		
		RestorePriceArrays(); 
		
		staticvarset("~PSize2i" +  symbol, PSize2i); 
		}
	 
}	

	 symbol = Name();
	//	RocRatio = MA(c,3) / MA(c,100);
	PSizetot = StaticVarGet( "~PSize" );  // total pos size allocation all tickers
	 RocRatioL1 = StaticVarGet ( "~RocRatio" +  symbol ); // 	
	 ROCTick3 = StaticVarGet ( "~ROCTick1" +  symbol  ); // Individual ticker that passes everything
	 ROCtickN2 = StaticVarGet ( "~ROCFilTOT" );  // get # of  filtered ROC tickers works
	 
	 PSize3 = StaticVarGet ( "~PSize2i"  +  symbol );
	 SymbolCount = StaticVarGet ( "~SymbolCount" ); // gross # of tickers
	 ActiveTicka = StaticVarGet ( "~Symctact" );  // get # active tickers
	
// ------------------  --------------------------			 

	RocRatiox = MA(c,3) / MA(c,100);
	HistoryTestx = iif( ref(c,-100)>0,1,0);
	
	Filter = 1;
	PSize3Prev = Ref(PSize3,-1);
	AddColumn( PSize3Prev , "PSize3Prev" , 1.2 );
	AddColumn( PSize3 , "PSize3" , 1.2 );


dt = DateTime();

//-------------------------------------------------------BACKTEST----------------------------------------------------------

Psizechg = PSize3 - PSize3Prev;  // Chg only 
PsizeScalein = IIf(Psizechg < 0,0,Psizechg);  // 
PsizeScaleout = IIf(Psizechg < 0,-Psizechg,0);
size =IIf(PsizeScalein>0,PsizeScalein,IIf(PsizeScaleout>0,PsizeScaleout,PSize3));  // added PSize3 end
AddColumn(Psizechg,"Psizechg");
AddColumn(PsizeScalein,"PsizeScalein");
AddColumn(PsizeScaleout,"PsizeScaleout");


Buy =iif(PSize3==0,0,IIf(PsizeScalein>0 ,sigScaleIn, IIf(size>0,1,0)));

Sell = IIf(psize3 == 0,1,IIf(PsizeScaleout>0,sigScaleOut,0));
AddColumn(Buy,"Buy");
AddColumn(Sell,"Sell");
PositionSize = -PSize3;  // was size = change rather than absolute 
AddColumn(size,"size");
AddColumn(PositionSize,"PositionSize");

Here is the version with CBT

function Trace(tt, str) {
	_TRACE(DateTimeToStr(tt)+" "+str);
}

_SECTION_BEGIN( "NAME" );
formn = ParamStr( "set name", "Remix Signal" );
SetFormulaName( formn );
_SECTION_END();

DBVOn = ParamToggle("DebugView","OFF|ON", 0);
	if( DBVOn ) _TRACE("First line");
	
_SECTION_BEGIN("Basic Regular Setup");

 SetBacktestMode(backtestRegular);
	divlong = divshort = 1;
	SetOption("MinPosValue", 1 );
	SetOption("MinShares", .001 );
	MaxPositions = Param("max pos", 3,1,20,4); // Max Positions to hold, CW defaults to 10
	SetOption("MaxOpenLong", MaxPositions/DIVLONG );
	SetOption("MaxOpenShort", MaxPositions/DIVSHORT );
	HoldrankExt=param("HoldrankExt",0,0,4,1);	//extra positions beyound max positions to allow position to slip; default to 0
	SetOption("MaxOpenPositions", MaxPositions);
	SetOption("WorstRankHeld", MaxPositions +HoldrankExt);
	RoundLotSize = 0;

RModeN = Param("0=Trade,1=det,2=sum,3=no", 1,0,3,1);   // 0 - trade list; 1 - detailed log;2 - summary;3 - no output (custom only)
	SetOption("PortfolioReportMode", RModeN );
SetOption("SeparateLongShortRank", False );
SetOption("InitialEquity", 100000 );
//SetOption("AllowSameBarExit", False );
SetOption("ActivateStopsImmediately", False );
SetOption("AllowPositionShrinking", True );
SetOption("FuturesMode", False );
SetOption("InterestRate", 0 );

SetOption("MinPosValue", 1 );
SetOption("MinShares", .001 );
SetOption("PriceBoundChecking", True );
SetOption("ReverseSignalForcesExit", True ); 
SetOption("UsePrevBarEquityForPosSizing", FALSE );  // not sure?

SetOption("EveryBarNullCheck", False );
SetOption("HoldMinBars", 0 );
SetOption("EarlyExitFee", 0 );
SetOption("HoldMinDays", 0 );
SetOption("EarlyExitDays", 0 );
SetOption("DisableRuinStop", TRUE );
SetOption("GenerateReport", 1 );  //full report
SetOption("ExtraColumnsLocation", 1 ); // working

SetOption( "CommissionMode", 3 ); /* 3 set commissions $ per share; 1=% of trade */
comamt  =Param("com amt/shr", .01,.0,.5,.01);  // 
SetOption( "CommissionAmount", comamt ); /* per share  + SLIPPAGE */

MArlvl = param("marg %", 100, 50, 100.1, 10);
SetOption ("AccountMargin", MArLvl);   // 

_SECTION_END();	
	
	// same day Close
	SetTradeDelays( 0, 0, 0, 0 );  // 1 day delay
	BuyPrice = CoverPrice = SellPrice = ShortPrice = Close;  // trade the close


_SECTION_BEGIN("Rank Loop"); 
	a=b=d=buyr1=sellr1=ma20=rank=roc200i=rank2=rankcomb=Perf1=Sharp1=Sharp2=symlist=WatchLSz = NoS  = numberOfSymbols = Symctact=ROC1= HistoryTest = RocRatio = ROCTick1 = PSize2i= ROCTick2 = ROCTickN = PSize2i =0; 

// ---------------------------------------------------------------------------------------------- 
_SECTION_END();
	wlist = GetOption("FilterIncludeWatchlist"); 
 
if ( Status( "stocknum" ) == 0 )    
	{  	
	if (2 == 2 )  // Applies only to filter - needs open paren?
		{ 
			symlist = CategoryGetSymbols( categoryWatchlist, wlist ) ; 
			numberOfSymbols = StrCount( symlist, "," ) + 1;
			WatchLSz = NoS  = numberOfSymbols; 		 				 
			StaticVarRemove("*");  // delete ALL static variables 
		} 	
// --------------------------------watchlist Loop Pass 1 --------------------------------------------------
	for( i = 0; ( symbol= StrExtract( symlist, i ) ) != ""; i++ ) // Here i=1 symbol - looping through every symbol in watchlist of symbols
		{ 
		SetForeign(symbol);

		StaticVarAdd( "~SymbolCount", 1 );  // Should be same as WatchLSz  = "~SymbolCount" is new static variable 
		 
   		 Symctact = iif(c>0,1,0);  // Testing for activity C= CLOSE = closing price
		 StaticVarAdd( "~Symctact", Symctact );  // # active ticker on that bar = works
		 
		 HistoryTest = iif( ref(c,-100)>0,1,0);
		  if( DBVOn ) _TRACE("1 HistoryTest + sym " + HistoryTest + " " +  symbol);
		  
		 RocRatio = MA(c,3) / MA(c,100);
		 if( DBVOn ) _TRACE("1 RocRatio + sym " + RocRatio + " " +  symbol);
		 RocRatio12 = iif(RocRatio>1, 1,0);
		  if( DBVOn ) _TRACE("1 RocRatio12 + sym " + RocRatio12 + " " +  symbol);
		  
		 ROCTick1 = iif(RocRatio12==1 AND HistoryTest==1,1,0); //# of tickers active and passing filer test
		 
		 if( DBVOn ) _TRACE("1 ROCTick1 + sym " + ROCTick1 + " " +  symbol);
		 
		 StaticVarAdd( "~ROCFilTOT", ROCTick1 ); // sum tickers that get allocation now
 
		RestorePriceArrays(); 

		staticvarset("~RocRatio" +  symbol, RocRatio);  
		staticvarset("~RocRatio12" +  symbol, RocRatio12); //
		staticvarset("~ROCTick1" +  symbol, ROCTick1); // Add Stuff needs to be outside of loop to grab totals
		if( DBVOn ) _TRACE("1 ROCTick1 + sym o" + ROCTick1 + " " +  symbol);
		}	

// --------------------------------WL Loop Pass 2 -------------------------------------------------- 
	for( i = 0; ( symbol= StrExtract( symlist, i ) ) != ""; i++ ) // Here i=1 symbol - looping through every symbol in watchlist of symbols
		{ 
		SetForeign(symbol); 
		
		ROCTick2 = StaticVarGet ( "~ROCTick1" +  symbol ); // Individual ticker that passes
		if( DBVOn ) _TRACE("2 ROCTick2 + sym " + ROCTick2 + " " +  symbol);
		
		ROCtickN = StaticVarGet ( "~ROCFilTOT" );  // Total get # of  filtered ROC tickers for allocation works
		
		PSize2i = IIf(ROCTick2==1 AND ROCtickN>0, 100/ROCtickN,0);
		if( DBVOn ) _TRACE("2 PSize2i + sym " + PSize2i + " " +  symbol);
		
		StaticVarAdd( "~PSize", PSize2i );
		
		RestorePriceArrays(); 
		
		staticvarset("~PSize2i" +  symbol, PSize2i); 
		}
	 
}	

	 symbol = Name();
	//	RocRatio = MA(c,3) / MA(c,100);
	PSizetot = StaticVarGet( "~PSize" );  // total pos size allocation all tickers
	 RocRatioL1 = StaticVarGet ( "~RocRatio" +  symbol ); // 	
	 ROCTick3 = StaticVarGet ( "~ROCTick1" +  symbol  ); // Individual ticker that passes everything
	 ROCtickN2 = StaticVarGet ( "~ROCFilTOT" );  // get # of  filtered ROC tickers works
	 
	 PSize3 = StaticVarGet ( "~PSize2i"  +  symbol );
	 SymbolCount = StaticVarGet ( "~SymbolCount" ); // gross # of tickers
	 ActiveTicka = StaticVarGet ( "~Symctact" );  // get # active tickers
	
// ------------------  --------------------------			 

	RocRatiox = MA(c,3) / MA(c,100);
	HistoryTestx = iif( ref(c,-100)>0,1,0);
	
	Filter = 1;
	PSize3Prev = Ref(PSize3,-1);
	AddColumn( PSize3Prev , "PSize3Prev" , 1.2 );
	AddColumn( PSize3 , "PSize3" , 1.2 );


dt = DateTime();

//-------------------------------------------------------BACKTEST----------------------------------------------------------

Psizechg = PSize3 - PSize3Prev;  // Chg only 
PsizeScalein = IIf(Psizechg < 0,0,Psizechg);  // 
PsizeScaleout = IIf(Psizechg < 0,-Psizechg,0);
size =IIf(PsizeScalein>0,PsizeScalein,IIf(PsizeScaleout>0,PsizeScaleout,PSize3));  // added PSize3 end
AddColumn(Psizechg,"Psizechg");
AddColumn(PsizeScalein,"PsizeScalein");
AddColumn(PsizeScaleout,"PsizeScaleout");
//-----------------------------------------------------------------------------------------------------------------


Buy =iif(PSize3==0,0,IIf(PsizeScalein>0 ,sigScaleIn, IIf(PSize3>0,1,0)));

Sell = IIf(psize3 == 0,1,IIf(PsizeScaleout>0,sigScaleOut,0));
AddColumn(Buy,"Buy");

AddColumn(Sell,"Sell");

PositionSize = -PSize3;  //
AddColumn(PositionSize,"PositionSize");
AddColumn(size,"size");


//-------------------------------------------------------CBT----------------------------------------------------------

SetOption("UseCustomBacktestProc", True );
SetCustomBacktestProc("");
if ( Status( "action" ) == actionPortfolio ) 
{
    bo = GetBacktesterObject();
    bo.PreProcess();

    for ( bar = 0; bar < BarCount; bar++ )
    {
		entries = bo.GetSignalQty(bar, 1); // get entries
		exits   = bo.GetSignalQty(bar, 2);
        opens   = bo.GetOpenPosQty();
		openPositions = (entries+opens);
		_TRACEF("Day %g",bar);
		_TRACEF("Open Positions %g - Entry signals %g - Exit Signals %g",opens,entries,exits);
		
		for ( sig = bo.GetFirstSignal( bar ); sig; sig = bo.GetNextSignal( bar ) )
		{
			reqPosition = size[bar];

			eq = bo.Equity;
			if (sig.IsScale())
			{
				
				pos=bo.FindOpenPos(sig.Symbol);
				if(!IsNull(pos))
				{
					
				
					
					currentSize=pos.GetPositionValue()/bo.Equity();
					
					if(currentSize<reqPosition)
					{
						increaseSize=(reqPosition-currentSize);
						price = pos.GetPrice( bar, "C" );
						_TRACEF(pos.Symbol+"(%g) Entry signal scaling In position, Current Size %g , new Size %g",pos.Shares,currentSize,reqPosition/100*eq);
						bo.ScaleTrade( bar, pos.Symbol,1, price,increaseSize);
						
					}
					
					else
					{
						increaseSize=(reqPosition-currentSize);
						price = pos.GetPrice( bar, "C" );
						_TRACEF(pos.Symbol+"(%g) Entry signal scaling In position, Current Size %g , new Size %g",pos.Shares,currentSize,reqPosition/100*eq);
						bo.ScaleTrade( bar, pos.Symbol,0, price,-increaseSize);
					}
				
				}
			}
			
			
		}
		
		bo.ProcessTradeSignals( bar );
	}
    bo.PostProcess();
}	


@mradtke & others - if you could take another look, much appreciated!

There are settings which influence the order in which AmiBroker processes signals (entries before exits or vice versa), including Allow Same Bar Exits. However, if you really need complete control over the order in which entries, scale-ins, scale-outs, and exits are performed, then you're probably best off with a low-level CBT.

In your CBT code, you are trying to do your scaling in and out before calling bo.ProcessTradeSignals(). First, I'm not a big fan of mixing mid-level and low-level CBT calls, although I don't believe it's strictly forbidden. More importantly though, AmiBroker has not performed ANY of the entries and exits for the bar at the time that you're doing your scaling, so of course no cash has been freed up from exits. Also, unless you've taken measures to prevent AB from processing the scaling signals, it may be the case that you are processing those signals yourself once and AB is trying to process them a second time in bo.ProcessTradeSignals().

6 Likes

@mradtke Sorry for delay in response - I did not have notification set.
Your comments are once again really helpful and insightful; if only I had 1/2 of your coding skills!

This should give us the path to the solution; will plan to follow-up and post.

Kind Regards,
Scott

@swalk10 Atleast hit the like button on @mradtke's posts, he's been helping both of you and none so far :frowning:

Yes, did that

I tend to think words are more important, but good to do both.

FWIW: if you read How to use this site you will understand that "Likes" count in user stats, "words" don't (unfortunately).