Exploration Results differ from PositionScore choices using rankings from Static Variables

I'm having an issue where, when comparing the exploration results of the highest ranked stocks in an exploration (using the exact same system code) on an/any specific date and then compare the stocks that were actually chosen by the system on the same exact specific dates, varies significantly.

The code is designed to rank according to two distinct rankings using a slightly modified Static Variable method outlined here in the last example by creating multiple static variables and combining them -- https://www.amibroker.com/guide/h_ranking.html

  • The system rotates a list of 20 stocks monthly
  • When I run the same code in exploration on any of the dates shown by the system, the purchase decisions for the list of stocks are different even though the buy criteria uses the same combined ranking i.e. PositionScore = combined ranking

In other words, if I run the system and look at any specific date and then run the exploration using exact same code, the 20 stocks that rank highest in the exploration do not match those chosen by positionscore. In fact, the stocks chosen from the PositionScore decision making process are way down the ranking list versus the top rankings shown in the exploration.

I hope it won't be difficult to picture this scenario without posting the entire code, otherwise perhaps I can post the code and take out some things. I usually don't mind sharing most code but I'm testing this system on information given from someone else and that person would prefer I do not make it public knowledge.

Thank you. Regards,

DaveDM

@DaveDM

it isn't difficult to understand what you are saying. But, you almost certainly have a mistake in your code. So for anyone to help you correct that code it would be ridiculous to even try without looking at it, testing it, attempting corrections to it.

Trust me, you have not discovered the Holy Grail, but if you are really hesitant to reveal you secret sauce, just make simple change to your ranking generation (that is your secret, not the idea of a rotational strategy) and plug in something generic like a momentum score. If that gives you the same problem that you've been seeing, then post this code and members of the forum can try to help.

If a simple momentum score corrects your problem then you have at least narrowed it down to the "secret sauce" being your source of mistakes.

Good luck.

4 Likes

In addition to @portfoliobuilder 's comments, make sure that if you're using trade delays for entry that you're comparing the Exploration list from the bar that the entry signal was generated, not the bar that it was executed.

@portfoliobuilder

TY. I simplified the code and here it is. Funny thing is, it now seems to work as intended. The real code is not that much different from the simplified code except for the calculation of each value in the Rank section. Perhaps there is some complexity or nuance in the actual formulae used that is throwing off the ranking somehow. In this example you can see that if you run a back test in one Analysis window and an Exploration of a specific date in another Analysis window, the stock picks actually line up for that specific buy date. I was looking at 02/03/2018. I'm scratching my head right now... :-/


// Position Sizing

maxpos = param("Maximum Positions", 20, 1, 100, 1); // maximum number of open positions
startequity = Param("Starting Equity", 100000, 5000, 1000000, 1000);
SetOption("InitialEquity", startequity ); // set initial equity = 100K
SetOption( "MaxOpenPositions", maxpos );
SetPositionSize( 100 / maxpos, spsPercentOfEquity );


// Calculate EMA's
	SMA1 = MA(Close, 100);
	SMA2 = MA(Close, 200);
	
// No fractional shares
RoundLotSize = 1;


BuyDayofMonth = param("Buy Day of Month", 1, 1, 31, 1); // 1 and 2 best optimized values

d = Day();
m = Month();

BuyDay[ 0 ] = d[ 0 ];
for( j = 1; j <= 31; j++)

