Dynamic PORTFOLIO size (not position size)

Hi, I've searched these forums extensively as well as the User's Guide and I haven't found example code for what I'm looking to do. I have a simple rotational strategy I'd like to run on a basket of about 14 ETFs, but one caveat is that I require the symbols chosen for investment to be above a certain threshold--in this case must exceed the performance of the Russell 3000 ETF IWV (the ratio needs to be greater than 1 showing outperformance). Some months there may be only 3 symbols that satisfy the requirement. Other months there may be as many as 9 symbols. My questions is how to I tell Amibroker to proportion the available equity to 3 symbols some months, and 9 symbols in others?
Can it be done with rotational trading/positionscore? I'm not sure how to use the "spsPercentofEquity" option when I'm not sure how many positions will be selected. Thanks for any help or pointing me in the right direction.
Cheers!
Tony

EnableRotationalTrading();
/*
NumberHeld=15;
PositionSize=-100/Numberheld;

SetOption("MaxOpenPositions",NumberHeld);
*/

RUA=Foreign("IWV","C");
RUAroc=ROC(RUA,100);

symROC=ROC(C,100)/RUAroc;
ROCthresh=IIf(symROC>1,symROC,0);

monthend=Month()!=Ref(Month(),-1);
//QuarterEnd = MonthEnd AND Month()%3 == 0;
//Market Regime Filter
bullmarket=Foreign("~BULLBEAR","C")==2;

PositionScore=IIf(bullmarket,(IIf(monthend,ROCthresh,scoreNoRotate)),scoreExitAll);

type or paste code here

I don't believe that the built-in rotational mode includes the functionality that you're trying to implement. I'm happy to be corrected if another forum member knows how to do this.

Your goals could be easily achieved using a custom back test, but writing a CBT is a more advanced approach suitable only for those with a solid understanding of AFL and programming in general.

Hi thank you for the response, I'm happy to pursue the answer myself but if more experienced programmers know which avenues won't work that's a huge timesaver as well. Rotational trading is such a huge feature of Amibroker that I try to use it as often as possible, but apparently not so in this case. Thanks again
Tony

@TonyR perhaps the GTAA framework posted here offers some foundation for your project.

You really don't need the CBT for this, and it is fairly straightforward.

Think about the process. Amibroker iterates thru each ticker in your watchlist. At each ticker, if you had a count by bar of the number of tickers in the wathclist that pass your criteria, then -1/count*100 would give you a position size. You can set this and then let the PositionScore you already have control entries and exits.

So, you need a "pre-process" before the ticker iteration. Within that step you loop thru the tickers and keep a static var with the ROCthresh by ticker and a single static var of the count of tickers that satisfy the threshold. This "pre-process" is accomplished by -

if ( Status( "stocknum" ) == 0 ) { }

You will be left with two complications that you may choose not to deal with. You will have months when the count increases and decreases and will have to decide if the percentage of existing positions needs to be adjusted (rebalanced). That will require the CBT. My suggestion would be to get results that are in the ballpark first, and then consider if you need
CBT rebalancing.

5 Likes

@TonyR posted solution is pending in moderation. That's a first...

2 Likes

The following code is an attempt to implement the requested dynamic portfolio size method, using the RUAroc benchmark and the market regime filter (now: always true). The code has a couple of extras: benchmark fund, top size, and position size mode are user adjustable through the Parameters window. Exploration view is possible for: all bars, end of months, or last bar only. The code is designed for EOD data.

NB! Somewhere is a bug in hiding, because the position size values are greyed out in exploration view. Hence the dynamic coloring doesn't work (yet?) and is switched off. Anyone?

/* DynamicPortfolioSize_v1.afl */

// --- begin of code ---

// --- detect tradelist ---
wlnumber        = GetOption( "FilterIncludeWatchlist" );
watchlist       = GetCategorySymbols( categoryWatchlist, wlnumber );
NumberOfFunds   = StrCount( watchlist, "," ) + 1;
NoF             = NumberOfFunds;

// --- input benchmark fund ---
_benchmark      = ParamStr( "Benchmark Symbol", "IWV" );

// --- rebalance frequency ---
frequency       = ParamList( "Rebalance Frequency:", "Monthly|Bi-Monthly|Quarterly|Annually", 0 );

