S&P 500 Reversal Coding System

I’m just getting started with AFL and I coded the simple S&P 500 Reversal system below.

If you have a moment to read it, I welcome feedback on errors and suggested improvements.

// Simple S&P 500 Reversal System -- comments at bottom after the code

SetTradeDelays( 0, 0, 0, 0 );             // no trade delays

SetOption("MaxOpenPositions", 1);
SetOption("ExtraColumnsLocation", 1 ); 

SetCustomBacktestProc( "" );

if ( Status( "action" ) == actionPortfolio )
{
    bo = GetBacktesterObject();
    bo.Backtest(); // run default backtest procedure

    // read Net Profit, Winners and Losers profits from the report
    st = bo.GetPerformanceStats( 0 );
    netProfit = st.GetValue( "NetProfit" );
    tradeProfits = st.GetValue("WinnersTotalProfit") + st.GetValue("LosersTotalLoss");

    bo.AddCustomMetric( "Trading profits", tradeProfits ); 
    bo.AddCustomMetric( "Interest earnings", netProfit - tradeProfits );

} 

BuyPrice  = C;
SellPrice = C;

dnPct = Optimize("DN %", 7.2, 5.0, 10.0, 0.05);
upPct = Optimize("UP %", 8.4, 5.0, 10.0, 0.05);

// prime bar 0
s105exp[0]  = 1;                  // set initial exposure: 1 is long, 0 is out
s105stop[0] = C[0] * (1-dnPct);   // set initial stop
s105Flag[0] = 1;                  // set initial trade flag: 1 is buy, -1 is sell, 9 is end of data and long

for( i = 1; i < BarCount; i++ )
{
	if (s105exp[i-1] == 1)				  // if Long
	{
		if (C[i] >= s105stop[i-1])		// stay in -- must close under
		{
			s105exp[i]  = 1;			// maintain long
			s105stop[i] = Max(s105stop[i-1],(C[i]*(1-(dnPct/100))));		// update sell stop
		}
		else							            // sell and go flat 
		{
			s105Flag[i] = -1;
			s105exp[i]  = 0;
			s105stop[i] = C[i]*(1+(upPct/100));		// set buy stop
		}
	}
	else								            // else if out	
	{
		if (C[i] <= s105stop[i-1])		// stay out -- must close over
		{
			s105exp[i]  = 0;
			s105stop[i] = Min(s105stop[i-1],(C[i]*(1+(upPct/100))));		// update buy stop
		}
		else							            // buy and go long
		{
			s105Flag[i] = 1;
			s105exp[i]  = 1;
			s105stop[i] = C[i]*(1-(dnPct/100));		// set sell stop
		}
	}
}

if (s105Flag[BarCount-1] == 0) { s105Flag[BarCount-1] = 9;}

AddColumn(C,"Close",1.2);
AddColumn(s105exp,  "S105 Exp", 1.0);
AddColumn(s105stop, "S105 Stop", 1.3);
AddColumn(s105Flag, "S105 Flag", 1.0 );


Buy  = (s105Flag == 1);
Sell = (s105Flag ==-1);


Filter = (s105Flag != 0);


/*************************************************************************************************************************
A simple weekly reversal system for the S&P 500 Index (SPX) as an AFL coding exercise.

Backtester Periodicity set to Weekly.

Initial state set to Long.

Sell when the S&P falls -7.2% using a weekly close trailing stop.

Buy when the SPX rises +8.4% using a similar stop. 

Reversal systems, by definition, won't miss a major move up or hold a major move down. 

But their numerous "whipsaws" make them hard to stick with.

This concept is courtesy of Ned Davis Research and NDR Chart S105.

A similar system for the NASDAQ 100 Index is discussed on p.51 of Ned's 2014 book "Being Right or Making Money".

This code is for educational purposes only and is not intended as investment advice.

2-Nov-2017
*************************************************************************************************************************/

Welcome @stevo713 It has been a while since I read the Ned Davis book (love all his stuff) but I also think that model was based upon Marty Zweig’s 4% model published around 1986 in “Winning On Wall Street” (long before Davis’ books).

Your code may be nice but you are in AmiBroker world now. Array’s can help you avoid many of the loops you seem to want to write.

Here is a brief example that only took a few minutes to code (that’s why it may be completely wrong!) but take a look and experiment with it.

DownThreshold = Optimize( "DN %", -4, -10, -2, 0.05 );
UpThreshold = Optimize( "UP %", 4, 2, 10.0, 0.05 );