{

if ((d[j] == BuyDayofMonth) OR ((d[j] == BuyDayofMonth + 1 OR d[j] == BuyDayofMonth + 2 OR d[j] == BuyDayofMonth + 3 OR d[j] == BuyDayofMonth + 4) AND Buy = 0));
{	
	


if ( GetOption( "ApplyTo" ) == 2 )
{
     wlnum = GetOption( "FilterIncludeWatchlist" );
     List = CategoryGetSymbols( categoryWatchlist, wlnum ) ;
}
else
if ( GetOption( "ApplyTo" ) == 0 )
{
     List = CategoryGetSymbols( categoryAll, 0 );
}
else
{
     Error( "The formula works fine if your ApplyTo setting is 'Filter' or 'All' " );
}


// Rank1

if ( Status("stocknum") == 0 ) 
{
    StaticVarRemove( "values1*" );

    for ( m = 0; ( Symbol = StrExtract( List, m ) )  != "";  m++    )
    {
        SetForeign ( symbol );        
             
        values1 = RSI(14);
        
        RestorePriceArrays();
        StaticVarSet ( "values1" + symbol, values1 );
        _TRACE( symbol );
    }

    StaticVarGenerateRanks( "rank1", "values1", 0, 1224); 
}

symbol = Name();

values1 = StaticVarGet ( "values1" + symbol );
rank1 = StaticVarGet ( "rank1values1" + symbol );

AddColumn ( values1, "Values1" );
AddColumn ( rank1, "Rank1" );


Filter = 1;

SetSortColumns( 2, 4 );

// Rank2

if ( GetOption( "ApplyTo" ) == 2 )
{
     wlnum = GetOption( "FilterIncludeWatchlist" );
     List = CategoryGetSymbols( categoryWatchlist, wlnum ) ;
}
else
if ( GetOption( "ApplyTo" ) == 0 )
{
     List = CategoryGetSymbols( categoryAll, 0 );
}
else
{
     Error( "The formula works fine if your ApplyTo setting is 'Filter' or 'All' " );
}


if ( Status("stocknum") == 0 ) 
{
    StaticVarRemove( "values2*" );

    for ( m = 0; ( Symbol = StrExtract( List, m ) )  != "";  m++    )
    {
        SetForeign ( symbol );        
       
        values2 = RSI(20);
        
        RestorePriceArrays();
        StaticVarSet ( "values2" + symbol, values2 );
        _TRACE( symbol );
    }

    StaticVarGenerateRanks( "rank2", "values2", 0, 1224); 
}

symbol = Name();

values2 = StaticVarGet ( "values2" + symbol );
rank2 = StaticVarGet ( "rank2values2" + symbol );

AddColumn ( values2, "Values2" );
AddColumn ( rank2, "Rank2" );

Filter = 1;

SetSortColumns( 2, 4 );


// Combine Values

combinedrankvalues = rank1 + rank2 ;
combinedrank = (1/combinedrankvalues) ; // Use for inverting so largest number for lowest/best combined rank for when using PositionScore

AddColumn( combinedrankvalues, "Combined Rank Values" ) ;
AddColumn( combinedrank, "Combined Rank" ) ;


PositionScore = combinedrank ; 
		
	
	Buy = Ref(C, -1) > SMA1 AND Ref(C, -1) > SMA2;
}
 
 
}  
   
  Mnth = Month();
     
    BuyDay[ 0 ] = d[ 0 ];
	for( j = 1; j <= 31; j++)
{

		Sell = (Month() != Ref(Mnth, -1) and (d[j] == BuyDayofMonth OR d[j] == BuyDayofMonth + 1 OR d[j] = BuyDayofMonth + 2 OR d[j] == BuyDayofMonth +3 OR d[j] == BuyDayofMonth + 4)) ;
		
}
   

BuyPrice = close;
SellPrice = close;




@DaveDM I hope that has solved your problem, but may I at least point out to you that your original post mentioned

But in your code you are not using AmiBroker's built in rotational settings such as,

SetBacktestMode( backtestRotational );

If you had then you would not use regular buy/sell/short/cover signals at all.

Perhaps worth reviewing,
https://www.amibroker.com/guide/afl/enablerotationaltrading.html

http://www.amibroker.com/kb/2016/01/30/separate-ranks-for-categories-that-can-be-used-in-backtesting/

http://www.amibroker.com/kb/2016/04/17/long-only-rotational-back-test/