// --- detect period ends ---
MonthEnd        = Month() != Ref( Month(), 1 );
BiMonthEnd      = Month()%2  == 0 AND MonthEnd;
QuarterEnd      = Month()%3  == 0 AND MonthEnd;
YearEnd         = Month()%12 == 0 AND MonthEnd;

// --- init rebalancing frequency ---
if ( frequency == "Monthly"    ) Rebalance   = MonthEnd;
if ( frequency == "Bi-Monthly" ) Rebalance   = BiMonthEnd;
if ( frequency == "Quarterly"  ) Rebalance   = QuarterEnd;
if ( frequency == "Annually"   ) Rebalance   = YearEnd;
Rebalance = Rebalance OR Status( "LastBarInTest" );

// --- top selection ---
TopSize         = Param( "Number of Top Positions:", NoF, 1, NoF, 1 );
PosSizeMode     = ParamToggle( "Position Size Mode:", "Fixed|Spread", 0 );

// --- backtester settings ---
SetBacktestMode( backtestRegular );
SetOption( "CommissionAmount", 0.00 );
SetOption( "InitialEquity", 100000 );
SetTradeDelays( 0, 0, 0, 0 ); 
SetOption( "MaxOpenLong", NoF );
SetOption( "MaxOpenPositions", NoF );
SetOption( "AllowPositionShrinking", True );
SetOption( "AllowSameBarExit", True ); 
SetOption( "ReverseSignalForcesExit", False ); 
SetOption( "HoldMinBars", 1 );
SetOption("ExtraColumnsLocation", 11 ); 
RoundLotSize = 1;

// --- init values ---
count = SumPosSize = 0;

// --- asset selection and capital allocation routine ---
// based on https://groups.yahoo.com/neo/groups/amibroker/conversations/topics/178791
if ( ( Status( "stocknum" ) == 0 OR Status("stocknum") == -1 ) ) // AND Status("actionex") != actionPortfolio )
{
    // --- remove staticvars ---
    StaticVarRemove( "RUA*"   );
    StaticVarRemove( "bull*"  );
    StaticVarRemove( "sym*"   );
    StaticVarRemove( "ROC*"   );
    StaticVarRemove( "count*" );
    StaticVarRemove( "Rank*"  );
    StaticVarRemove( "Pos*"   );
    StaticVarRemove( "Sum*"   );

	// --- import data for benchmark fund (default: IWV) ---
	RUA 	= Foreign( _benchmark, "C" );
	RUAroc  = ROC( RUA, 100 );
	
	StaticVarSet( "RUAroc", RUAroc );

	// --- setup of market regime filter ---
	// bullmarket = Foreign( "~BULLBEAR","C" ) == 2;
	bullmarket = 1; // <--- remove this line and de-comment prior line

	StaticVarSet( "bullmarket", bullmarket );
	
	// --- loop for collecting outperformance data ---
    for ( i = 0; ( symbol = StrExtract( watchlist, i ) )  != "";  i++ )
    {
		// --- retrieve stored values ---
		RUAroc     = StaticVarGet( "RUAroc" );

        SetForeign( symbol );
       
			// --- load quotes ---
			Data       = Close;
			
			// --- test outperformance ---			
			symROC	   = ROC( Data, 100 ) / RUAroc;
			ROCthresh  = IIf( symROC > 1, symROC , 0 );
			
			// --- count number of outperforming funds ---
			count	   = IIf( symROC > 1, count + 1, count );
						
		RestorePriceArrays();
		
		// --- store threshold value ---
        StaticVarSet( "symROC"    + symbol, ROCthresh );
        StaticVarSet( "ROCthresh" + symbol, ROCthresh );
    }

	// --- store count ---
    StaticVarSet( "count", count );
    
    // --- generate ranks ---
    StaticVarGenerateRanks( "Rank_", "ROCthresh", 0, 1224 );
    
    // --- loop for position sizing ---
    for ( i = 0; ( symbol = StrExtract( watchlist, i ) )  != "";  i++ )
    {
    	// --- retrieve stored values ---
    	Rank_ROCthresh = StaticVarGet( "Rank_ROCthresh" + symbol );
    	ROCthresh      = StaticVarGet( "ROCthresh"      + symbol );
    	count          = StaticVarGet( "count"                   ) + 0.00000001;
    	bullmarket     = StaticVarGet( "bullmarket"              );
    	   	
        // --- position sizing ---
        // fixed pos.size:       
        if ( PosSizeMode ) PosSize = IIf( Rank_ROCthresh <= TopSize AND ROCthresh > 0 AND bullmarket, 100 / NoF, 0 );
        // spread total over outperforming funds:							
		else               PosSize = IIf( Rank_ROCthresh <= TopSize AND ROCthresh > 0 AND bullmarket, IIf( Count >= 1, 100 / Min( count, TopSize ), 0 ), 0 );

		
		PosSize         = IIf( Status( "BarInRange" ), PosSize, 0 );
		SumPosSize      = SumPosSize + PosSize;
		
		// --- store values ---
		StaticVarSet( "PosSize" + symbol, PosSize );
	}
	
	StaticVarSet( "SumPosSize", SumPosSize );
}