PercentChange = ROC( C, 1 ); // if running on weekly data

BuySignal = PercentChange > UpThreshold;
SellSignal = PercentChange < DownThreshold;

Buy = BuySignal;
Sell = SellSignal;

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

I’ll try to flush out the code in a few hours.

3 Likes

Thanks, Larry – I forgot about Marty Z’s book (it’s “only” been ~30 years).

As I was reading in the User’s Guide about EXREM I found this link: 4% System

Very similar to your code.

I think the only systematic difference is how I “hold” my stops with

Max(s105stop[i-1],(C[i]*(1-(dnPct/100))));

and

Min(s105stop[i-1],(C[i]*(1+(upPct/100))));

1 Like

@stevo713 We should have known, with a system that was 30 years old there must be some AmiBroker code out there somewhere! Marty Z's book was already about 6 or 7 years old when I read it and it was one of the two or three reasons I became interested in quantifiable investing/trading. Too bad it took me until 2015 to buy AmiBroker. If you like Ned Davis and Marty Zweig you must be a good guy, and if we ever get together I'll buy the first round.

My code somewhat flushed out is below, not proof read but seems to work as I went to test it on the old Value Line Geometric index (but I am not sure if Marty Z meant the Arithmetic, and I don't seem to have my old well worn copy of his book anywhere).

////////////////////////////
// Constants
////////////////////////////

Float = 100000;
MaxPositions = 1;

////////////////////////
// Backtester Options
///////////////////////

SetOption( "CommissionAmount", 0.0 );
SetOption( "MaxOpenPositions", MaxPositions );
SetOption( "InitialEquity", Float );
SetOption( "AllowSameBarExit", True );
SetOption( "AllowPositionShrinking", True );
SetPositionSize( 100 / MaxPositions, spsPercentOfEquity );
SetTradeDelays( 0, 0, 0, 0 ); // trading on the Close, if you want to trade on Next Weekly Open, adjust these numbers

DownThreshold = Optimize( "DN %", -4, -10, -2, 0.05 );
UpThreshold = Optimize( "UP %", 4, 2, 10.0, 0.05 );

PercentChange = ROC( C, 1 ); // if running on weekly data

BuySignal = PercentChange > UpThreshold;
SellSignal = PercentChange < DownThreshold;

Buy = BuySignal;
Sell = SellSignal;

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

Short = Cover = 0;
InLong = Flip(Buy, Sell);
Filter=1;
AddColumn(C, "Close");
AddColumn(PercentChange, "Weekly % Change");
AddColumn(Buy, "Buy", 1.0,colorBlack, iif(Buy==1, colorLime, colorDefault));
AddColumn(InLong, "InLong", 1.0, colorDefault, IIf(InLong, colorGreen, colorDefault));
AddColumn(Sell, "Sell", 1.0,colorBlack, iif(Sell==1, colorRed, colorDefault));

I use Explore for debuggin. Exploration result looks like this.
image

And surprisingly the risk adjusted results are substantially better than buy-and-hold.

image

But before you go trade this with your baby's milk money, I have not had time to proof read it or really debug it!

5 Likes

Nice code & I like the color-coded columns – helpful – thank you.

I’m not surprised about the good risk-adjusted numbers – simple reversal systems avoid prolonged downtrends so exposure is often low and risk-adjusted performance is high(er).

These numbers on the backtest look way too high. When I tried to replicate your test, it looked like the last week’s returns for the long signals are being cut off. In other words, the sell signal week of -4% is not being included in the long returns for the prior long signal. I suspect there is a problem with the Trade Delay in your AFL script conflicting with the Backtester settings in the Trades tab. You can see it clearly when you drop the weekly returns in a spreadsheet and go week by week and compare the returns for each buy signal.

@paulgraham I agree there is a “future leak” in that it trades on the Open of the week that gets its signal data at the end of the weekly Close. It was just a code example I whipped up and shouldn’t have posted the backtest results (but I did say that I had not debugged anything).

So more properly the backtester settings should be adjusted to either trade on the Close, or set trade delays and trade on the next weekly Open.

I think it can be hard-coded,

SetTradeDelays( 0, 0, 0, 0 ); 
BuyPrice = Close;
SellPrice = Close;

or the other choice,

SetTradeDelays(1,1,0,0);
BuyPrice = Open;
SellPrice = Open;

Either way, it still beats buy-and-hold both on an absolute basis and substantially on a risk-adjusted basis.

4 Likes

Thanks for the clarification. I appreciate your work on this subject.