Access Each Security Each Bar in Backtester Interface?

Hello,

Is there access in the Porfolio Backtester Interface to each security (each stock in the backtest) for each bar?

I’d like to use that to code two ideas:

*** IDEA 1 *************************************

I’d like to create a Monthly or Weekly Holdings Report – a rectangular array with one row per week or month of each holding’s weight. It would look something like:

Date SPY MDY IWM Cash

09/30 1.00 0.00 0.00 0.00

10/31 0.50 0.50 0.00 0.00

11/30 0.33 0.33 0.33 0.00

12/31 0.00 0.00 0.00 1.00

*** IDEA 2 *************************************

I’d like to run a backtest where all held securities are equally weighted. So after the 1st buy signal the security is weighted 100%. After the 2nd buy signal, each is weighted 50%, etc.

Basically, for each (monthly) bar, I’d store the number of open positions in nOpen, then set each held security’s weight to 100%/nOpen.

I guess I’d do this by looping over all instruments each bar checking to see if they are (bought OR (held and not sold)).

Example: 3 instruments: SPY, MDY, IWM.

Assume, after the close that
SPY is already held and not sold.
MDY was not held and has a buy signal.
IWM was not held and has no buy signal.
Then nOpen=2 & weight SPY & MDY at 50%.

If only SPY were held it would be at 100%.
If all three were held each would be at 33.3%.
If three were equally held and then two were sold then the remaining one would be held at 100%.


Thanks for any tips

Here's an example of using the CBT to rebalance open positions.

You can use GetOpenPosQty to determine how many positions are open on any bar, then calculate your required size and use ScaleTrade as in the example above.

image

If you save the bar-by-bar position sizes into Static Variables for each symbol, you can then easily produce your report with Explore, after the backtest has been run.

So in the CBT, when you're looping through the open positions within a bar loop, you can use something like this (remembering first to use StaticVarRemove at the commencement of the CBT):

MyCalcSize = // Your calculation for size for this symbol on this bar
SymbolPosSize = Nz(StaticVarGet("PosSize" + trade.Symbol));
SymbolPosSize[i] = MyCalcSize; // set current bar's size (i being the bar loop iteration variable)
StaticVarSet("PosSize" + trade.Symbol, SymbolPosSize); // Save result back to static variable

Then your exploration would call the static variables created in the CBT.

Filter = Name() == "SPY"; // Single set of results when ran against a watchlist
AddColumn(StaticVarGet("PosSize" + "SPY"), "SPY");
AddColumn(StaticVarGet("PosSize" + "MDY"), "MDY");
AddColumn(StaticVarGet("PosSize" + "IWM"), "IWM");
2 Likes

Thanks! I’ll give it a go…

Here’s what I came up with for IDEA 2, above: “I’d like to run a backtest where all held securities are equally weighted. So after the 1st buy signal the security is weighted 100%. After the 2nd buy signal, each is weighted 50%, etc.”

I had to go with the low level backtester interface and do sells first to free up cash then {rescale open positions and do buys} weighted at -100/myNewPosCount.

Thanks to all for the helpful tips above and elsewhere in the forum.

//************************************************************************************************************************
// Options
//************************************************************************************************************************

Version( 6.20 );
SetOption("InitialEquity",			1000000	);
SetOption("MinShares",				0		);
SetOption("DisableRuinStop",		True	);
SetOption("FuturesMode", 			False	);
SetOption("AllowPositionShrinking",	True    );
SetOption("AccountMargin",			100	    ); // 100 = no margin
SetOption("CommissionMode",         1       ); // percent of trade
SetOption("CommissionAmount",       0.00    ); // no commissions
SetOption( "ExtraColumnsLocation", 	1 		); 
SetTradeDelays( 0, 0, 0, 0                  ); // no trade delays

//************************************************************************************************************************
// Model
//************************************************************************************************************************

FastMAbars = Optimize("Fast MA",  4, 2,  5, 1);
SlowMAbars = Optimize("Slow MA", 20, 6, 20, 1);

FastMA = MA( C, FastMAbars );
SlowMA = MA( C, SlowMAbars );

Buy    = Cross( FastMA, SlowMA );
Sell   = Cross( SlowMA, FastMA );

Buy = ExRem(  Buy,  Sell );
Sell = ExRem( Sell, Buy ); 

//************************************************************************************************************************
// Low-Level Backtester Interface
//************************************************************************************************************************

SetCustomBacktestProc( "" );

if( Status( "action" ) == actionPortfolio ) {
	
    bo = GetBacktesterObject();			
    bo.PreProcess();					
    
	for( i = 0; i < BarCount; i++ )	{	

		// set N current positions, buy signals, sell signals, then calc myNewPosCount
		
		myOldPosCount = 0;	for( pos = bo.GetFirstOpenPos(); pos; pos = bo.GetNextOpenPos() ) {	myOldPosCount++;}
		
		myBuySigCount = 0; 
		mySellSigCount = 0; 
		for( sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) ) {	
		
			if (sig.IsEntry() AND sig.IsLong()) {myBuySigCount++;}
			if (sig.IsExit()  AND sig.IsLong()) {mySellSigCount++;}
		}
		
		myNewPosCount = myOldPosCount + myBuySigCount - mySellSigCount;
		
		// *** process exits first to create cash
		for (sig = bo.GetFirstSignal(i); sig; sig = bo.GetNextSignal(i)) { if (sig.IsExit() AND sig.IsLong()) bo.ExitTrade(i, sig.Symbol, sig.Price); }

		// *** rebalance current positions
		for( pos = bo.GetFirstOpenPos(); pos; pos = bo.GetNextOpenPos() ) {
			bo.ScaleTrade(i, pos.Symbol, (((0.9999*bo.Equity/myNewPosCount) - pos.GetPositionValue) > 0), pos.GetPrice(i,"C"), abs((bo.Equity/myNewPosCount) - pos.GetPositionValue));
		}

		// *** process entries
		for (sig = bo.GetFirstSignal(i); sig; sig = bo.GetNextSignal(i)) { 
			if (sig.IsEntry() AND sig.IsLong()) {
				sig.PosSize = -99.99/myNewPosCount;
				bo.EnterTrade(i, sig.Symbol, True, sig.Price, sig.PosSize); 
			}
		}
		
		//bo.HandleStops(i);	// system is stop-less
		
		bo.UpdateStats(i,1);
		bo.UpdateStats(i,2);
		
	}	//*** end i loop over bars ***
	
    bo.PostProcess();
}

1 Like