// --- retrieve values ---
symbol         = Name();
RUAroc         = StaticVarGet( "RUAroc"                  );
Rank_ROCthresh = StaticVarGet( "Rank_ROCthresh" + symbol );
symROC         = StaticVarGet( "symROC"         + symbol );
ROCthresh      = StaticVarGet( "ROCthresh"      + symbol );
PosSize        = StaticVarGet( "PosSize"        + symbol );
SumPosSize     = StaticVarGet( "SumPosSize"              );
count          = StaticVarGet( "count"                   );
bullmarket     = StaticVarGet( "bullmarket"              );

// --- set position sizes ---
SetPositionSize( PosSize, spsPercentOfEquity );

// --- re-balance at the end/close of every month ---
Buy          = Rebalance AND PosSize > 0;
Sell         = Rebalance;
Short        = Cover = 0;
BuyPrice     = Close;
SellPrice    = Close;

// --- exploration filter ---
ExploreFilter = ParamList( "Rebalance Frequency:", "All Bars|End of Month|Last Bar" );
if ( ExploreFilter == "All Bars"     ) Filter = 1;
if ( ExploreFilter == "End of Month" ) Filter = Month() != Ref( Month(), 1 ) OR Status( "LastBarInTest" );
if ( ExploreFilter == "Last Bar"     ) Filter = Status( "LastBarInTest" );

// --- sort for exploration only (not on backtest) ---
if ( Status( "actionex" ) == actionExplore ) 
{
	SetSortColumns( 2, 7 );

	// --- columns for exploration ---
	ColorROC  = IIf( ROCthresh > 1, colorBrightGreen, colorWhite );
	ColorPos  = IIf( PosSize > 0  , colorGold       , colorWhite );
	
	AddColumn( RUAroc         , "RUAroc"         , 3.3              );
	AddColumn( ROC(Close,100) , "ROC(100) (%)"   , 3.3              );
	AddColumn( symROC         , "symROC"         , 3.3, 1, ColorROC );
	AddColumn( ROCthresh      , "ROCthresh"      , 3.3, 1, ColorROC );
	AddColumn( Rank_ROCthresh , "Rank"           , 1.0              );
	//AddColumn( PosSize        , "PosSize (%)"    , 3,3, 1, ColorPos );
	AddColumn( PosSize        , "PosSize (%)"    , 3,3              );
	AddColumn( SumPosSize     , "SumPosSize (%)" , 3.3              );		
	AddColumn( count          , "# Total.Count"  , 1.0              );		
	AddColumn( bullmarket     , "Market Filter"  , 1.0              );		
}

// --- end of code ---
type or paste code here
4 Likes

@TrendXplorer, re the minor coloring issue:

	AddColumn( PosSize        , "PosSize (%)"    , 3.3, 1, ColorPos );

Seems a comma was used instead of a dot! (Also in the alternative uncommented line...)

3 Likes

Kudos to @beppe for spotting the b-u-g :wink:

Apart from replacing the line quoted by @beppe please replace the following snippet too ( !PosSizeMode instead of PosSizeMode ):

        // --- position sizing ---
        // fixed pos.size:       
        if ( !PosSizeMode ) PosSize = IIf( Rank_ROCthresh <= TopSize AND ROCthresh > 0 AND bullmarket, 100 / NoF, 0 );
        // spread total over outperforming funds:							
		else               PosSize = IIf( Rank_ROCthresh <= TopSize AND ROCthresh > 0 AND bullmarket, IIf( Count >= 1, 100 / Min( count, TopSize ), 0 ), 0 );

Updated code: DynamicPortfolioSize_v1.1.afl (6.8 KB)

6 Likes

Thank you TrendXplorer, ABbruiser, Beppe and others! I truly and deeply appreciate the help!
Kindest Regards,
Tony R