How to do pair switching logic in AFL?

Hi All -
I would like to code a simple pair switching strategy in AFL but not sure how to do. I appreciate if any one can provide a sample or pointers. I am new to AFL and below is the code I could come up so far.

System Rules:
If Close > 40 Bar MA then exit from Pair Symbol and Buy current symbol.
If Close < 40 Bar MA then exit current symbol and Buy Pair Symbol.
Pair Symbol = IEF ; WatchList = SPY, MDY, IJH

Code:

// Overall Settings
SetOption("InitialEquity",					100000		);
SetOption("MinShares",						100 		);
SetTradeDelays( 1, 1, 1, 1);
BuyPrice = SellPrice = ShortPrice = CoverPrice = Open;

// Detect watchlist & Set Position Size.
wlnumber		= GetOption( "FilterIncludeWatchlist" );
watchlist		= GetCategorySymbols( categoryWatchlist, wlnumber );
numAssets		= StrCount( watchlist, "," ) + 1;
PositionSize = 100/numAssets;
SetPositionSize(PositionSize,				spsShares 	);

// System Rules:
// If Close > 40 Bar MA then exit from Pair Symbol and Buy current symbol. 
// If Close < 40 Bar MA then exit current symbol and Buy Pair Symbol.
// Pair Symbol = IEF ; WatchList = SPY, MDY, IJH

pairSymbol = "IEF";
if( Name() !=  pairSymbol)
{ 
	m = MA( Close, 40 );
	Buy = Cross( Close, m ); 
	Sell = Cross( m, Close ); 
}
if(Name() == pairSymbol)
{
    // ???
}

The following code for a risk parity based paired switching approach was used for a comment on SeekingAlpha:
https://seekingalpha.com/article/3979846-safeguard-equity-portfolio-one-etn-xvz#comment-72444752

Hope this helps,
JW

/* --- RiskParity_PairedSwitching_v1.afl ---
**
** model re-balances TQQQ/UST based on Risk Parity  
** trading at the close of every month/quarter/year
** apply with monthly data
**
** version: June 7, 2016
*/

// --- start of code ---

// --- backtester settings ---
SetOption( "CommissionAmount", 0.00 );
SetOption( "InitialEquity", 100000 );
SetTradeDelays( 0, 0, 0, 0 );
SetOption( "MaxOpenLong", 2 );
SetOption( "MaxOpenPositions", 2);
SetOption( "AllowPositionShrinking", True );
SetBacktestMode( backtestRegular );

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

// --- set lot size ---
RoundLotSize = 0.0001;

// --- detect tradelist ---
wlnumber        = GetOption( "FilterIncludeWatchlist" );
watchlist       = GetCategorySymbols( categoryWatchlist, wlnumber );

// --- init ---
sumVol = sumPosValue = 0;

// --- position allocation routine ---
if ( Status( "stocknum" ) == 0 )
{
    StaticVarRemove( "pos*" );
    StaticVarRemove( "ret*" );
    StaticVarRemove( "vol*" );
    StaticVarRemove( "sum*" );

    for ( i = 0; ( symbol = StrExtract( watchlist, i ) )  != "";  i++ )
    {
        SetForeign( symbol );
			ret1m  = ROC( Close, 1 );
		RestorePriceArrays();
		
		// --- volatility ---
        //
        // --- match for Excel's STDEV.P ---
        vol = StDev( ret1m, 12 );
        //
        // --- port to Excel's STDEV.S ---
        vol  = sqrt( vol ^ 2 * 12 / 11 );
        //
        // --- annualization ---
        vol = vol * sqrt( 12 );  

		sumVol = sumVol + vol;
			       
        StaticVarSet( "ret1m"   + symbol, ret1m );
        StaticVarSet( "vol"     + symbol, vol   );
    }
    
    StaticVarSet( "sumVol", sumVol );
    
    for ( i = 0; ( symbol = StrExtract( watchlist, i ) )  != "";  i++ )
    {
		vol     = StaticVarGet( "vol" + symbol );
		sumVol  = StaticVarGet( "sumVol" );
		
		posValue    = 1 / ( vol / sumVol );		
		sumPosValue = sumPosValue + posValue;
		
		StaticVarSet( "posValue" + symbol, posValue );   
    }
    
    StaticVarSet( "sumPosValue", sumPosValue );
    
    for ( i = 0; ( symbol = StrExtract( watchlist, i ) )  != "";  i++ )
    {
		posValue    = StaticVarGet( "posValue" + symbol );
		sumPosValue = StaticVarGet( "sumPosValue" );
		
		posSize     = 100 * posValue / sumPosValue; 
		
		StaticVarSet( "posSize" + symbol, posSize );   
    }

}    

// --- get values and ranks for Momentum ---
symbol    = Name();
ret1m     = StaticVarGet( "ret1m"    + symbol );
vol       = StaticVarGet( "vol"      + symbol );
posSize   = StaticVarGet( "posSize"  + symbol );

sumVol    = StaticVarGet( "sumVol"     );

// --- rebalance ---
if ( frequency == "Monthly" )
{
	rebalance = 1;
	holdbars  = 1;
}

if ( frequency == "Quarterly" )
{
	rebalance = Month()%3 == 0;
	holdbars  = 3;
}

if ( frequency == "Annually" )
{
	rebalance = Month()%12 == 0;
	holdbars  = 12;
}

// --- trade logic ---
Buy       = rebalance + posSize > 0;
Sell      = 0;
Short     = Cover = 0;
BuyPrice  = Close;
SellPrice = Close;

// --- set position sizes ---
PositionSize = -PosSize; // "-" sign for spsPercentOfEquity

// --- exit at EoM for rebalancing --- 
ApplyStop( stopTypeNBar, stopModeBars, holdbars, exitatstop = 0 );

// --- 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 ) 
{
	SetSortColumns( 2, 1 );

	ColorRet  = IIf( ret1m > 0, colorLime, colorRed );
	
	AddColumn( Close           , "Close (current)", 1.2              );
	AddColumn( Ref( Close, -1 ), "Close (prior)"  , 1.2              );
	AddColumn( ret1m           , "ret1m%"         , 3.3, 1, ColorRet );
	AddColumn( vol             , "Vol (Y%)"       , 3.3              );
	AddColumn( posSize         , "PosSize (%)"    , 3.3              );
	AddColumn( sumVol          , "SumVol (Y%)"    , 3.3              );
}

// --- save portfolio equity ---
_PortfolioName = ParamStr( "~~~PortfolioName", "~~~RP" );

SetCustomBacktestProc("");

if( Status("action") == actionPortfolio )
{
 bo = GetBacktesterObject();
 bo.Backtest();
 AddToComposite( bo.EquityArray, 
                 _PortfolioName, "X", 
                 atcFlagDeleteValues | atcFlagEnableInPortfolio );
}

// --- end of code ---
9 Likes

Thanks. I don’t see anything that helps but will read a second time during the week.

Is your goal to use 1/3 of your equity for SPY, MDY, and IJH when they are above their 40-day MA, and to put all unused equity into IEF? So for example, if SPY and MDY both close below MA(C,40), then you would have ~33.3% of your equity in IJH, and ~66.6% of your equity in IEF?

@TrendXplorer

very nice piece of code and interesting link to the SA article.
And I like also your very clear and easy to read coding style.

Did you do additional tests on other pairs (I saw the screencast for QQQ-IEF)?

1 Like

@beppe

Actually I did, for TQQQ and UST see two comment earlier :slightly_smiling_face:

2 Likes