And a search on this forum will find maybe a dozen threads which discuss various rotational strategies.
image

And lastly your code (perhaps because you only permitted the world a glimpse of a portion) is full of syntax errors,

image

Please use techniques described here: How do I debug my formula?

Thank you for your feedback, I'll circle back over these recommendations and re-write my code accordingly. I originally had the system designed as a rotational system, it might make sense to return to that.

@portfoliobuilder

Thank you for your feedback. I re-wrote the strategy as a rotational backtest strategy.

I have a few challenges with this --

1 - I am unclear on how (or whether it's even possible) to exist specific positions when certain conditions are met while in rotational mode, for example, if one of the positions falls below a specific moving average, is it possible to exit just that (or those) positions AND rotate into another position right away, or does the portfolio have to wait until the next rotation?

2 - If a market proxy like the SPY is below it's specific moving average, is it possible to use as a macro filter to exit all positions (either right away or one a specific rotation date) and stay out of the market until that condition is again met?

3 - Is it possible to select specific days of the month for the rotation to occur... it appears much easier to do weekly rotations based on Day() == Number_Representing_Day_of_Week?

Hope that makes sense. Thank you.

Regards,

Dave

@DaveDM Re: #1, I haven't attempted anything like that but I think it is possible. The rotational mode uses only score variable (PositionScore) to rank and rotate securities. Exits are generated automatically when security's rank drops below "worst rank held". There is no real control over when exits happen except of setting low score to force exits, so code your system to set a low score if a position falls below a specific moving average. Maybe something like this (untested code),

RawScore = ..your original score calculations...

EarlyExitFilter = Close < EMA( Close, 100 );

PositionScore = IIF( EarlyExitFilter, 0, RawScore);

Re: #2, sure that is a very commonly used idea. On your rotation day you can use a Market Regime Filter by incorporating this filter into your PositionScore (I have seen code where filters are used to change PositionSize instead, for example setting PositionSize to zero).

Something like,

Index = Foreign( "SPY", "C" );
MarketRegimeFilter = Index > MA( index, 200 );

RotationDay = …your original identification of your rotation day
RawScore = ..your original score calculations...

Score = IIf(MarketRegimeFilter, RawScore, 0 );
PositionScore = IIf( RotationDay, Score, scoreNoRotate );

You may be interested in reading about scoreExitAll though I have no experience with it,
https://www.amibroker.com/guide/afl/enablerotationaltrading.html

Re: #3, Sure, you can essentially do the same thing monthly by calculating which trading day of the month you want to rotate on. Depending on how you choose to identify the trading day it is worth being careful if you want to be near the end of the month as there are different numbers of trading days in different months. On NYSE past couple of years range from 19-23 trading days per month.

An example of calculating trading day of the month ( as with many ideas you can code into AmiBroker different methods to get the same results).

// Counting the actual Trading Days each month

TradingDayThisMonth = BarsSince( Day() < Ref(Day(),-1) ) + 1;

// alternate way of doing the same Trading Day of this month
alternateCode = BarsSince(Month() != Ref( Month(), -1) )+1;

// let's identify a specific day
ThirdDay = TradingDayThisMonth==3;

/////////////////////////////////////////////////
//  Explore to debug our newly created variables
/////////////////////////////////////////////////
Filter=1;
AddColumn(Close, "Close");
AddColumn(TradingDayThisMonth, "TradingDayThisMonth",1);
AddColumn(alternateCode, "alternateCode",1, colorDefault, colorLightYellow);
AddColumn( ThirdDay,"Third Day",1,colorBlue, IIf(ThirdDay, colorYellow,colorDefault));

Plot(TradingDayThisMonth, "TradingDayThisMonth", colorGreen, styleStaircase);

Looking at what those variables are creating,
image

Hope that helps, good luck!

3 Likes

Thanks @portfoliobuilder for this response, I'll study in more detail and implement the ideas. Much appreciated :slight_smile: !