After contacting @ArnaudP in private, he asked me to share the FAA code I have for monthly data.

Please do comment on possible improvements. I am especially interested how to modify the code to work with daily data while (still) observing the exact monthly endpoints (not being i.e. 4 x 21).

Importantly: this code is not to correct the matrix approach shared by @fxshrat. It's just another way, originating from the era when AmiBroker did not have build-in matrix functions.

In the below code the correlation matrix is calculated in two different ways. For Explorations, the code uses a loop in similar fashion to the earlier shared approached. For the rotational calculations to determine the monthly position sizes, the code uses a custom function CalcPfC( symbol, tickerlist, length ).

Acknowledgement goes to the old Yahoo board member SanzProphet, who kindly helped me along with this code back in 2014.

In Exploration View the result looks like:

Running the code on the N10 investment universe and the separate out of market fund as shown on the screenshot with TopSize=10, results in the following long term performance chart:

Point to note: next to the filterlist with both all investment assets as well as the out of market fund, the code needs a dedicated watchlist with only the investment assets. Otherwise the correlation calculations are off.

```
////////////////////////////////////////////////////////////////////////////////////////
//
// --- FAA_Dual_Monthly_v1.1.afl ---
//
// --- introduction ---
//
// based on:
// "Generalized Momentum and Flexible Asset Allocation (FAA), An Heuristic Approach"
// Keller and Van Putten, 2012
// FAA Electronic copy available at: http://ssrn.com/abstract=2193735
//
// FAA strategy rules:
// M: Calculate 4 months relative momentum on monthly returns, then rank (highest is best)
// V: Calculate 4 months standard deviation on monthly returns, then rank (lowest is best)
// C: Calculate 4 month average correlation on monthly prices, then rank (lowest is best)
// Select top 3 assets based on final MVC ranking: 100% M + 50% V + 50% C
// From the top 3 replace asset with M <= 0 for cash (instead of FAA cash proxy: VFISX)
// Rebalance monthly
//
// --- afl-version ---
//
// AmiBroker implementation by TrendXplorer: www.trendxplorer.info
// in collaboration with SanzProphet: sanzprophet.blogspot.com
// original version: Feb. 18, 2014, modified for BacktestRotational: Aug. 18, 2018
//
// STRATEGY SETUP:
// 1 - ADJUSTING START AND END DATES
// 2 - USE BACKTESTER SETTINGS FOR MONTHLY PERIODICITY
// 3 - SELECT FILTERLIST
// 4 - START BACKTEST
//
//
////////////////////////////////////////////////////////////////////////////////////////
// --- begin of code ---
// --- inputs ---
frequency = ParamList( "Rebalance Frequency:", "Monthly|Bi-Monthly|Quarterly|Annually", 0 );
// --- detect invest universe ---
wln_market = Param( "WatchListNumber for Market Assets:", 11, 0, 150, 1 );
marketlist = GetCategorySymbols( categoryWatchlist, wln_market );
NoM = StrCount( marketlist, "," ) + 1;
TopSize = Param( "Number of Positions", 3, 1, NoM, 1 );
MomLength = Param( "Momentum Period" , 4, 1, 12, 1 );
VolLength = Param( "Volatility Period" , 4, 1, 12, 1 );
CorLength = Param( "Correlation Period" , 4, 1, 12, 1 );
wM = Param( "Momentum Weight" , 1.0, 0.0, 1.0, 0.1 ); // weight of momentum
wV = Param( "Volatility Weight" , 0.5, 0.0, 1.0, 0.1 ); // weight of volatility
wC = Param( "Correlation Weight" , 0.5, 0.0, 1.0, 0.1 ); // weight of correlation
_Cashticker = ParamStr( "Cash Proxy", "SHY" );
showCorTable = ParamToggle( "Show Correlation Table:", "No|Yes", 1 );
// --- detect tradelist ---
wlnumber = GetOption( "FilterIncludeWatchlist" );
filterlist = GetCategorySymbols( categoryWatchlist, wlnumber );
// --- backtester settings ---
SetBacktestMode( backtestRegular );
SetOption( "CommissionAmount", 0.00 );
SetOption( "InitialEquity", 100000 );
SetTradeDelays( 0, 0, 0, 0 );
SetOption( "MaxOpenLong", TopSize );
SetOption( "MaxOpenPositions", TopSize );
SetOption( "AllowPositionShrinking", True );
SetOption( "AllowSameBarExit", True );
SetOption( "ReverseSignalForcesExit", False );
SetOption( "HoldMinBars", 1 );
SetOption("ExtraColumnsLocation", 11 );
RoundLotSize = 0.0001;
// --- 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" );
// --- function for portfolio calculation provided by SanzProphet ---
function CalcPfC( symbol, tickerlist, length )
{
// This function takes a symbol from a watchlist
// and calculates the average correlation of the
// symbol to the other symbols
CorSum = 0;
if ( tickerlist != "" )
{
for ( j = 0; ( ticker = StrExtract( tickerlist, j ) ) != ""; j++ )
{
data1 = Foreign( ticker, "C" );
data2 = Foreign( symbol, "C" );
log1 = log( data1 / Ref( data1, -1 ) );
log2 = log( data2 / Ref( data2, -1 ) );
CorSum = CorSum + Correlation( log1, log2, Length );
}
}
// take out the diagonal of the correlation matrix and average
PfC = ( CorSum - 1 ) / ( j - 1 );
return ( PfC );
}
// --- init ---
SumPosSize = 0;
// --- asset selection and capital allocation routine ---
if ( ( Status( "stocknum" ) == 0 OR Status("stocknum") == -1 ) )
{
StaticVarRemove( "Mom*" );
StaticVarRemove( "Vol*" );
StaticVarRemove( "Cor*" );
StaticVarRemove( "MVC*" );
StaticVarRemove( "Pos*" );
StaticVarRemove( "Sum*" );
StaticVarRemove( "num*" );
StaticVarRemove( "cas*" );
for ( i = 0; ( symbol = StrExtract( marketlist, i ) ) != ""; i++ )
{
SetForeign ( symbol );
Mom = 100 * ( Close / Ref( Close, -MomLength ) - 1 ); // highest is best
Vol = -100 * StDev( ( Close / Ref( Close, -1 ) - 1 ), VolLength, False ) * sqrt( 12 ); // lowest is best
RestorePriceArrays();
Cor = -CalcPfC( symbol, marketlist, CorLength ); // lowest is best
if ( symbol == _Cashticker )
{
CashMom = Mom;
Mom = Vol = Cor = Null;
StaticVarSet( "CashMom", CashMom );
}
StaticVarSet( "Mom" + symbol, Mom );
StaticVarSet( "Vol" + symbol, Vol );
StaticVarSet( "Cor" + symbol, Cor );
StaticVarSet( "number" + symbol, i + 1 );
}
// generate ranks for M, V and C
StaticVarGenerateRanks( "Rank", "Mom", 0, 1224 );
StaticVarGenerateRanks( "Rank", "Vol", 0, 1224 );
StaticVarGenerateRanks( "Rank", "Cor", 0, 1224 );
for ( i = 0; ( symbol = StrExtract( marketlist, i ) ) != ""; i++ )
{
RankMom = StaticVarGet( "RankMom" + symbol );
RankVol = StaticVarGet( "RankVol" + symbol );
RankCor = StaticVarGet( "RankCor" + symbol );
// in case of tie, RankMom is decisive through + 0.001
MVC = ( wM + 0.001 ) * RankMom + wV * RankVol + wC * RankCor;
StaticVarSet( "MVC" + symbol, -MVC );
}
// generate master ranking
StaticVarGenerateRanks( "Rank", "MVC", 0, 1224 );
for ( i = 0; ( symbol = StrExtract( marketlist, i ) ) != ""; i++ )
{
// --- retrieve stored values ---
RankMVC = StaticVarGet( "RankMVC" + symbol );
Mom = StaticVarGet( "Mom" + symbol );
PosSize = IIf( RankMVC <= TopSize AND Mom > 0, 100 / TopSize, 0 );
SumPosSize = SumPosSize + PosSize;
// --- store position size percentages ---
StaticVarSet( "PosSize" + symbol, PosSize );
}
// --- calculate allocation for out-of-market "cash" fund ---
PosSizeCash = 100 - SumPosSize;
// --- store value ---
StaticVarSet( "SumPosSize" , SumPosSize );
StaticVarSet( "PosSizeCash", PosSizeCash );
for ( i = 0; ( symbol = StrExtract( filterlist, i ) ) != ""; i++ )
{
if ( symbol == _Cashticker )
{
// --- check if cash symbol is part of investment universe too ---
PosSizeCash = StaticVarGet( "PosSizeCash" );
PosSize = StaticVarGet( "PosSize" + symbol );
PosSize = IIf( !IsNull( PosSize ), PosSizeCash + PosSize, PosSizeCash );
SetForeign ( symbol );
Mom = 100 * ( Close / Ref( Close, -MomLength ) - 1 );
RestorePriceArrays();
StaticVarSet( "Mom" + symbol, Mom );
StaticVarSet( "number" + symbol, NoM + 1 );
StaticVarSet( "PosSize" + symbol, PosSize );
StaticVarSet( "RankMom" + symbol, Null );
StaticVarSet( "RankVol" + symbol, Null );
StaticVarSet( "RankCor" + symbol, Null );
StaticVarSet( "MVC" + symbol, Null );
StaticVarSet( "RankMVC" + symbol, Null );
}
}
}
// --- get values and ranks for M, V and C ---
symbol = Name();
Mom = StaticVarGet( "Mom" + symbol );
Vol = -StaticVarGet( "Vol" + symbol );
Cor = -StaticVarGet( "Cor" + symbol );
MVC = -StaticVarGet( "MVC" + symbol );
RankMom = StaticVarGet( "RankMom" + symbol );
RankVol = StaticVarGet( "RankVol" + symbol );
RankCor = StaticVarGet( "RankCor" + symbol );
RankMVC = StaticVarGet( "RankMVC" + symbol );
CashMom = StaticVarGet( "CashMom" );
PosSize = StaticVarGet( "PosSize" + symbol );
SumPosSize = StaticVarGet( "SumPosSize" );
number = StaticVarGet( "number" + symbol );
// --- 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 = ParamToggle( "ExploreFilter", "LastBarInTest|All", 1 );
if ( ExploreFilter )
Filter = 1;
else
Filter = Status( "LastBarInTest" );
// --- sort for exploration only (not on backtest) ---
if ( Status( "actionex" ) == actionExplore )
{
// if ( showCorTable ) SetSortColumns( 2, 3 );
// else SetSortColumns( 2, -11, 10 );
SetSortColumns( 2, 3 );
// --- columns for exploration ---
ColorMom = IIf( Mom > 0 , colorBrightGreen, colorRed );
ColorCash = IIf( CashMom > 0, colorBrightGreen, colorRed );
PosColor = IIf( symbol == _Cashticker, IIf( PosSize > 0, colorYellow, colorWhite ), IIf( PosSize > 0, colorGold, colorWhite ) );
AddColumn( number, "Symbol#", 1.0 );
// --- calculate correlation matrix for exploration ---
if ( showCorTable )
{
ticker = Name();
for ( j = 0; ( symbol = StrExtract( marketlist, j ) ) != ""; j++ )
{
data = Foreign( symbol, "Close" );
logData = log( data / Ref( data, -1 ) );
logClose = log( Close / Ref( Close, -1 ) );
CorXY = Correlation( logClose, logData, CorLength );
Color = IIf( CorXY > 0, ColorRGB( 0, 204, 0 ), IIf( CorXY < 0, colorLightOrange, colorWhite ) );
Color = IIf( ticker == symbol, colorBlack, Color );
AddColumn( CorXY, symbol, 2.3, 1, Color );
}
}
if ( symbol == _CashTicker )
{
AddColumn( CashMom, "Momentum", 3.3, 1, ColorMom );
}
else
{
AddColumn( Mom , "Momentum", 3.3, 1, ColorMom );
}
AddColumn( Vol , "Volatility" , 1.3 );
AddColumn( Cor , "Portf.Cor" , 1.3 );
AddColumn( RankMom, "RankMom" , 1.0 );
AddColumn( RankVol, "RankVol" , 1.0 );
AddColumn( RankCor, "RankCor" , 1.0 );
AddColumn( MVC , "MVC " , 3.3 );
AddColumn( RankMVC, "RankMVC" , 1.0 );
AddColumn( PosSize, "PosSize (%)", 3.3, 1, PosColor );
}
////////////////////////////////////////////////////////////////////////////////////////
//
// --- end of code ---
//
////////////////////////////////////////////////////////////////////////////////////////
```